@powersync/nuxt 0.0.0-dev-20260128023420

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 (64) hide show
  1. package/LICENSE +201 -0
  2. package/README +374 -0
  3. package/dist/module.d.mts +39 -0
  4. package/dist/module.json +9 -0
  5. package/dist/module.mjs +160 -0
  6. package/dist/runtime/assets/powersync-icon.svg +14 -0
  7. package/dist/runtime/components/BucketsInspectorTab.d.vue.ts +3 -0
  8. package/dist/runtime/components/BucketsInspectorTab.vue +646 -0
  9. package/dist/runtime/components/BucketsInspectorTab.vue.d.ts +3 -0
  10. package/dist/runtime/components/ConfigInspectorTab.d.vue.ts +3 -0
  11. package/dist/runtime/components/ConfigInspectorTab.vue +121 -0
  12. package/dist/runtime/components/ConfigInspectorTab.vue.d.ts +3 -0
  13. package/dist/runtime/components/DataInspectorTab.d.vue.ts +3 -0
  14. package/dist/runtime/components/DataInspectorTab.vue +678 -0
  15. package/dist/runtime/components/DataInspectorTab.vue.d.ts +3 -0
  16. package/dist/runtime/components/LoadingSpinner.d.vue.ts +3 -0
  17. package/dist/runtime/components/LoadingSpinner.vue +12 -0
  18. package/dist/runtime/components/LoadingSpinner.vue.d.ts +3 -0
  19. package/dist/runtime/components/LogsTab.d.vue.ts +3 -0
  20. package/dist/runtime/components/LogsTab.vue +325 -0
  21. package/dist/runtime/components/LogsTab.vue.d.ts +3 -0
  22. package/dist/runtime/components/PowerSyncInstanceTab.d.vue.ts +3 -0
  23. package/dist/runtime/components/PowerSyncInstanceTab.vue +9 -0
  24. package/dist/runtime/components/PowerSyncInstanceTab.vue.d.ts +3 -0
  25. package/dist/runtime/components/SyncStatusTab.d.vue.ts +3 -0
  26. package/dist/runtime/components/SyncStatusTab.vue +272 -0
  27. package/dist/runtime/components/SyncStatusTab.vue.d.ts +3 -0
  28. package/dist/runtime/composables/useDiagnosticsLogger.d.ts +27 -0
  29. package/dist/runtime/composables/useDiagnosticsLogger.js +41 -0
  30. package/dist/runtime/composables/usePowerSyncInspector.d.ts +42 -0
  31. package/dist/runtime/composables/usePowerSyncInspector.js +19 -0
  32. package/dist/runtime/composables/usePowerSyncInspectorDiagnostics.d.ts +153 -0
  33. package/dist/runtime/composables/usePowerSyncInspectorDiagnostics.js +254 -0
  34. package/dist/runtime/composables/usePowerSyncKysely.d.ts +23 -0
  35. package/dist/runtime/composables/usePowerSyncKysely.js +7 -0
  36. package/dist/runtime/index.d.ts +9 -0
  37. package/dist/runtime/layouts/powersync-inspector-layout.d.vue.ts +13 -0
  38. package/dist/runtime/layouts/powersync-inspector-layout.vue +90 -0
  39. package/dist/runtime/layouts/powersync-inspector-layout.vue.d.ts +13 -0
  40. package/dist/runtime/pages/__powersync-inspector.d.vue.ts +3 -0
  41. package/dist/runtime/pages/__powersync-inspector.vue +153 -0
  42. package/dist/runtime/pages/__powersync-inspector.vue.d.ts +3 -0
  43. package/dist/runtime/plugin.client.d.ts +2 -0
  44. package/dist/runtime/plugin.client.js +11 -0
  45. package/dist/runtime/utils/AppSchema.d.ts +27 -0
  46. package/dist/runtime/utils/AppSchema.js +23 -0
  47. package/dist/runtime/utils/DynamicSchemaManager.d.ts +15 -0
  48. package/dist/runtime/utils/DynamicSchemaManager.js +91 -0
  49. package/dist/runtime/utils/JsSchemaGenerator.d.ts +8 -0
  50. package/dist/runtime/utils/JsSchemaGenerator.js +28 -0
  51. package/dist/runtime/utils/NuxtPowerSyncDatabase.d.ts +40 -0
  52. package/dist/runtime/utils/NuxtPowerSyncDatabase.js +117 -0
  53. package/dist/runtime/utils/RecordingStorageAdapter.d.ts +13 -0
  54. package/dist/runtime/utils/RecordingStorageAdapter.js +76 -0
  55. package/dist/runtime/utils/RustClientInterceptor.d.ts +24 -0
  56. package/dist/runtime/utils/RustClientInterceptor.js +102 -0
  57. package/dist/runtime/utils/TokenConnector.d.ts +14 -0
  58. package/dist/runtime/utils/TokenConnector.js +62 -0
  59. package/dist/runtime/utils/addImportsFrom.d.ts +1 -0
  60. package/dist/runtime/utils/addImportsFrom.js +4 -0
  61. package/dist/runtime/utils/index.d.ts +1 -0
  62. package/dist/runtime/utils/index.js +1 -0
  63. package/dist/types.d.mts +9 -0
  64. package/package.json +90 -0
@@ -0,0 +1,678 @@
1
+ <template>
2
+ <div
3
+ border="t"
4
+ border-color="gray-200"
5
+ relative
6
+ n-bg="base"
7
+ flex="~ col"
8
+ h="screen"
9
+ overflow="hidden"
10
+ >
11
+ <SplitterGroup
12
+ id="splitter-group-1"
13
+ class="h-full"
14
+ direction="horizontal"
15
+ >
16
+ <SplitterPanel
17
+ id="splitter-group-1-panel-1"
18
+ :min-size="20"
19
+ :default-size="20"
20
+ class="border-x border-b border-gray-200 flex flex-col overflow-hidden"
21
+ >
22
+ <div class="flex-1 flex flex-col overflow-hidden">
23
+ <!-- Search Bar -->
24
+ <div class="p-2 border-b border-gray-200 dark:border-neutral-700">
25
+ <NTextInput
26
+ v-model="searchQuery"
27
+ n="xs"
28
+ class="w-full"
29
+ placeholder="Search tables and views..."
30
+ icon="carbon:search"
31
+ @input="onSearchInput"
32
+ />
33
+ </div>
34
+
35
+ <!-- Tree Container -->
36
+ <div class="flex-1 overflow-y-auto">
37
+ <TreeRoot
38
+ v-if="filteredTreeData && filteredTreeData.length > 0"
39
+ v-slot="{ flattenItems }"
40
+ v-model="selectedEntry"
41
+ v-model:expanded="expandedItems"
42
+ :items="filteredTreeData"
43
+ :get-key="(item2) => item2.name"
44
+ :get-children="(item2) => item2.children || void 0"
45
+ class="h-full"
46
+ >
47
+ <TreeItem
48
+ v-for="item in flattenItems"
49
+ :key="item._id"
50
+ v-bind="item.bind"
51
+ v-slot="{ isExpanded, isSelected }"
52
+ @select="
53
+ (event) => {
54
+ if (item.value.type !== 'folder') {
55
+ selectEntry(item.value);
56
+ }
57
+ }
58
+ "
59
+ >
60
+ <div
61
+ class="flex items-center gap-2 py-1 px-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-neutral-700 w-full"
62
+ :class="{
63
+ 'bg-gray-300 dark:bg-neutral-700 font-semibold': isSelected && item.value.type !== 'folder',
64
+ 'font-medium': item.value.type === 'folder'
65
+ }"
66
+ :style="{ paddingLeft: `${item.level * 16 + 8}px` }"
67
+ >
68
+ <!-- Folder/Item Icon -->
69
+ <NIcon
70
+ :icon="
71
+ item.value.type === 'folder' ? isExpanded ? 'carbon:folder-open' : 'carbon:folder' : item.value.icon
72
+ "
73
+ n="sm"
74
+ class="flex-shrink-0"
75
+ :class="
76
+ item.value.type === 'folder' ? 'text-indigo-500' : 'text-gray-500'
77
+ "
78
+ />
79
+
80
+ <!-- Name -->
81
+ <span class="truncate">{{ item.value.name }}</span>
82
+
83
+ <!-- Expand/Collapse indicator for folders -->
84
+ <NIcon
85
+ v-if="item.value.type === 'folder'"
86
+ :icon="
87
+ isExpanded ? 'carbon:chevron-down' : 'carbon:chevron-right'
88
+ "
89
+ class="flex-shrink-0 ml-auto text-gray-400"
90
+ />
91
+ </div>
92
+ </TreeItem>
93
+ </TreeRoot>
94
+
95
+ <!-- No Results Message -->
96
+ <div
97
+ v-else-if="searchQuery && filteredTreeData.length === 0"
98
+ class="flex-1 flex items-center justify-center text-gray-500 dark:text-gray-400 text-sm"
99
+ >
100
+ No tables or views found for "{{ searchQuery }}"
101
+ </div>
102
+ </div>
103
+ </div>
104
+ </SplitterPanel>
105
+ <SplitterResizeHandle
106
+ id="splitter-group-1-resize-handle-1"
107
+ class="w-[2px] bg-gray-200 hover:bg-indigo-300 dark:hover:bg-indigo-700"
108
+ />
109
+ <SplitterPanel
110
+ id="splitter-group-1-panel-2"
111
+ :min-size="20"
112
+ class="border-b border-x border-gray-200"
113
+ >
114
+ <div
115
+ v-if="!selectedEntry"
116
+ class="flex w-full h-full justify-center items-center align-middle"
117
+ >
118
+ <div
119
+ text="sm gray-500"
120
+ flex="~ gap-2 items-center"
121
+ >
122
+ <NIcon icon="carbon:document-blank" />
123
+ <span>No Selection</span>
124
+ </div>
125
+ </div>
126
+ <SplitterGroup
127
+ v-else
128
+ id="splitter-group-2"
129
+ direction="vertical"
130
+ >
131
+ <SplitterPanel
132
+ id="splitter-group-2-panel-1"
133
+ :min-size="10"
134
+ :default-size="10"
135
+ >
136
+ <!-- Custom Code Editor with Syntax Highlighting -->
137
+ <div class="relative h-full w-full">
138
+ <!-- Syntax Highlighted Background -->
139
+ <div
140
+ ref="highlightContainer"
141
+ class="absolute inset-0 pointer-events-none overflow-auto"
142
+ :style="{ scrollBehavior: 'auto' }"
143
+ >
144
+ <div
145
+ class="syntax-highlight-bg"
146
+ v-html="html"
147
+ />
148
+ </div>
149
+
150
+ <!-- Transparent Textarea Overlay -->
151
+ <textarea
152
+ ref="textareaRef"
153
+ v-model="query"
154
+ class="absolute inset-0 w-full h-full resize-none bg-transparent outline-none border-none overflow-auto editor-textarea dark:placeholder:text-gray-400"
155
+ style="
156
+ color: rgba(0, 0, 0, 0.01);
157
+ text-shadow: 0 0 0 transparent;
158
+ "
159
+ placeholder="Enter SQL query..."
160
+ spellcheck="false"
161
+ autocorrect="off"
162
+ autocomplete="off"
163
+ autocapitalize="off"
164
+ data-gramm="false"
165
+ @keydown.enter.meta.prevent="executeQuery"
166
+ @keydown.enter.ctrl.prevent="executeQuery"
167
+ @scroll="syncScroll"
168
+ @input="syncScroll"
169
+ />
170
+
171
+ <!-- Cursor/Selection Overlay -->
172
+ <div
173
+ ref="cursorOverlay"
174
+ class="absolute inset-0 pointer-events-none overflow-hidden"
175
+ >
176
+ <!-- This will show cursor and selection -->
177
+ </div>
178
+ </div>
179
+ </SplitterPanel>
180
+ <SplitterResizeHandle
181
+ id="splitter-group-2-resize-handle-1"
182
+ class="h-[2px] bg-gray-200 hover:bg-indigo-300 dark:hover:bg-indigo-700"
183
+ />
184
+ <SplitterPanel
185
+ id="splitter-group-2-panel-2"
186
+ :min-size="40"
187
+ class="border-t-2 border-gray-200 flex flex-col"
188
+ >
189
+ <!-- Query Results Table -->
190
+ <div
191
+ v-if="isLoading"
192
+ class="flex justify-center items-center h-full"
193
+ >
194
+ <NLoading />
195
+ </div>
196
+
197
+ <div
198
+ v-else-if="queryError"
199
+ class="flex-1 flex flex-col overflow-hidden"
200
+ >
201
+ <div
202
+ class="px-3 py-2 flex justify-between items-center bg-gray-50 dark:bg-neutral-800 border-b border-gray-200 dark:border-neutral-700 flex-shrink-0"
203
+ >
204
+ <NButton
205
+ n="xs"
206
+ icon="carbon:play"
207
+ @click="executeQuery"
208
+ >
209
+ Execute Query
210
+ </NButton>
211
+ </div>
212
+ <div
213
+ class="p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded"
214
+ >
215
+ <div class="text-red-800 dark:text-red-200 font-medium">
216
+ Query Error:
217
+ </div>
218
+ <div class="text-red-700 dark:text-red-300 text-sm mt-1">
219
+ {{ queryError }}
220
+ </div>
221
+ </div>
222
+ </div>
223
+
224
+ <div
225
+ v-else-if="currentTableRows && currentTableRows.length > 0"
226
+ class="flex-1 flex flex-col overflow-hidden"
227
+ >
228
+ <!-- Results summary -->
229
+ <div
230
+ class="px-3 py-2 flex justify-between items-center bg-gray-50 dark:bg-neutral-800 border-b border-gray-200 dark:border-neutral-700 flex-shrink-0"
231
+ >
232
+ <div class="flex items-center gap-3">
233
+ <NButton
234
+ n="xs green"
235
+ icon="carbon:play"
236
+ @click="executeQuery"
237
+ >
238
+ Execute Query
239
+ </NButton>
240
+
241
+ <div class="text-xs text-gray-600 dark:text-gray-400">
242
+ {{ currentTableRows.length }} row{{
243
+ currentTableRows.length !== 1 ? "s" : ""
244
+ }}
245
+ returned
246
+ </div>
247
+ </div>
248
+
249
+ <div class="flex items-center gap-1">
250
+ <NButton
251
+ n="xs"
252
+ icon="carbon:chevron-left"
253
+ :disabled="!table.getCanPreviousPage()"
254
+ @click="table.previousPage()"
255
+ />
256
+
257
+ <!-- Page Jump Input -->
258
+ <div class="flex items-center gap-1">
259
+ <span class="text-xs text-gray-600 dark:text-gray-400">Page</span>
260
+ <NTextInput
261
+ v-model="currentPageInput"
262
+ n="xs"
263
+ class="w-12 text-center"
264
+ type="number"
265
+ min="1"
266
+ :max="table.getPageCount()"
267
+ @blur="jumpToPage"
268
+ @keydown.enter="jumpToPage"
269
+ />
270
+ <span class="text-xs text-gray-600 dark:text-gray-400">of {{ table.getPageCount() }}</span>
271
+ </div>
272
+
273
+ <NButton
274
+ n="xs"
275
+ icon="carbon:chevron-right"
276
+ :disabled="!table.getCanNextPage()"
277
+ @click="table.nextPage()"
278
+ />
279
+ </div>
280
+
281
+ <!-- Page Size Control -->
282
+ <div class="flex items-center gap-1">
283
+ <span class="text-xs text-gray-600 dark:text-gray-400">Show:</span>
284
+ <NTextInput
285
+ v-model="pageSizeInput"
286
+ n="xs"
287
+ class="w-16"
288
+ type="number"
289
+ min="1"
290
+ max="1000"
291
+ @blur="updatePageSize"
292
+ @keydown.enter="updatePageSize"
293
+ />
294
+ <span class="text-xs text-gray-600 dark:text-gray-400">per page</span>
295
+ </div>
296
+ </div>
297
+
298
+ <!-- Data Table -->
299
+ <div
300
+ class="flex-1 border border-gray-200 dark:border-neutral-700 rounded-b-lg overflow-auto"
301
+ >
302
+ <table class="w-full min-w-max">
303
+ <thead class="bg-gray-50 dark:bg-neutral-950 sticky top-0">
304
+ <tr>
305
+ <th
306
+ v-for="header in table.getFlatHeaders()"
307
+ :key="header.id"
308
+ class="px-2 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 tracking-wider border-r border-gray-200 dark:border-neutral-700 last:border-r-0 relative overflow-hidden"
309
+ :class="
310
+ header.column.getCanSort() ? 'cursor-pointer hover:bg-gray-100 dark:hover:bg-neutral-700' : ''
311
+ "
312
+ :style="{
313
+ width: `${header.getSize()}px`,
314
+ maxWidth: `${header.getSize()}px`,
315
+ minWidth: `${header.getSize()}px`
316
+ }"
317
+ @click="
318
+ header.column.getToggleSortingHandler()?.($event)
319
+ "
320
+ >
321
+ <div class="flex items-center gap-1 min-w-0">
322
+ <div class="truncate flex-1 min-w-0">
323
+ <FlexRender
324
+ :render="header.column.columnDef.header"
325
+ :props="header.getContext()"
326
+ />
327
+ </div>
328
+ <span
329
+ v-if="header.column.getIsSorted()"
330
+ class="text-xs flex-shrink-0"
331
+ >
332
+ {{
333
+ header.column.getIsSorted() === "asc" ? "\u25B2" : "\u25BC"
334
+ }}
335
+ </span>
336
+ </div>
337
+ <!-- Column Resize Handle -->
338
+ <div
339
+ v-if="header.column.getCanResize()"
340
+ class="absolute top-0 right-0 h-full w-1 cursor-col-resize bg-transparent hover:bg-neutral-500 hover:bg-opacity-50 group"
341
+ @mousedown="header.getResizeHandler()?.($event)"
342
+ @touchstart="header.getResizeHandler()?.($event)"
343
+ @click.stop
344
+ >
345
+ <div
346
+ class="w-full h-full group-hover:bg-gray-500 transition-colors duration-150"
347
+ />
348
+ </div>
349
+ </th>
350
+ </tr>
351
+ </thead>
352
+ <tbody
353
+ class="bg-white dark:bg-neutral-900 border-t border-gray-200 dark:border-neutral-700"
354
+ >
355
+ <tr
356
+ v-for="row in table.getRowModel().rows"
357
+ :key="row.id"
358
+ class="hover:bg-gray-50 dark:hover:bg-neutral-800 border-b border-gray-200 dark:border-neutral-700"
359
+ >
360
+ <td
361
+ v-for="cell in row.getVisibleCells()"
362
+ :key="cell.id"
363
+ class="px-2 py-3 text-sm text-gray-900 dark:text-gray-100 border-r border-gray-200 dark:border-neutral-700 last:border-r-0 overflow-hidden"
364
+ :style="{
365
+ width: `${cell.column.getSize()}px`,
366
+ maxWidth: `${cell.column.getSize()}px`,
367
+ minWidth: `${cell.column.getSize()}px`
368
+ }"
369
+ >
370
+ <div
371
+ class="truncate w-full"
372
+ :title="String(cell.getValue())"
373
+ >
374
+ <FlexRender
375
+ :render="cell.column.columnDef.cell"
376
+ :props="cell.getContext()"
377
+ />
378
+ </div>
379
+ </td>
380
+ </tr>
381
+ </tbody>
382
+ </table>
383
+ </div>
384
+ </div>
385
+
386
+ <div
387
+ v-else-if="currentTableRows && currentTableRows.length === 0"
388
+ class="flex-1 flex flex-col overflow-hidden"
389
+ >
390
+ <div
391
+ class="px-3 py-2 flex justify-between items-center bg-gray-50 dark:bg-neutral-800 border-b border-gray-200 dark:border-neutral-700 flex-shrink-0"
392
+ >
393
+ <NButton
394
+ n="xs"
395
+ icon="carbon:play"
396
+ @click="executeQuery"
397
+ >
398
+ Execute Query
399
+ </NButton>
400
+ </div>
401
+ <div class="text-center py-8">
402
+ <div class="text-gray-500 dark:text-gray-400">
403
+ No results found
404
+ </div>
405
+ </div>
406
+ </div>
407
+
408
+ <div
409
+ v-else
410
+ class="text-center py-8"
411
+ >
412
+ <div class="text-gray-500 dark:text-gray-400">
413
+ Execute a query to see results
414
+ </div>
415
+ </div>
416
+ </SplitterPanel>
417
+ </SplitterGroup>
418
+ </SplitterPanel>
419
+ </SplitterGroup>
420
+ </div>
421
+ </template>
422
+
423
+ <script setup>
424
+ import { usePowerSyncInspectorDiagnostics } from "#imports";
425
+ import {
426
+ SplitterGroup,
427
+ SplitterPanel,
428
+ SplitterResizeHandle,
429
+ TreeItem,
430
+ TreeRoot
431
+ } from "reka-ui";
432
+ import { asyncComputed } from "@vueuse/core";
433
+ import {
434
+ FlexRender,
435
+ getCoreRowModel,
436
+ useVueTable,
437
+ getSortedRowModel,
438
+ getPaginationRowModel
439
+ } from "@tanstack/vue-table";
440
+ import { ref, computed, watch, onMounted } from "vue";
441
+ import { codeToHtml } from "shiki";
442
+ import Fuse from "fuse.js";
443
+ const ENTRIES_QUERY = `
444
+ SELECT name, type
445
+ FROM sqlite_schema
446
+ WHERE type IN ('table', 'view')
447
+ ORDER BY type, name;
448
+ `;
449
+ const { db } = usePowerSyncInspectorDiagnostics();
450
+ const entriesRows = ref(null);
451
+ const expandedItems = ref([]);
452
+ const _tableInfo = ref(null);
453
+ const treeData = computed(() => {
454
+ if (!entriesRows.value) return [];
455
+ const tables = entriesRows.value.filter((entry) => entry.type === "table");
456
+ const views = entriesRows.value.filter((entry) => entry.type === "view");
457
+ const treeItems = [];
458
+ if (tables.length > 0) {
459
+ treeItems.push({
460
+ name: "Tables",
461
+ type: "folder",
462
+ icon: "carbon:folder",
463
+ children: tables.map((table2) => ({
464
+ ...table2,
465
+ icon: "carbon:data-base"
466
+ }))
467
+ });
468
+ }
469
+ if (views.length > 0) {
470
+ treeItems.push({
471
+ name: "Views",
472
+ type: "folder",
473
+ icon: "carbon:view",
474
+ children: views.map((view) => ({
475
+ ...view,
476
+ icon: "carbon:data-view"
477
+ }))
478
+ });
479
+ }
480
+ return treeItems;
481
+ });
482
+ const initializeFuse = () => {
483
+ if (!entriesRows.value) return;
484
+ const fuseOptions = {
485
+ keys: ["name"],
486
+ threshold: 0.3,
487
+ // Adjust fuzzy search sensitivity (0 = exact, 1 = very fuzzy)
488
+ includeScore: true,
489
+ includeMatches: true
490
+ };
491
+ fuse.value = new Fuse(entriesRows.value, fuseOptions);
492
+ };
493
+ const filteredTreeData = computed(() => {
494
+ if (!searchQuery.value || !fuse.value) {
495
+ return treeData.value;
496
+ }
497
+ const searchResults = fuse.value.search(searchQuery.value);
498
+ const filteredEntries = searchResults.map((result) => result.item);
499
+ const tables = filteredEntries.filter((entry) => entry.type === "table");
500
+ const views = filteredEntries.filter((entry) => entry.type === "view");
501
+ const filteredTreeItems = [];
502
+ if (tables.length > 0) {
503
+ filteredTreeItems.push({
504
+ name: "Tables",
505
+ type: "folder",
506
+ icon: "carbon:folder",
507
+ children: tables.map((table2) => ({
508
+ ...table2,
509
+ icon: "carbon:data-base"
510
+ }))
511
+ });
512
+ }
513
+ if (views.length > 0) {
514
+ filteredTreeItems.push({
515
+ name: "Views",
516
+ type: "folder",
517
+ icon: "carbon:view",
518
+ children: views.map((view) => ({
519
+ ...view,
520
+ icon: "carbon:data-view"
521
+ }))
522
+ });
523
+ }
524
+ return filteredTreeItems;
525
+ });
526
+ onMounted(async () => {
527
+ entriesRows.value = await db.value.getAll(ENTRIES_QUERY);
528
+ initializeFuse();
529
+ });
530
+ watch(entriesRows, () => {
531
+ initializeFuse();
532
+ });
533
+ const onSearchInput = () => {
534
+ expandedItems.value = ["Tables", "Views"];
535
+ };
536
+ const selectedEntry = ref(
537
+ void 0
538
+ );
539
+ const query = ref("");
540
+ const isLoading = ref(false);
541
+ const queryError = ref(null);
542
+ const _hasLimitOrOffset = ref(false);
543
+ const currentTableRows = ref(null);
544
+ const currentPageInput = ref("1");
545
+ const pageSizeInput = ref("50");
546
+ const searchQuery = ref("");
547
+ const fuse = ref();
548
+ const textareaRef = ref();
549
+ const highlightContainer = ref();
550
+ const cursorOverlay = ref();
551
+ const columns = computed(() => {
552
+ if (!currentTableRows.value || currentTableRows.value.length === 0) return [];
553
+ const firstRow = currentTableRows.value[0];
554
+ const dataColumns = Object.keys(firstRow);
555
+ const columnsArray = [
556
+ // Row number column (no header name)
557
+ {
558
+ id: "rowNumber",
559
+ header: "",
560
+ cell: ({ row }) => row.index + 1,
561
+ size: 60,
562
+ enableSorting: false,
563
+ enableResizing: false
564
+ },
565
+ // Data columns
566
+ ...dataColumns.map((key) => ({
567
+ accessorKey: key,
568
+ header: key,
569
+ size: 150,
570
+ minSize: 20,
571
+ maxSize: 800,
572
+ enableResizing: true,
573
+ cell: ({ getValue }) => {
574
+ const value = getValue();
575
+ if (value === null) return "NULL";
576
+ if (value === void 0) return "UNDEFINED";
577
+ return String(value);
578
+ }
579
+ }))
580
+ ];
581
+ return columnsArray;
582
+ });
583
+ const table = useVueTable({
584
+ get data() {
585
+ return currentTableRows.value || [];
586
+ },
587
+ get columns() {
588
+ return columns.value;
589
+ },
590
+ getCoreRowModel: getCoreRowModel(),
591
+ getSortedRowModel: getSortedRowModel(),
592
+ getPaginationRowModel: getPaginationRowModel(),
593
+ enableColumnResizing: true,
594
+ columnResizeMode: "onChange",
595
+ defaultColumn: {
596
+ size: 150,
597
+ minSize: 20,
598
+ maxSize: 800
599
+ },
600
+ initialState: {
601
+ pagination: {
602
+ pageSize: 50
603
+ // Show 50 rows per page
604
+ }
605
+ }
606
+ });
607
+ const html = asyncComputed(
608
+ async () => await codeToHtml(query.value, {
609
+ lang: "sql",
610
+ themes: {
611
+ light: "catppuccin-latte",
612
+ dark: "catppuccin-frappe"
613
+ }
614
+ })
615
+ );
616
+ const syncScroll = () => {
617
+ if (textareaRef.value && highlightContainer.value) {
618
+ highlightContainer.value.scrollTop = textareaRef.value.scrollTop;
619
+ highlightContainer.value.scrollLeft = textareaRef.value.scrollLeft;
620
+ }
621
+ };
622
+ const jumpToPage = () => {
623
+ const pageNumber = Number.parseInt(currentPageInput.value, 10);
624
+ if (pageNumber >= 1 && pageNumber <= table.getPageCount()) {
625
+ table.setPageIndex(pageNumber - 1);
626
+ } else {
627
+ currentPageInput.value = String(table.getState().pagination.pageIndex + 1);
628
+ }
629
+ };
630
+ const updatePageSize = () => {
631
+ const pageSize = Number.parseInt(pageSizeInput.value, 10);
632
+ if (pageSize >= 1 && pageSize <= 1e3) {
633
+ table.setPageSize(pageSize);
634
+ } else {
635
+ pageSizeInput.value = String(table.getState().pagination.pageSize);
636
+ }
637
+ };
638
+ watch(
639
+ () => table.getState().pagination.pageIndex,
640
+ (newPageIndex) => {
641
+ currentPageInput.value = String(newPageIndex + 1);
642
+ }
643
+ );
644
+ watch(
645
+ () => table.getState().pagination.pageSize,
646
+ (newPageSize) => {
647
+ pageSizeInput.value = String(newPageSize);
648
+ }
649
+ );
650
+ const selectEntry = (entry) => {
651
+ selectedEntry.value = entry;
652
+ query.value = `SELECT * FROM ${selectedEntry.value.name};`;
653
+ executeQuery();
654
+ };
655
+ const executeQuery = async () => {
656
+ if (!query.value.trim() || !db.value) return;
657
+ isLoading.value = true;
658
+ queryError.value = null;
659
+ try {
660
+ const result = await db.value.getAll(query.value);
661
+ currentTableRows.value = result;
662
+ } catch (error) {
663
+ queryError.value = error instanceof Error ? error.message : "Unknown error occurred";
664
+ currentTableRows.value = null;
665
+ } finally {
666
+ isLoading.value = false;
667
+ }
668
+ };
669
+ watch(selectedEntry, () => {
670
+ if (selectedEntry.value) {
671
+ executeQuery();
672
+ }
673
+ });
674
+ </script>
675
+
676
+ <style>
677
+ .syntax-highlight-bg{font-family:Monaco,Menlo,Ubuntu Mono,monospace;font-size:14px;line-height:1.5;margin:0;padding:.25rem 0 0}.syntax-highlight-bg pre{background:transparent!important;height:100%;overflow:visible}.syntax-highlight-bg code,.syntax-highlight-bg pre{font-family:inherit;font-size:inherit;line-height:inherit;margin:0;padding:0}.syntax-highlight-bg code{counter-increment:step 0;counter-reset:step;display:block}.syntax-highlight-bg code .line:before{color:rgba(115,138,148,.4);content:counter(step);counter-increment:step;display:inline-block;margin-right:1.5rem;text-align:right;width:1rem}.dark .syntax-highlight-bg code .line:before{color:#d6cdcd}.editor-textarea{font-family:Monaco,Menlo,Ubuntu Mono,monospace!important;font-size:14px!important;line-height:1.5!important;margin:0!important;padding:.25rem 0 0 2.5rem!important;-moz-tab-size:2;-o-tab-size:2;tab-size:2;white-space:pre;word-wrap:normal;box-sizing:border-box;caret-color:#2563eb!important;overflow-wrap:normal;scrollbar-width:thin}.dark .editor-textarea{caret-color:#60a5fa!important}.editor-textarea:focus{caret-color:#2563eb!important}.dark .editor-textarea:focus{caret-color:#60a5fa!important}.editor-textarea::-moz-selection{background-color:rgba(59,130,246,.3)}.editor-textarea::selection{background-color:rgba(59,130,246,.3)}.dark .editor-textarea::-moz-selection{background-color:rgba(96,165,250,.3)}.dark .editor-textarea::selection{background-color:rgba(96,165,250,.3)}.editor-textarea::-moz-placeholder{color:rgba(115,138,148,.6);font-family:inherit;font-size:inherit;line-height:inherit}.editor-textarea::placeholder{color:rgba(115,138,148,.6);font-family:inherit;font-size:inherit;line-height:inherit}
678
+ </style>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;