@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.
- package/LICENSE +201 -0
- package/README +374 -0
- package/dist/module.d.mts +39 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +160 -0
- package/dist/runtime/assets/powersync-icon.svg +14 -0
- package/dist/runtime/components/BucketsInspectorTab.d.vue.ts +3 -0
- package/dist/runtime/components/BucketsInspectorTab.vue +646 -0
- package/dist/runtime/components/BucketsInspectorTab.vue.d.ts +3 -0
- package/dist/runtime/components/ConfigInspectorTab.d.vue.ts +3 -0
- package/dist/runtime/components/ConfigInspectorTab.vue +121 -0
- package/dist/runtime/components/ConfigInspectorTab.vue.d.ts +3 -0
- package/dist/runtime/components/DataInspectorTab.d.vue.ts +3 -0
- package/dist/runtime/components/DataInspectorTab.vue +678 -0
- package/dist/runtime/components/DataInspectorTab.vue.d.ts +3 -0
- package/dist/runtime/components/LoadingSpinner.d.vue.ts +3 -0
- package/dist/runtime/components/LoadingSpinner.vue +12 -0
- package/dist/runtime/components/LoadingSpinner.vue.d.ts +3 -0
- package/dist/runtime/components/LogsTab.d.vue.ts +3 -0
- package/dist/runtime/components/LogsTab.vue +325 -0
- package/dist/runtime/components/LogsTab.vue.d.ts +3 -0
- package/dist/runtime/components/PowerSyncInstanceTab.d.vue.ts +3 -0
- package/dist/runtime/components/PowerSyncInstanceTab.vue +9 -0
- package/dist/runtime/components/PowerSyncInstanceTab.vue.d.ts +3 -0
- package/dist/runtime/components/SyncStatusTab.d.vue.ts +3 -0
- package/dist/runtime/components/SyncStatusTab.vue +272 -0
- package/dist/runtime/components/SyncStatusTab.vue.d.ts +3 -0
- package/dist/runtime/composables/useDiagnosticsLogger.d.ts +27 -0
- package/dist/runtime/composables/useDiagnosticsLogger.js +41 -0
- package/dist/runtime/composables/usePowerSyncInspector.d.ts +42 -0
- package/dist/runtime/composables/usePowerSyncInspector.js +19 -0
- package/dist/runtime/composables/usePowerSyncInspectorDiagnostics.d.ts +153 -0
- package/dist/runtime/composables/usePowerSyncInspectorDiagnostics.js +254 -0
- package/dist/runtime/composables/usePowerSyncKysely.d.ts +23 -0
- package/dist/runtime/composables/usePowerSyncKysely.js +7 -0
- package/dist/runtime/index.d.ts +9 -0
- package/dist/runtime/layouts/powersync-inspector-layout.d.vue.ts +13 -0
- package/dist/runtime/layouts/powersync-inspector-layout.vue +90 -0
- package/dist/runtime/layouts/powersync-inspector-layout.vue.d.ts +13 -0
- package/dist/runtime/pages/__powersync-inspector.d.vue.ts +3 -0
- package/dist/runtime/pages/__powersync-inspector.vue +153 -0
- package/dist/runtime/pages/__powersync-inspector.vue.d.ts +3 -0
- package/dist/runtime/plugin.client.d.ts +2 -0
- package/dist/runtime/plugin.client.js +11 -0
- package/dist/runtime/utils/AppSchema.d.ts +27 -0
- package/dist/runtime/utils/AppSchema.js +23 -0
- package/dist/runtime/utils/DynamicSchemaManager.d.ts +15 -0
- package/dist/runtime/utils/DynamicSchemaManager.js +91 -0
- package/dist/runtime/utils/JsSchemaGenerator.d.ts +8 -0
- package/dist/runtime/utils/JsSchemaGenerator.js +28 -0
- package/dist/runtime/utils/NuxtPowerSyncDatabase.d.ts +40 -0
- package/dist/runtime/utils/NuxtPowerSyncDatabase.js +117 -0
- package/dist/runtime/utils/RecordingStorageAdapter.d.ts +13 -0
- package/dist/runtime/utils/RecordingStorageAdapter.js +76 -0
- package/dist/runtime/utils/RustClientInterceptor.d.ts +24 -0
- package/dist/runtime/utils/RustClientInterceptor.js +102 -0
- package/dist/runtime/utils/TokenConnector.d.ts +14 -0
- package/dist/runtime/utils/TokenConnector.js +62 -0
- package/dist/runtime/utils/addImportsFrom.d.ts +1 -0
- package/dist/runtime/utils/addImportsFrom.js +4 -0
- package/dist/runtime/utils/index.d.ts +1 -0
- package/dist/runtime/utils/index.js +1 -0
- package/dist/types.d.mts +9 -0
- 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;
|