database-devtools 0.1.0

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 (94) hide show
  1. package/LICENSE +5 -0
  2. package/dist/adapter/index.cjs +314 -0
  3. package/dist/adapter/index.cjs.map +1 -0
  4. package/dist/adapter/index.d.cts +34 -0
  5. package/dist/adapter/index.d.ts +34 -0
  6. package/dist/adapter/index.js +6 -0
  7. package/dist/adapter/index.js.map +1 -0
  8. package/dist/adapter-BjJm71wW.d.cts +30 -0
  9. package/dist/adapter-DJSjDewg.d.ts +30 -0
  10. package/dist/adapters/sqlite/index.cjs +251 -0
  11. package/dist/adapters/sqlite/index.cjs.map +1 -0
  12. package/dist/adapters/sqlite/index.d.cts +21 -0
  13. package/dist/adapters/sqlite/index.d.ts +21 -0
  14. package/dist/adapters/sqlite/index.js +6 -0
  15. package/dist/adapters/sqlite/index.js.map +1 -0
  16. package/dist/chunk-2JGL2JVD.js +52 -0
  17. package/dist/chunk-2JGL2JVD.js.map +1 -0
  18. package/dist/chunk-AZECU6D7.js +27 -0
  19. package/dist/chunk-AZECU6D7.js.map +1 -0
  20. package/dist/chunk-BLOSGEQV.js +71 -0
  21. package/dist/chunk-BLOSGEQV.js.map +1 -0
  22. package/dist/chunk-CPVVIH5G.js +258 -0
  23. package/dist/chunk-CPVVIH5G.js.map +1 -0
  24. package/dist/chunk-DGUM43GV.js +10 -0
  25. package/dist/chunk-DGUM43GV.js.map +1 -0
  26. package/dist/chunk-HIDZTOJ3.js +9 -0
  27. package/dist/chunk-HIDZTOJ3.js.map +1 -0
  28. package/dist/chunk-I65IM47D.js +243 -0
  29. package/dist/chunk-I65IM47D.js.map +1 -0
  30. package/dist/chunk-MXVIGMVT.js +3 -0
  31. package/dist/chunk-MXVIGMVT.js.map +1 -0
  32. package/dist/chunk-O6AO7KHA.js +3 -0
  33. package/dist/chunk-O6AO7KHA.js.map +1 -0
  34. package/dist/chunk-TNDOAHT5.js +1784 -0
  35. package/dist/chunk-TNDOAHT5.js.map +1 -0
  36. package/dist/chunk-ZS5MZR75.js +655 -0
  37. package/dist/chunk-ZS5MZR75.js.map +1 -0
  38. package/dist/cli.cjs +2053 -0
  39. package/dist/cli.cjs.map +1 -0
  40. package/dist/client/index.cjs +947 -0
  41. package/dist/client/index.cjs.map +1 -0
  42. package/dist/client/index.d.cts +75 -0
  43. package/dist/client/index.d.ts +75 -0
  44. package/dist/client/index.js +98 -0
  45. package/dist/client/index.js.map +1 -0
  46. package/dist/createDevToolsServer-B7NQ_5H4.d.cts +292 -0
  47. package/dist/createDevToolsServer-DlNMoCJR.d.ts +292 -0
  48. package/dist/handleDeviceSnapshot-CR-4QMdk.d.ts +116 -0
  49. package/dist/handleDeviceSnapshot-cYGuitSc.d.cts +116 -0
  50. package/dist/index.cjs +5877 -0
  51. package/dist/index.cjs.map +1 -0
  52. package/dist/index.d.cts +61 -0
  53. package/dist/index.d.ts +61 -0
  54. package/dist/index.js +2748 -0
  55. package/dist/index.js.map +1 -0
  56. package/dist/inspector/index.cjs +57 -0
  57. package/dist/inspector/index.cjs.map +1 -0
  58. package/dist/inspector/index.d.cts +26 -0
  59. package/dist/inspector/index.d.ts +26 -0
  60. package/dist/inspector/index.js +5 -0
  61. package/dist/inspector/index.js.map +1 -0
  62. package/dist/inspectors/sqlite/index.cjs +306 -0
  63. package/dist/inspectors/sqlite/index.cjs.map +1 -0
  64. package/dist/inspectors/sqlite/index.d.cts +53 -0
  65. package/dist/inspectors/sqlite/index.d.ts +53 -0
  66. package/dist/inspectors/sqlite/index.js +260 -0
  67. package/dist/inspectors/sqlite/index.js.map +1 -0
  68. package/dist/register-BuicFu9L.d.cts +32 -0
  69. package/dist/register-aITLN9Q9.d.ts +32 -0
  70. package/dist/server/index.cjs +1948 -0
  71. package/dist/server/index.cjs.map +1 -0
  72. package/dist/server/index.d.cts +15 -0
  73. package/dist/server/index.d.ts +15 -0
  74. package/dist/server/index.js +7 -0
  75. package/dist/server/index.js.map +1 -0
  76. package/dist/snapshot-Dhyecgo2.d.cts +33 -0
  77. package/dist/snapshot-Dhyecgo2.d.ts +33 -0
  78. package/dist/types/protocol.cjs +309 -0
  79. package/dist/types/protocol.cjs.map +1 -0
  80. package/dist/types/protocol.d.cts +291 -0
  81. package/dist/types/protocol.d.ts +291 -0
  82. package/dist/types/protocol.js +4 -0
  83. package/dist/types/protocol.js.map +1 -0
  84. package/dist/types-BRLJdBRY.d.cts +16 -0
  85. package/dist/types-CF6qEU-_.d.ts +16 -0
  86. package/dist/types-CIA11diJ.d.cts +62 -0
  87. package/dist/types-CKVRCX-e.d.ts +62 -0
  88. package/dist/web/assets/index-CLEaIsUd.css +1 -0
  89. package/dist/web/assets/index-DQrPRTOy.js +100 -0
  90. package/dist/web/assets/sql-wasm-UFUCzYNW.wasm +0 -0
  91. package/dist/web/index.html +26 -0
  92. package/dist/write-C3toEIMs.d.cts +21 -0
  93. package/dist/write-C3toEIMs.d.ts +21 -0
  94. package/package.json +137 -0
@@ -0,0 +1,947 @@
1
+ 'use strict';
2
+
3
+ // src/types/protocol.ts
4
+ var DevToolsRole = {
5
+ MOBILE: "mobile",
6
+ BROWSER: "browser"
7
+ };
8
+ var MessageType = {
9
+ REGISTER: "register",
10
+ PING: "ping",
11
+ PONG: "pong",
12
+ DEVICE_STATUS: "deviceStatus",
13
+ REFRESH_REQUEST: "refreshRequest",
14
+ SNAPSHOT_UPLOAD_REQUESTED: "snapshotUploadRequested",
15
+ REFRESH_STATUS: "refreshStatus",
16
+ SNAPSHOT_READY: "snapshotReady",
17
+ REFRESH_ERROR: "refreshError",
18
+ EXPORT_SNAPSHOT_REQUEST: "exportSnapshotRequest",
19
+ EXPORT_SNAPSHOT_ERROR: "exportSnapshotError",
20
+ BEGIN_TRANSACTION_REQUEST: "beginTransactionRequest",
21
+ COMMIT_TRANSACTION_REQUEST: "commitTransactionRequest",
22
+ ROLLBACK_TRANSACTION_REQUEST: "rollbackTransactionRequest",
23
+ WRITE_REQUEST: "writeRequest",
24
+ BEGIN_TRANSACTION: "beginTransaction",
25
+ COMMIT_TRANSACTION: "commitTransaction",
26
+ ROLLBACK_TRANSACTION: "rollbackTransaction",
27
+ EXECUTE_WRITE: "executeWrite",
28
+ TRANSACTION_ACK: "transactionAck",
29
+ WRITE_ACK: "writeAck",
30
+ TRANSACTION_STATUS: "transactionStatus",
31
+ WRITE_RESULT: "writeResult",
32
+ WRITE_ERROR: "writeError"
33
+ };
34
+ var DEVTOOLS_WS_PATH = "/ws";
35
+ var DEFAULT_DEVTOOLS_PORT = 3847;
36
+ var DEVICE_SNAPSHOT_API_PATH = "/api/devices";
37
+ var PROJECT_DATABASE_API_PATH = "/api/project/database";
38
+ var PROJECT_DATABASE_META_API_PATH = "/api/project/database/meta";
39
+ var PROJECT_DATABASES_API_PATH = "/api/project/databases";
40
+ var PROJECT_DEVICE_EXPORTS_API_PATH = "/api/project/devices";
41
+ function buildDevToolsWsUrl(host = "localhost", port = DEFAULT_DEVTOOLS_PORT) {
42
+ return `ws://${host}:${port}${DEVTOOLS_WS_PATH}`;
43
+ }
44
+ function buildDeviceSnapshotUrl(httpBaseUrl, deviceId) {
45
+ const base = httpBaseUrl.replace(/\/$/, "");
46
+ return `${base}${DEVICE_SNAPSHOT_API_PATH}/${encodeURIComponent(deviceId)}/snapshot`;
47
+ }
48
+ function wsUrlToHttpUrl(wsUrl) {
49
+ const url = new URL(wsUrl);
50
+ url.protocol = url.protocol === "wss:" ? "https:" : "http:";
51
+ url.pathname = "";
52
+ url.search = "";
53
+ url.hash = "";
54
+ return url.toString().replace(/\/$/, "");
55
+ }
56
+ function hasMessageType(value) {
57
+ return typeof value === "object" && value !== null && typeof value.type === "string";
58
+ }
59
+ function isSnapshotUploadRequestedMessage(value) {
60
+ if (!hasMessageType(value) || value.type !== MessageType.SNAPSHOT_UPLOAD_REQUESTED) {
61
+ return false;
62
+ }
63
+ const message = value;
64
+ return typeof message.deviceId === "string" && message.refreshType === "snapshot";
65
+ }
66
+ function isRefreshStatusMessage(value) {
67
+ if (!hasMessageType(value) || value.type !== MessageType.REFRESH_STATUS) {
68
+ return false;
69
+ }
70
+ const message = value;
71
+ return typeof message.deviceId === "string" && typeof message.state === "string";
72
+ }
73
+ function isSnapshotReadyMessage(value) {
74
+ if (!hasMessageType(value) || value.type !== MessageType.SNAPSHOT_READY) {
75
+ return false;
76
+ }
77
+ const message = value;
78
+ return typeof message.deviceId === "string" && typeof message.size === "number" && typeof message.exportedAt === "number" && typeof message.kind === "string" && typeof message.mimeType === "string";
79
+ }
80
+ function isRefreshErrorMessage(value) {
81
+ if (!hasMessageType(value) || value.type !== MessageType.REFRESH_ERROR) {
82
+ return false;
83
+ }
84
+ const message = value;
85
+ return typeof message.deviceId === "string" && typeof message.code === "string";
86
+ }
87
+ function isExportSnapshotErrorMessage(value) {
88
+ if (!hasMessageType(value) || value.type !== MessageType.EXPORT_SNAPSHOT_ERROR) {
89
+ return false;
90
+ }
91
+ const message = value;
92
+ return typeof message.code === "string" && typeof message.message === "string";
93
+ }
94
+ function isPingMessage(value) {
95
+ if (!hasMessageType(value) || value.type !== MessageType.PING) {
96
+ return false;
97
+ }
98
+ const message = value;
99
+ return typeof message.pingId === "string";
100
+ }
101
+ function isDeviceStatusMessage(value) {
102
+ if (!hasMessageType(value) || value.type !== MessageType.DEVICE_STATUS) {
103
+ return false;
104
+ }
105
+ const message = value;
106
+ return typeof message.payload === "object" && message.payload !== null;
107
+ }
108
+ function isBeginTransactionMessage(value) {
109
+ if (!hasMessageType(value) || value.type !== MessageType.BEGIN_TRANSACTION) {
110
+ return false;
111
+ }
112
+ const message = value;
113
+ return typeof message.transactionId === "string";
114
+ }
115
+ function isCommitTransactionMessage(value) {
116
+ if (!hasMessageType(value) || value.type !== MessageType.COMMIT_TRANSACTION) {
117
+ return false;
118
+ }
119
+ const message = value;
120
+ return typeof message.transactionId === "string";
121
+ }
122
+ function isRollbackTransactionMessage(value) {
123
+ if (!hasMessageType(value) || value.type !== MessageType.ROLLBACK_TRANSACTION) {
124
+ return false;
125
+ }
126
+ const message = value;
127
+ return typeof message.transactionId === "string";
128
+ }
129
+ function isExecuteWriteMessage(value) {
130
+ if (!hasMessageType(value) || value.type !== MessageType.EXECUTE_WRITE) {
131
+ return false;
132
+ }
133
+ const message = value;
134
+ return typeof message.writeId === "string" && typeof message.transactionId === "string" && typeof message.operation === "object" && message.operation !== null;
135
+ }
136
+ function isTransactionStatusMessage(value) {
137
+ if (!hasMessageType(value) || value.type !== MessageType.TRANSACTION_STATUS) {
138
+ return false;
139
+ }
140
+ const message = value;
141
+ return typeof message.transactionId === "string" && typeof message.deviceId === "string" && typeof message.state === "string";
142
+ }
143
+ function isWriteResultMessage(value) {
144
+ if (!hasMessageType(value) || value.type !== MessageType.WRITE_RESULT) {
145
+ return false;
146
+ }
147
+ const message = value;
148
+ return typeof message.writeId === "string" && typeof message.transactionId === "string" && typeof message.rowsAffected === "number";
149
+ }
150
+ function isWriteErrorMessage(value) {
151
+ if (!hasMessageType(value) || value.type !== MessageType.WRITE_ERROR) {
152
+ return false;
153
+ }
154
+ const message = value;
155
+ return typeof message.transactionId === "string" && typeof message.code === "string";
156
+ }
157
+
158
+ // src/utils/ids.ts
159
+ function generateDeviceId() {
160
+ return generateUniqueId("device");
161
+ }
162
+ function generateTransactionId() {
163
+ return generateUniqueId("txn");
164
+ }
165
+ function generateWriteId() {
166
+ return generateUniqueId("write");
167
+ }
168
+ function generateUniqueId(prefix) {
169
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
170
+ return `${prefix}-${crypto.randomUUID()}`;
171
+ }
172
+ const random = Math.random().toString(36).slice(2, 10);
173
+ return `${prefix}-${Date.now()}-${random}`;
174
+ }
175
+
176
+ // src/utils/reconnect.ts
177
+ var DEFAULT_BASE_MS = 1e3;
178
+ var DEFAULT_MAX_MS = 3e4;
179
+ var DEFAULT_JITTER_MS = 500;
180
+ function calculateBackoffDelay(attempt, options = {}) {
181
+ const baseMs = options.baseMs ?? DEFAULT_BASE_MS;
182
+ const maxMs = options.maxMs ?? DEFAULT_MAX_MS;
183
+ const jitterMs = options.jitterMs ?? DEFAULT_JITTER_MS;
184
+ const exponential = baseMs * 2 ** attempt;
185
+ const capped = Math.min(exponential, maxMs);
186
+ const jitter = Math.floor(Math.random() * jitterMs);
187
+ return capped + jitter;
188
+ }
189
+ var ReconnectScheduler = class {
190
+ attempt = 0;
191
+ timer = null;
192
+ onReconnect;
193
+ options;
194
+ constructor({ onReconnect, ...options }) {
195
+ this.onReconnect = onReconnect;
196
+ this.options = options;
197
+ }
198
+ schedule() {
199
+ if (this.timer !== null) {
200
+ return;
201
+ }
202
+ const delay = calculateBackoffDelay(this.attempt, this.options);
203
+ this.attempt += 1;
204
+ this.timer = setTimeout(() => {
205
+ this.timer = null;
206
+ this.onReconnect();
207
+ }, delay);
208
+ }
209
+ reset() {
210
+ this.attempt = 0;
211
+ this.cancel();
212
+ }
213
+ cancel() {
214
+ if (this.timer !== null) {
215
+ clearTimeout(this.timer);
216
+ this.timer = null;
217
+ }
218
+ }
219
+ };
220
+
221
+ // src/client/createDevToolsClient.ts
222
+ var WRITE_TIMEOUT_MS = 3e4;
223
+ function createDevToolsClient(options = {}) {
224
+ const role = options.role ?? DevToolsRole.MOBILE;
225
+ const WebSocketImpl = options.WebSocketImpl ?? globalThis.WebSocket;
226
+ if (!WebSocketImpl) {
227
+ throw new Error("WebSocket is not available in this environment.");
228
+ }
229
+ let socket = null;
230
+ let deviceStatus = null;
231
+ let connectionState = "disconnected";
232
+ let lastConnectionError = null;
233
+ let intentionalDisconnect = false;
234
+ let transactionState = "idle";
235
+ let activeTransactionId = null;
236
+ let activeDeviceId = null;
237
+ const pendingTransaction = /* @__PURE__ */ new Map();
238
+ const pendingWrites = /* @__PURE__ */ new Map();
239
+ const deviceId = role === DevToolsRole.MOBILE ? options.deviceId ?? generateDeviceId() : void 0;
240
+ let wsUrl = options.serverUrl ?? buildDevToolsWsUrl("localhost", DEFAULT_DEVTOOLS_PORT);
241
+ const setConnectionState = (state) => {
242
+ if (connectionState === state) {
243
+ return;
244
+ }
245
+ connectionState = state;
246
+ options.onConnectionStateChange?.(state);
247
+ };
248
+ const setTransactionState = (state) => {
249
+ if (transactionState === state) {
250
+ return;
251
+ }
252
+ transactionState = state;
253
+ options.onTransactionStateChange?.(state);
254
+ };
255
+ const resetTransaction = () => {
256
+ setTransactionState("idle");
257
+ activeTransactionId = null;
258
+ activeDeviceId = null;
259
+ };
260
+ const rejectPendingTransaction = (transactionId, error) => {
261
+ const pending = pendingTransaction.get(transactionId);
262
+ if (pending) {
263
+ pending.reject(error);
264
+ pendingTransaction.delete(transactionId);
265
+ }
266
+ };
267
+ const rejectAllPending = (error) => {
268
+ for (const [id, pending] of pendingTransaction) {
269
+ pending.reject(error);
270
+ pendingTransaction.delete(id);
271
+ }
272
+ for (const [id, pending] of pendingWrites) {
273
+ pending.reject(error);
274
+ pendingWrites.delete(id);
275
+ }
276
+ resetTransaction();
277
+ };
278
+ const reconnectScheduler = new ReconnectScheduler({
279
+ onReconnect: () => {
280
+ openSocket();
281
+ }
282
+ });
283
+ const send = (message) => {
284
+ if (!socket || socket.readyState !== WebSocketImpl.OPEN) {
285
+ return;
286
+ }
287
+ socket.send(JSON.stringify({ ...message, timestamp: Date.now() }));
288
+ };
289
+ const sendRegister = () => {
290
+ send({
291
+ type: MessageType.REGISTER,
292
+ role,
293
+ ...deviceId ? { deviceId } : {},
294
+ ...options.metadata ? { metadata: options.metadata } : {}
295
+ });
296
+ };
297
+ const handleServerMessage = (parsed) => {
298
+ if (isPingMessage(parsed)) {
299
+ send({
300
+ type: MessageType.PONG,
301
+ pingId: parsed.pingId
302
+ });
303
+ return;
304
+ }
305
+ if (isDeviceStatusMessage(parsed)) {
306
+ deviceStatus = parsed.payload;
307
+ options.onDeviceStatus?.(parsed.payload);
308
+ return;
309
+ }
310
+ if (isSnapshotUploadRequestedMessage(parsed)) {
311
+ options.onSnapshotUploadRequested?.(parsed);
312
+ return;
313
+ }
314
+ if (isExportSnapshotErrorMessage(parsed)) {
315
+ options.onExportSnapshotError?.(parsed);
316
+ return;
317
+ }
318
+ if (isRefreshStatusMessage(parsed)) {
319
+ options.onRefreshStatus?.(parsed);
320
+ return;
321
+ }
322
+ if (isSnapshotReadyMessage(parsed)) {
323
+ options.onSnapshotReady?.(parsed);
324
+ return;
325
+ }
326
+ if (isRefreshErrorMessage(parsed)) {
327
+ options.onRefreshError?.(parsed);
328
+ return;
329
+ }
330
+ if (isBeginTransactionMessage(parsed)) {
331
+ options.onBeginTransaction?.(parsed);
332
+ return;
333
+ }
334
+ if (isCommitTransactionMessage(parsed)) {
335
+ options.onCommitTransaction?.(parsed);
336
+ return;
337
+ }
338
+ if (isRollbackTransactionMessage(parsed)) {
339
+ options.onRollbackTransaction?.(parsed);
340
+ return;
341
+ }
342
+ if (isExecuteWriteMessage(parsed)) {
343
+ options.onExecuteWrite?.(parsed);
344
+ return;
345
+ }
346
+ if (isTransactionStatusMessage(parsed)) {
347
+ options.onTransactionStatus?.(parsed);
348
+ if (role !== DevToolsRole.BROWSER) {
349
+ return;
350
+ }
351
+ const pending = pendingTransaction.get(parsed.transactionId);
352
+ if (parsed.state === "open" && pending?.action === "begin") {
353
+ setTransactionState("open");
354
+ activeTransactionId = parsed.transactionId;
355
+ activeDeviceId = parsed.deviceId;
356
+ pending.resolve();
357
+ pendingTransaction.delete(parsed.transactionId);
358
+ return;
359
+ }
360
+ if (parsed.state === "idle" && pending && (pending.action === "commit" || pending.action === "rollback")) {
361
+ resetTransaction();
362
+ pending.resolve();
363
+ pendingTransaction.delete(parsed.transactionId);
364
+ return;
365
+ }
366
+ if (parsed.state === "failed") {
367
+ rejectPendingTransaction(
368
+ parsed.transactionId,
369
+ new Error(parsed.message ?? "Transaction failed")
370
+ );
371
+ resetTransaction();
372
+ }
373
+ return;
374
+ }
375
+ if (isWriteResultMessage(parsed)) {
376
+ options.onWriteResult?.(parsed);
377
+ const pending = pendingWrites.get(parsed.writeId);
378
+ if (pending) {
379
+ pending.resolve({ rowsAffected: parsed.rowsAffected });
380
+ pendingWrites.delete(parsed.writeId);
381
+ }
382
+ return;
383
+ }
384
+ if (isWriteErrorMessage(parsed)) {
385
+ options.onWriteError?.(parsed);
386
+ const error = new Error(parsed.message);
387
+ if (parsed.writeId) {
388
+ const pendingWrite = pendingWrites.get(parsed.writeId);
389
+ if (pendingWrite) {
390
+ pendingWrite.reject(error);
391
+ pendingWrites.delete(parsed.writeId);
392
+ }
393
+ } else {
394
+ rejectPendingTransaction(parsed.transactionId, error);
395
+ resetTransaction();
396
+ }
397
+ }
398
+ };
399
+ const openSocket = () => {
400
+ if (socket && (socket.readyState === WebSocketImpl.OPEN || socket.readyState === WebSocketImpl.CONNECTING)) {
401
+ return;
402
+ }
403
+ setConnectionState(intentionalDisconnect ? "disconnected" : connectionState === "disconnected" ? "connecting" : "reconnecting");
404
+ socket = new WebSocketImpl(wsUrl);
405
+ socket.addEventListener("open", () => {
406
+ reconnectScheduler.reset();
407
+ lastConnectionError = null;
408
+ setConnectionState("connected");
409
+ options.onConnect?.();
410
+ sendRegister();
411
+ });
412
+ socket.addEventListener("message", (event) => {
413
+ let parsed;
414
+ try {
415
+ parsed = JSON.parse(String(event.data));
416
+ } catch {
417
+ return;
418
+ }
419
+ handleServerMessage(parsed);
420
+ });
421
+ socket.addEventListener("close", () => {
422
+ socket = null;
423
+ options.onDisconnect?.();
424
+ rejectAllPending(new Error("Disconnected from DevTools hub"));
425
+ if (intentionalDisconnect) {
426
+ setConnectionState("disconnected");
427
+ return;
428
+ }
429
+ setConnectionState("reconnecting");
430
+ reconnectScheduler.schedule();
431
+ });
432
+ socket.addEventListener("error", () => {
433
+ const error = new Error(`WebSocket connection failed: ${wsUrl}`);
434
+ lastConnectionError = error.message;
435
+ options.onError?.(error);
436
+ });
437
+ };
438
+ const connect = () => {
439
+ intentionalDisconnect = false;
440
+ openSocket();
441
+ };
442
+ const disconnect = () => {
443
+ intentionalDisconnect = true;
444
+ reconnectScheduler.cancel();
445
+ socket?.close();
446
+ socket = null;
447
+ rejectAllPending(new Error("Disconnected"));
448
+ setConnectionState("disconnected");
449
+ };
450
+ const setServerUrl = (url) => {
451
+ wsUrl = url;
452
+ const wasConnected = connectionState === "connected" || connectionState === "connecting" || connectionState === "reconnecting";
453
+ disconnect();
454
+ intentionalDisconnect = false;
455
+ if (wasConnected) {
456
+ connect();
457
+ }
458
+ };
459
+ const requestRefresh = (targetDeviceId) => {
460
+ send({
461
+ type: MessageType.REFRESH_REQUEST,
462
+ deviceId: targetDeviceId,
463
+ refreshType: "snapshot"
464
+ });
465
+ };
466
+ const requestExportSnapshot = () => {
467
+ send({
468
+ type: MessageType.EXPORT_SNAPSHOT_REQUEST,
469
+ refreshType: "snapshot"
470
+ });
471
+ };
472
+ const sendTransactionAck = (message) => {
473
+ send({
474
+ type: MessageType.TRANSACTION_ACK,
475
+ ...message
476
+ });
477
+ };
478
+ const sendWriteAck = (message) => {
479
+ send({
480
+ type: MessageType.WRITE_ACK,
481
+ ...message
482
+ });
483
+ };
484
+ const waitForTransaction = (transactionId, action) => {
485
+ return new Promise((resolve, reject) => {
486
+ const timer = setTimeout(() => {
487
+ pendingTransaction.delete(transactionId);
488
+ reject(new Error(`Transaction ${action} timed out`));
489
+ }, WRITE_TIMEOUT_MS);
490
+ pendingTransaction.set(transactionId, {
491
+ action,
492
+ resolve: () => {
493
+ clearTimeout(timer);
494
+ resolve();
495
+ },
496
+ reject: (error) => {
497
+ clearTimeout(timer);
498
+ reject(error);
499
+ }
500
+ });
501
+ });
502
+ };
503
+ const beginTransaction = async (targetDeviceId) => {
504
+ if (role !== DevToolsRole.BROWSER) {
505
+ throw new Error("beginTransaction is only available for browser clients");
506
+ }
507
+ if (transactionState !== "idle") {
508
+ throw new Error("A transaction is already active");
509
+ }
510
+ const transactionId = generateTransactionId();
511
+ setTransactionState("opening");
512
+ activeTransactionId = transactionId;
513
+ activeDeviceId = targetDeviceId;
514
+ const promise = waitForTransaction(transactionId, "begin");
515
+ send({
516
+ type: MessageType.BEGIN_TRANSACTION_REQUEST,
517
+ transactionId,
518
+ deviceId: targetDeviceId
519
+ });
520
+ await promise;
521
+ };
522
+ const commitTransaction = async () => {
523
+ if (role !== DevToolsRole.BROWSER) {
524
+ throw new Error("commitTransaction is only available for browser clients");
525
+ }
526
+ if (!activeTransactionId || !activeDeviceId || transactionState !== "open") {
527
+ throw new Error("No open transaction");
528
+ }
529
+ const transactionId = activeTransactionId;
530
+ const deviceIdForCommit = activeDeviceId;
531
+ setTransactionState("committing");
532
+ const promise = waitForTransaction(transactionId, "commit");
533
+ send({
534
+ type: MessageType.COMMIT_TRANSACTION_REQUEST,
535
+ transactionId,
536
+ deviceId: deviceIdForCommit
537
+ });
538
+ await promise;
539
+ };
540
+ const rollbackTransaction = async () => {
541
+ if (role !== DevToolsRole.BROWSER) {
542
+ throw new Error("rollbackTransaction is only available for browser clients");
543
+ }
544
+ if (!activeTransactionId || !activeDeviceId) {
545
+ throw new Error("No active transaction");
546
+ }
547
+ const transactionId = activeTransactionId;
548
+ const deviceIdForRollback = activeDeviceId;
549
+ setTransactionState("rolling_back");
550
+ const promise = waitForTransaction(transactionId, "rollback");
551
+ send({
552
+ type: MessageType.ROLLBACK_TRANSACTION_REQUEST,
553
+ transactionId,
554
+ deviceId: deviceIdForRollback
555
+ });
556
+ await promise;
557
+ };
558
+ const executeWrite = async (operation) => {
559
+ if (role !== DevToolsRole.BROWSER) {
560
+ throw new Error("executeWrite is only available for browser clients");
561
+ }
562
+ if (!activeTransactionId || !activeDeviceId || transactionState !== "open") {
563
+ throw new Error("No open transaction");
564
+ }
565
+ const writeId = generateWriteId();
566
+ const promise = new Promise((resolve, reject) => {
567
+ const timer = setTimeout(() => {
568
+ pendingWrites.delete(writeId);
569
+ reject(new Error("Write operation timed out"));
570
+ }, WRITE_TIMEOUT_MS);
571
+ pendingWrites.set(writeId, {
572
+ resolve: (result) => {
573
+ clearTimeout(timer);
574
+ resolve(result);
575
+ },
576
+ reject: (error) => {
577
+ clearTimeout(timer);
578
+ reject(error);
579
+ }
580
+ });
581
+ });
582
+ send({
583
+ type: MessageType.WRITE_REQUEST,
584
+ writeId,
585
+ transactionId: activeTransactionId,
586
+ deviceId: activeDeviceId,
587
+ operation
588
+ });
589
+ return promise;
590
+ };
591
+ return {
592
+ connect,
593
+ disconnect,
594
+ getConnectionState: () => connectionState,
595
+ getLastConnectionError: () => lastConnectionError,
596
+ getDeviceStatus: () => deviceStatus,
597
+ getDeviceId: () => deviceId,
598
+ getServerUrl: () => wsUrl,
599
+ setServerUrl,
600
+ send,
601
+ requestRefresh,
602
+ requestExportSnapshot,
603
+ sendTransactionAck,
604
+ sendWriteAck,
605
+ beginTransaction,
606
+ commitTransaction,
607
+ rollbackTransaction,
608
+ executeWrite,
609
+ getTransactionState: () => transactionState,
610
+ getActiveTransactionId: () => activeTransactionId
611
+ };
612
+ }
613
+
614
+ // src/client/fetchSnapshot.ts
615
+ async function fetchSnapshot(downloadUrl) {
616
+ let response;
617
+ try {
618
+ response = await fetch(downloadUrl);
619
+ } catch (error) {
620
+ const networkMessage = error instanceof Error ? error.message : String(error);
621
+ throw new Error(
622
+ `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.`
623
+ );
624
+ }
625
+ if (!response.ok) {
626
+ throw new Error(
627
+ `Failed to fetch snapshot from ${downloadUrl}: ${response.status} ${response.statusText}`
628
+ );
629
+ }
630
+ return response.arrayBuffer();
631
+ }
632
+
633
+ // src/client/resolveDeviceDatabaseUrl.ts
634
+ function resolveApiBase(serverUrl, options) {
635
+ if (options?.useSameOriginApi && typeof window !== "undefined") {
636
+ return window.location.origin;
637
+ }
638
+ if (serverUrl.startsWith("ws://") || serverUrl.startsWith("wss://")) {
639
+ return wsUrlToHttpUrl(serverUrl);
640
+ }
641
+ return serverUrl.replace(/\/$/, "");
642
+ }
643
+ function resolveDeviceExportDatabaseUrl(serverUrl, deviceId, options) {
644
+ const base = resolveApiBase(serverUrl, options);
645
+ return `${base}${PROJECT_DEVICE_EXPORTS_API_PATH}/${encodeURIComponent(deviceId)}/database`;
646
+ }
647
+ function resolveDeviceExportDatabaseMetaUrl(serverUrl, deviceId, options) {
648
+ const base = resolveApiBase(serverUrl, options);
649
+ return `${base}${PROJECT_DEVICE_EXPORTS_API_PATH}/${encodeURIComponent(deviceId)}/database/meta`;
650
+ }
651
+ function resolveDeviceExportsUrl(serverUrl, options) {
652
+ const base = resolveApiBase(serverUrl, options);
653
+ return `${base}${PROJECT_DEVICE_EXPORTS_API_PATH}`;
654
+ }
655
+
656
+ // src/client/deviceDatabase.ts
657
+ async function fetchJson(url, errorLabel) {
658
+ let response;
659
+ try {
660
+ response = await fetch(url);
661
+ } catch (error) {
662
+ const networkMessage = error instanceof Error ? error.message : String(error);
663
+ throw new Error(`Network error fetching ${errorLabel} from ${url}. ${networkMessage}`);
664
+ }
665
+ if (!response.ok) {
666
+ throw new Error(`Failed to fetch ${errorLabel} from ${url}: ${response.status} ${response.statusText}`);
667
+ }
668
+ return await response.json();
669
+ }
670
+ async function fetchDeviceExportMeta(serverUrl, deviceId, options) {
671
+ const url = resolveDeviceExportDatabaseMetaUrl(serverUrl, deviceId, options);
672
+ return fetchJson(url, "device export metadata");
673
+ }
674
+ async function fetchDeviceExportDatabase(serverUrl, deviceId, options) {
675
+ const url = resolveDeviceExportDatabaseUrl(serverUrl, deviceId, options);
676
+ return fetchSnapshot(url);
677
+ }
678
+ async function fetchDeviceExports(serverUrl, options) {
679
+ const url = resolveDeviceExportsUrl(serverUrl, options);
680
+ const payload = await fetchJson(url, "device exports");
681
+ return payload.devices ?? [];
682
+ }
683
+
684
+ // src/client/resolveProjectDatabaseUrl.ts
685
+ function resolveApiBase2(serverUrl, options) {
686
+ if (options?.useSameOriginApi && typeof window !== "undefined") {
687
+ return window.location.origin;
688
+ }
689
+ if (serverUrl.startsWith("ws://") || serverUrl.startsWith("wss://")) {
690
+ return wsUrlToHttpUrl(serverUrl);
691
+ }
692
+ return serverUrl.replace(/\/$/, "");
693
+ }
694
+ function resolveProjectDatabaseUrl(serverUrl, options) {
695
+ return `${resolveApiBase2(serverUrl, options)}${PROJECT_DATABASE_API_PATH}`;
696
+ }
697
+ function resolveProjectDatabaseMetaUrl(serverUrl, options) {
698
+ return `${resolveApiBase2(serverUrl, options)}${PROJECT_DATABASE_META_API_PATH}`;
699
+ }
700
+ function resolveProjectDatabasesUrl(serverUrl, options) {
701
+ return `${resolveApiBase2(serverUrl, options)}${PROJECT_DATABASES_API_PATH}`;
702
+ }
703
+
704
+ // src/client/projectDatabase.ts
705
+ async function fetchProjectDatabaseMeta(serverUrl, options) {
706
+ const url = resolveProjectDatabaseMetaUrl(serverUrl, options);
707
+ let response;
708
+ try {
709
+ response = await fetch(url);
710
+ } catch (error) {
711
+ const networkMessage = error instanceof Error ? error.message : String(error);
712
+ throw new Error(`Network error fetching project database metadata from ${url}. ${networkMessage}`);
713
+ }
714
+ if (!response.ok) {
715
+ throw new Error(
716
+ `Failed to fetch project database metadata from ${url}: ${response.status} ${response.statusText}`
717
+ );
718
+ }
719
+ return await response.json();
720
+ }
721
+ async function fetchProjectDatabase(serverUrl, options) {
722
+ const url = resolveProjectDatabaseUrl(serverUrl, options);
723
+ return fetchSnapshot(url);
724
+ }
725
+ async function fetchProjectDatabases(serverUrl, options) {
726
+ const url = resolveProjectDatabasesUrl(serverUrl, options);
727
+ let response;
728
+ try {
729
+ response = await fetch(url);
730
+ } catch (error) {
731
+ const networkMessage = error instanceof Error ? error.message : String(error);
732
+ throw new Error(`Network error fetching project databases from ${url}. ${networkMessage}`);
733
+ }
734
+ if (!response.ok) {
735
+ throw new Error(
736
+ `Failed to fetch project databases from ${url}: ${response.status} ${response.statusText}`
737
+ );
738
+ }
739
+ const payload = await response.json();
740
+ return payload.databases ?? [];
741
+ }
742
+
743
+ // src/client/resolveSnapshotDownloadUrl.ts
744
+ function resolveSnapshotDownloadUrl(hubWsUrl, deviceId, options = {}) {
745
+ const path = `${DEVICE_SNAPSHOT_API_PATH}/${encodeURIComponent(deviceId)}/snapshot`;
746
+ if (options.useSameOriginApi) {
747
+ return path;
748
+ }
749
+ return buildDeviceSnapshotUrl(wsUrlToHttpUrl(hubWsUrl), deviceId);
750
+ }
751
+
752
+ // src/client/formatSyncError.ts
753
+ function formatSyncErrorMessage(code, message) {
754
+ return formatRefreshErrorMessage(code, message);
755
+ }
756
+ function formatRefreshErrorMessage(code, message) {
757
+ switch (code) {
758
+ case "DEVICE_OFFLINE":
759
+ return "Selected device disconnected. Reconnect the mobile app and try again.";
760
+ case "REFRESH_IN_PROGRESS":
761
+ return "A refresh is already in progress for this device.";
762
+ case "EXPORT_FAILED":
763
+ return `Device failed to export or upload snapshot: ${message}`;
764
+ case "UPLOAD_FAILED":
765
+ return `Snapshot upload failed on the hub: ${message}`;
766
+ case "TIMEOUT":
767
+ return "Timed out waiting for the device to upload a snapshot.";
768
+ case "SNAPSHOT_NOT_FOUND":
769
+ return "Snapshot expired or was not found. Click Refresh again.";
770
+ case "INVALID_REQUEST":
771
+ return `Invalid refresh request: ${message}`;
772
+ default:
773
+ return message;
774
+ }
775
+ }
776
+
777
+ // src/utils/deviceLabel.ts
778
+ function shortenDeviceId(deviceId, visibleChars = 4) {
779
+ if (deviceId.length <= visibleChars * 2 + 1) {
780
+ return deviceId;
781
+ }
782
+ return `${deviceId.slice(0, visibleChars)}\u2026${deviceId.slice(-visibleChars)}`;
783
+ }
784
+ function resolveDeviceDisplayName(deviceId, deviceStatus, fallback) {
785
+ const mobile = deviceStatus?.mobiles.find((device) => device.deviceId === deviceId);
786
+ if (mobile?.metadata?.appName) {
787
+ return mobile.metadata.appName;
788
+ }
789
+ if (fallback?.databaseName) {
790
+ return fallback.databaseName;
791
+ }
792
+ if (fallback?.label) {
793
+ return fallback.label;
794
+ }
795
+ return "Device export";
796
+ }
797
+ function resolveDeviceLabel(deviceId, deviceStatus, fallback) {
798
+ return {
799
+ deviceId,
800
+ deviceName: resolveDeviceDisplayName(deviceId, deviceStatus, fallback)
801
+ };
802
+ }
803
+ function formatSnapshotReceivedMessage(deviceLabel, databaseName) {
804
+ return `${deviceLabel.deviceName} (${shortenDeviceId(deviceLabel.deviceId)}) \xB7 ${databaseName}`;
805
+ }
806
+ function buildSnapshotLoadedToast(initiator, deviceId, databaseName, deviceStatus, fallback) {
807
+ const deviceLabel = resolveDeviceLabel(deviceId, deviceStatus, fallback);
808
+ return {
809
+ title: initiator === "mobile" ? "New database received" : "Database refreshed",
810
+ message: formatSnapshotReceivedMessage(deviceLabel, databaseName)
811
+ };
812
+ }
813
+
814
+ // src/utils/resolvePreferredDeviceId.ts
815
+ function findMobileByBundleId(mobiles, bundleId) {
816
+ if (!bundleId) {
817
+ return void 0;
818
+ }
819
+ return mobiles.find((mobile) => mobile.metadata?.bundleId === bundleId);
820
+ }
821
+ function resolvePreferredDeviceId(input) {
822
+ const connectedIds = new Set(input.mobiles.map((device) => device.deviceId));
823
+ const exportById = new Map(input.exports.map((entry) => [entry.deviceId, entry]));
824
+ if (input.selectedDeviceId) {
825
+ if (connectedIds.has(input.selectedDeviceId)) {
826
+ return input.selectedDeviceId;
827
+ }
828
+ const selectedExport = exportById.get(input.selectedDeviceId);
829
+ if (selectedExport) {
830
+ const bundleMatch = findMobileByBundleId(input.mobiles, selectedExport.bundleId);
831
+ if (bundleMatch) {
832
+ return bundleMatch.deviceId;
833
+ }
834
+ if (input.mobiles.length === 1) {
835
+ return input.mobiles[0].deviceId;
836
+ }
837
+ return input.selectedDeviceId;
838
+ }
839
+ }
840
+ return input.mobiles[0]?.deviceId ?? input.exports[0]?.deviceId ?? null;
841
+ }
842
+ function resolveLiveSwitchTarget(input) {
843
+ if (!input.selectedDeviceId || input.mobiles.length === 0) {
844
+ return null;
845
+ }
846
+ if (input.mobiles.some((mobile) => mobile.deviceId === input.selectedDeviceId)) {
847
+ return null;
848
+ }
849
+ const selectedExport = input.exports.find((entry) => entry.deviceId === input.selectedDeviceId);
850
+ const bundleMatch = findMobileByBundleId(input.mobiles, selectedExport?.bundleId);
851
+ if (bundleMatch) {
852
+ return bundleMatch.deviceId;
853
+ }
854
+ return input.mobiles[0]?.deviceId ?? null;
855
+ }
856
+
857
+ // src/types/snapshot.ts
858
+ var SNAPSHOT_KIND_HEADER = "x-database-kind";
859
+ var SNAPSHOT_MIME_HEADER = "x-snapshot-mime-type";
860
+ var SNAPSHOT_NAME_HEADER = "x-database-name";
861
+
862
+ // src/client/snapshotUpload.ts
863
+ function createSnapshotUploadBody(bytes) {
864
+ return bytes;
865
+ }
866
+ function resolveSnapshotUploadUrl(uploadUrl, hubWsUrl) {
867
+ const hubHttpBase = wsUrlToHttpUrl(hubWsUrl);
868
+ const target = new URL(uploadUrl);
869
+ const hub = new URL(hubHttpBase.endsWith("/") ? hubHttpBase : `${hubHttpBase}/`);
870
+ target.protocol = hub.protocol;
871
+ target.hostname = hub.hostname;
872
+ target.port = hub.port;
873
+ return target.toString();
874
+ }
875
+ async function uploadSnapshot(uploadUrl, bytes, options) {
876
+ const headers = {
877
+ "Content-Type": "application/octet-stream",
878
+ [options.kindHeader]: options.kind,
879
+ [options.mimeHeader]: options.mimeType
880
+ };
881
+ if (options.databaseName && options.nameHeader) {
882
+ headers[options.nameHeader] = options.databaseName;
883
+ }
884
+ const response = await fetch(uploadUrl, {
885
+ method: "POST",
886
+ headers,
887
+ body: createSnapshotUploadBody(bytes)
888
+ });
889
+ if (!response.ok) {
890
+ throw new Error(`Upload failed with status ${response.status}`);
891
+ }
892
+ }
893
+
894
+ // src/client/handleDeviceSnapshot.ts
895
+ async function handleDeviceSnapshotUpload(database, message, options = {}) {
896
+ if (options.deviceId && message.deviceId !== options.deviceId) {
897
+ return;
898
+ }
899
+ if (!database) {
900
+ throw new Error("No database adapter configured");
901
+ }
902
+ if (!options.hubServerUrl) {
903
+ throw new Error("hubServerUrl is required to upload a device snapshot");
904
+ }
905
+ const snapshot = await database.exportSnapshot();
906
+ const hubHttp = wsUrlToHttpUrl(options.hubServerUrl);
907
+ const uploadUrl = buildDeviceSnapshotUrl(hubHttp, message.deviceId);
908
+ await uploadSnapshot(uploadUrl, snapshot.bytes, {
909
+ kind: snapshot.kind,
910
+ mimeType: snapshot.mimeType,
911
+ kindHeader: SNAPSHOT_KIND_HEADER,
912
+ mimeHeader: SNAPSHOT_MIME_HEADER,
913
+ databaseName: database.name,
914
+ nameHeader: SNAPSHOT_NAME_HEADER
915
+ });
916
+ }
917
+
918
+ exports.buildSnapshotLoadedToast = buildSnapshotLoadedToast;
919
+ exports.createDevToolsClient = createDevToolsClient;
920
+ exports.createSnapshotUploadBody = createSnapshotUploadBody;
921
+ exports.fetchDeviceExportDatabase = fetchDeviceExportDatabase;
922
+ exports.fetchDeviceExportMeta = fetchDeviceExportMeta;
923
+ exports.fetchDeviceExports = fetchDeviceExports;
924
+ exports.fetchProjectDatabase = fetchProjectDatabase;
925
+ exports.fetchProjectDatabaseMeta = fetchProjectDatabaseMeta;
926
+ exports.fetchProjectDatabases = fetchProjectDatabases;
927
+ exports.fetchSnapshot = fetchSnapshot;
928
+ exports.formatRefreshErrorMessage = formatRefreshErrorMessage;
929
+ exports.formatSnapshotReceivedMessage = formatSnapshotReceivedMessage;
930
+ exports.formatSyncErrorMessage = formatSyncErrorMessage;
931
+ exports.handleDeviceSnapshotUpload = handleDeviceSnapshotUpload;
932
+ exports.resolveDeviceDisplayName = resolveDeviceDisplayName;
933
+ exports.resolveDeviceExportDatabaseMetaUrl = resolveDeviceExportDatabaseMetaUrl;
934
+ exports.resolveDeviceExportDatabaseUrl = resolveDeviceExportDatabaseUrl;
935
+ exports.resolveDeviceExportsUrl = resolveDeviceExportsUrl;
936
+ exports.resolveDeviceLabel = resolveDeviceLabel;
937
+ exports.resolveLiveSwitchTarget = resolveLiveSwitchTarget;
938
+ exports.resolvePreferredDeviceId = resolvePreferredDeviceId;
939
+ exports.resolveProjectDatabaseMetaUrl = resolveProjectDatabaseMetaUrl;
940
+ exports.resolveProjectDatabaseUrl = resolveProjectDatabaseUrl;
941
+ exports.resolveProjectDatabasesUrl = resolveProjectDatabasesUrl;
942
+ exports.resolveSnapshotDownloadUrl = resolveSnapshotDownloadUrl;
943
+ exports.resolveSnapshotUploadUrl = resolveSnapshotUploadUrl;
944
+ exports.shortenDeviceId = shortenDeviceId;
945
+ exports.uploadSnapshot = uploadSnapshot;
946
+ //# sourceMappingURL=index.cjs.map
947
+ //# sourceMappingURL=index.cjs.map