@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,41 @@
|
|
|
1
|
+
import { createBaseLogger, LogLevel } from "@powersync/web";
|
|
2
|
+
import { createConsola } from "consola";
|
|
3
|
+
import { createStorage } from "unstorage";
|
|
4
|
+
import localStorageDriver from "unstorage/drivers/session-storage";
|
|
5
|
+
import mitt from "mitt";
|
|
6
|
+
const emitter = mitt();
|
|
7
|
+
const logsStorage = createStorage({
|
|
8
|
+
driver: localStorageDriver({ base: "powersync:" })
|
|
9
|
+
});
|
|
10
|
+
const consola = createConsola({
|
|
11
|
+
level: 5,
|
|
12
|
+
// trace
|
|
13
|
+
fancy: true,
|
|
14
|
+
formatOptions: {
|
|
15
|
+
columns: 80,
|
|
16
|
+
colors: true,
|
|
17
|
+
compact: false,
|
|
18
|
+
date: true
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
consola.addReporter({
|
|
22
|
+
log: async (logObject) => {
|
|
23
|
+
const key = `log:${logObject.date.toISOString()}`;
|
|
24
|
+
await logsStorage.set(key, logObject);
|
|
25
|
+
emitter.emit("log", { key, value: logObject });
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
export const useDiagnosticsLogger = (customHandler) => {
|
|
29
|
+
const logger = createBaseLogger();
|
|
30
|
+
logger.useDefaults();
|
|
31
|
+
logger.setLevel(LogLevel.DEBUG);
|
|
32
|
+
logger.setHandler(async (messages, context) => {
|
|
33
|
+
const level = context.level.name;
|
|
34
|
+
const messageArray = Array.from(messages);
|
|
35
|
+
const mainMessage = String(messageArray[0] || "Empty log message");
|
|
36
|
+
const extraData = messageArray.slice(1).map((item) => item.toString()).join(" ");
|
|
37
|
+
consola[level.toLowerCase()](`[PowerSync] ${context.name ? `[${context.name}]` : ""} ${mainMessage}`, extraData, context);
|
|
38
|
+
await customHandler?.(messages, context);
|
|
39
|
+
});
|
|
40
|
+
return { logger, logsStorage, emitter };
|
|
41
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { RecordingStorageAdapter } from '../utils/RecordingStorageAdapter.js';
|
|
2
|
+
import { DynamicSchemaManager } from '../utils/DynamicSchemaManager.js';
|
|
3
|
+
declare function getCurrentSchemaManager(): DynamicSchemaManager;
|
|
4
|
+
/**
|
|
5
|
+
* A composable for setting up PowerSync Inspector functionality.
|
|
6
|
+
*
|
|
7
|
+
* This composable provides utilities for schema management and diagnostics setup.
|
|
8
|
+
* It exposes the diagnostics schema and internal utilities needed for the inspector.
|
|
9
|
+
*
|
|
10
|
+
* @returns An object containing:
|
|
11
|
+
* - `diagnosticsSchema` - The schema for diagnostics data collection. Use this to extend your app schema with diagnostic tables.
|
|
12
|
+
* - `RecordingStorageAdapter` - Used internally. Storage adapter class that records operations for diagnostic purposes.
|
|
13
|
+
* - `getCurrentSchemaManager()` - Used internally. Gets the current schema manager instance for dynamic schema operations.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const { diagnosticsSchema } = usePowerSyncInspector()
|
|
18
|
+
*
|
|
19
|
+
* // Combine with your app schema
|
|
20
|
+
* const combinedSchema = new Schema([
|
|
21
|
+
* ...yourAppSchema.tables,
|
|
22
|
+
* ...diagnosticsSchema.tables,
|
|
23
|
+
* ])
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function usePowerSyncInspector(): {
|
|
27
|
+
diagnosticsSchema: import("@powersync/common").Schema<{
|
|
28
|
+
local_bucket_data: import("@powersync/common").Table<{
|
|
29
|
+
total_operations: import("@powersync/common").BaseColumnType<number | null>;
|
|
30
|
+
last_op: import("@powersync/common").BaseColumnType<string | null>;
|
|
31
|
+
download_size: import("@powersync/common").BaseColumnType<number | null>;
|
|
32
|
+
downloaded_operations: import("@powersync/common").BaseColumnType<number | null>;
|
|
33
|
+
downloading: import("@powersync/common").BaseColumnType<number | null>;
|
|
34
|
+
}>;
|
|
35
|
+
local_schema: import("@powersync/common").Table<{
|
|
36
|
+
data: import("@powersync/common").BaseColumnType<string | null>;
|
|
37
|
+
}>;
|
|
38
|
+
}>;
|
|
39
|
+
RecordingStorageAdapter: typeof RecordingStorageAdapter;
|
|
40
|
+
getCurrentSchemaManager: typeof getCurrentSchemaManager;
|
|
41
|
+
};
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { DiagnosticsAppSchema } from "../utils/AppSchema.js";
|
|
2
|
+
import { RecordingStorageAdapter } from "../utils/RecordingStorageAdapter.js";
|
|
3
|
+
import { DynamicSchemaManager } from "../utils/DynamicSchemaManager.js";
|
|
4
|
+
let currentSchemaManager = null;
|
|
5
|
+
function getCurrentSchemaManager() {
|
|
6
|
+
if (currentSchemaManager) {
|
|
7
|
+
return currentSchemaManager;
|
|
8
|
+
}
|
|
9
|
+
currentSchemaManager = new DynamicSchemaManager();
|
|
10
|
+
return currentSchemaManager;
|
|
11
|
+
}
|
|
12
|
+
export function usePowerSyncInspector() {
|
|
13
|
+
const diagnosticsSchema = DiagnosticsAppSchema;
|
|
14
|
+
return {
|
|
15
|
+
diagnosticsSchema,
|
|
16
|
+
RecordingStorageAdapter,
|
|
17
|
+
getCurrentSchemaManager
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { AbstractPowerSyncDatabase, PowerSyncBackendConnector, PowerSyncConnectionOptions, SyncStatus, UploadQueueStats } from '@powersync/web';
|
|
2
|
+
import type { ComputedRef, Ref } from 'vue';
|
|
3
|
+
export type BucketRow = {
|
|
4
|
+
name: string;
|
|
5
|
+
id: number;
|
|
6
|
+
tables: string[];
|
|
7
|
+
data_size: number;
|
|
8
|
+
metadata_size: number;
|
|
9
|
+
row_count: number;
|
|
10
|
+
download_size: number;
|
|
11
|
+
downloaded_operations: number;
|
|
12
|
+
total_operations: number;
|
|
13
|
+
downloading: number;
|
|
14
|
+
};
|
|
15
|
+
export type TableRow = {
|
|
16
|
+
name: string;
|
|
17
|
+
count: number;
|
|
18
|
+
size: number;
|
|
19
|
+
};
|
|
20
|
+
/** Aggregated statistics from the diagnostics composable. */
|
|
21
|
+
export type UsePowerSyncInspectorDiagnosticsTotals = {
|
|
22
|
+
buckets: number;
|
|
23
|
+
row_count: number;
|
|
24
|
+
downloaded_operations: number | undefined;
|
|
25
|
+
total_operations: number;
|
|
26
|
+
data_size: string;
|
|
27
|
+
metadata_size: string;
|
|
28
|
+
download_size: string;
|
|
29
|
+
};
|
|
30
|
+
type ReadonlyRef<T> = Readonly<Ref<T>>;
|
|
31
|
+
/**
|
|
32
|
+
* Return type of {@link usePowerSyncInspectorDiagnostics}.
|
|
33
|
+
* Uses named types so the signature stays readable in docs and IDE.
|
|
34
|
+
*/
|
|
35
|
+
export interface UsePowerSyncInspectorDiagnosticsReturn {
|
|
36
|
+
db: Ref<AbstractPowerSyncDatabase> | undefined;
|
|
37
|
+
connector: ComputedRef<PowerSyncBackendConnector | null>;
|
|
38
|
+
connectionOptions: ComputedRef<PowerSyncConnectionOptions | null>;
|
|
39
|
+
isDiagnosticSchemaSetup: ReadonlyRef<boolean>;
|
|
40
|
+
/** Current sync status. Typed as SyncStatus for a concise doc signature; at runtime it may be a readonly proxy. */
|
|
41
|
+
syncStatus: ReadonlyRef<SyncStatus>;
|
|
42
|
+
hasSynced: ReadonlyRef<boolean>;
|
|
43
|
+
isConnected: ReadonlyRef<boolean>;
|
|
44
|
+
isSyncing: ReadonlyRef<boolean>;
|
|
45
|
+
isDownloading: ReadonlyRef<boolean>;
|
|
46
|
+
isUploading: ReadonlyRef<boolean>;
|
|
47
|
+
downloadError: ReadonlyRef<unknown>;
|
|
48
|
+
uploadError: ReadonlyRef<unknown>;
|
|
49
|
+
downloadProgressDetails: ReadonlyRef<unknown>;
|
|
50
|
+
lastSyncedAt: ReadonlyRef<Date | null>;
|
|
51
|
+
totalDownloadProgress: ReadonlyRef<string>;
|
|
52
|
+
uploadQueueStats: ReadonlyRef<UploadQueueStats | null>;
|
|
53
|
+
uploadQueueCount: ReadonlyRef<number>;
|
|
54
|
+
uploadQueueSize: ReadonlyRef<string>;
|
|
55
|
+
userID: ReadonlyRef<string | null>;
|
|
56
|
+
bucketRows: ReadonlyRef<BucketRow[] | null>;
|
|
57
|
+
tableRows: ReadonlyRef<TableRow[] | null>;
|
|
58
|
+
totals: ReadonlyRef<UsePowerSyncInspectorDiagnosticsTotals>;
|
|
59
|
+
clearData: () => Promise<void>;
|
|
60
|
+
formatBytes: (bytes: number, decimals?: number) => string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A comprehensive composable that provides real-time diagnostics data and sync status monitoring
|
|
64
|
+
* for your PowerSync client and local database.
|
|
65
|
+
*
|
|
66
|
+
* This composable can be used to create your own inspector or to monitor sync status in your application.
|
|
67
|
+
* It provides reactive properties that update automatically as the sync state changes.
|
|
68
|
+
*
|
|
69
|
+
* @returns An object containing:
|
|
70
|
+
*
|
|
71
|
+
* **Database & Connection:**
|
|
72
|
+
* - `db` - The PowerSync database instance
|
|
73
|
+
* - `connector` - The current PowerSync backend connector (computed)
|
|
74
|
+
* - `connectionOptions` - The current connection options (computed)
|
|
75
|
+
* - `isDiagnosticSchemaSetup` - Whether the diagnostic schema is properly set up
|
|
76
|
+
*
|
|
77
|
+
* **Sync Status:**
|
|
78
|
+
* - `syncStatus` - The current sync status object
|
|
79
|
+
* - `hasSynced` - Whether the database has completed at least one sync
|
|
80
|
+
* - `isConnected` - Whether the database is currently connected
|
|
81
|
+
* - `isSyncing` - Whether a sync operation is in progress
|
|
82
|
+
* - `isDownloading` - Whether data is currently being downloaded
|
|
83
|
+
* - `isUploading` - Whether data is currently being uploaded
|
|
84
|
+
* - `lastSyncedAt` - The timestamp of the last successful sync
|
|
85
|
+
*
|
|
86
|
+
* **Progress & Statistics:**
|
|
87
|
+
* - `totalDownloadProgress` - Total download progress as a percentage string (computed)
|
|
88
|
+
* - `uploadQueueStats` - Statistics about the upload queue
|
|
89
|
+
* - `uploadQueueCount` - Number of items in the upload queue (computed)
|
|
90
|
+
* - `uploadQueueSize` - Size of the upload queue in human-readable format (computed)
|
|
91
|
+
* - `bucketRows` - Array of bucket data rows
|
|
92
|
+
* - `tableRows` - Array of table statistics rows
|
|
93
|
+
* - `totals` - Aggregated statistics including bucket count, row count, and data sizes (computed)
|
|
94
|
+
*
|
|
95
|
+
* **Error Handling:**
|
|
96
|
+
* - `downloadError` - Any error that occurred during download
|
|
97
|
+
* - `uploadError` - Any error that occurred during upload
|
|
98
|
+
* - `downloadProgressDetails` - Detailed download progress information
|
|
99
|
+
*
|
|
100
|
+
* **User Info:**
|
|
101
|
+
* - `userID` - The current user ID extracted from the authentication token (computed)
|
|
102
|
+
*
|
|
103
|
+
* **Utilities:**
|
|
104
|
+
* - `clearData()` - Disconnects and clears all local PowerSync data, then reconnects. Useful for resetting the sync state during development or troubleshooting.
|
|
105
|
+
* - `formatBytes(bytes, decimals?)` - Formats byte counts into readable file sizes (e.g., "1.5 MiB"). Default decimals is 2.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```vue
|
|
109
|
+
* <script setup lang="ts">
|
|
110
|
+
* const {
|
|
111
|
+
* isConnected,
|
|
112
|
+
* hasSynced,
|
|
113
|
+
* isSyncing,
|
|
114
|
+
* totalDownloadProgress,
|
|
115
|
+
* uploadQueueStats,
|
|
116
|
+
* bucketRows,
|
|
117
|
+
* totals,
|
|
118
|
+
* clearData,
|
|
119
|
+
* } = usePowerSyncInspectorDiagnostics()
|
|
120
|
+
* </script>
|
|
121
|
+
*
|
|
122
|
+
* <template>
|
|
123
|
+
* <div>
|
|
124
|
+
* <!-- Connection Status -->
|
|
125
|
+
* <div v-if="isConnected" class="status-connected">
|
|
126
|
+
* Connected {{ hasSynced ? '✅' : '⏳' }}
|
|
127
|
+
* </div>
|
|
128
|
+
*
|
|
129
|
+
* <!-- Sync Progress -->
|
|
130
|
+
* <div v-if="isSyncing">
|
|
131
|
+
* Syncing... {{ totalDownloadProgress }}%
|
|
132
|
+
* </div>
|
|
133
|
+
*
|
|
134
|
+
* <!-- Statistics -->
|
|
135
|
+
* <div v-if="totals">
|
|
136
|
+
* <p>Buckets: {{ totals.buckets }}</p>
|
|
137
|
+
* <p>Total Rows: {{ totals.row_count }}</p>
|
|
138
|
+
* <p>Data Size: {{ totals.data_size }}</p>
|
|
139
|
+
* </div>
|
|
140
|
+
*
|
|
141
|
+
* <!-- Upload Queue -->
|
|
142
|
+
* <div v-if="uploadQueueStats">
|
|
143
|
+
* Pending uploads: {{ uploadQueueStats.count }}
|
|
144
|
+
* </div>
|
|
145
|
+
*
|
|
146
|
+
* <!-- Debug Actions -->
|
|
147
|
+
* <button @click="clearData">Clear Data</button>
|
|
148
|
+
* </div>
|
|
149
|
+
* </template>
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
export declare function usePowerSyncInspectorDiagnostics(): UsePowerSyncInspectorDiagnosticsReturn;
|
|
153
|
+
export {};
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { usePowerSync, useStatus } from "@powersync/vue";
|
|
2
|
+
import { ref, computed, readonly, onMounted, onUnmounted } from "vue";
|
|
3
|
+
import { computedAsync } from "@vueuse/core";
|
|
4
|
+
import { usePowerSyncInspector } from "./usePowerSyncInspector.js";
|
|
5
|
+
const BUCKETS_QUERY = `
|
|
6
|
+
WITH
|
|
7
|
+
oplog_by_table AS
|
|
8
|
+
(SELECT
|
|
9
|
+
bucket,
|
|
10
|
+
row_type,
|
|
11
|
+
sum(length(ifnull(data, ''))) as data_size,
|
|
12
|
+
sum(length(row_type) + length(row_id) + length(key) + 44) as metadata_size,
|
|
13
|
+
count() as row_count
|
|
14
|
+
FROM ps_oplog
|
|
15
|
+
GROUP BY bucket, row_type),
|
|
16
|
+
|
|
17
|
+
oplog_stats AS
|
|
18
|
+
(SELECT
|
|
19
|
+
bucket as bucket_id,
|
|
20
|
+
sum(data_size) as data_size,
|
|
21
|
+
sum(metadata_size) as metadata_size,
|
|
22
|
+
sum(row_count) as row_count,
|
|
23
|
+
json_group_array(row_type) tables
|
|
24
|
+
FROM oplog_by_table
|
|
25
|
+
GROUP BY bucket)
|
|
26
|
+
|
|
27
|
+
SELECT
|
|
28
|
+
local.id as name,
|
|
29
|
+
ps_buckets.id as id,
|
|
30
|
+
stats.tables,
|
|
31
|
+
stats.data_size,
|
|
32
|
+
stats.metadata_size,
|
|
33
|
+
IFNULL(stats.row_count, 0) as row_count,
|
|
34
|
+
local.download_size,
|
|
35
|
+
local.downloaded_operations,
|
|
36
|
+
local.total_operations,
|
|
37
|
+
local.downloading
|
|
38
|
+
FROM local_bucket_data local
|
|
39
|
+
LEFT JOIN ps_buckets ON ps_buckets.name = local.id
|
|
40
|
+
LEFT JOIN oplog_stats stats ON stats.bucket_id = ps_buckets.id`;
|
|
41
|
+
const BUCKETS_QUERY_FAST = `
|
|
42
|
+
SELECT
|
|
43
|
+
local.id as name,
|
|
44
|
+
ps_buckets.id as id,
|
|
45
|
+
'[]' as tables,
|
|
46
|
+
0 as data_size,
|
|
47
|
+
0 as metadata_size,
|
|
48
|
+
0 as row_count,
|
|
49
|
+
local.download_size,
|
|
50
|
+
local.downloaded_operations,
|
|
51
|
+
local.total_operations,
|
|
52
|
+
local.downloading
|
|
53
|
+
FROM local_bucket_data local
|
|
54
|
+
LEFT JOIN ps_buckets ON ps_buckets.name = local.id`;
|
|
55
|
+
const TABLES_QUERY = `
|
|
56
|
+
SELECT row_type as name, count() as count, sum(length(data)) as size FROM ps_oplog GROUP BY row_type
|
|
57
|
+
`;
|
|
58
|
+
function formatBytes(bytes, decimals = 2) {
|
|
59
|
+
if (!+bytes) return "0 Bytes";
|
|
60
|
+
const k = 1024;
|
|
61
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
62
|
+
const sizes = [
|
|
63
|
+
"Bytes",
|
|
64
|
+
"KiB",
|
|
65
|
+
"MiB",
|
|
66
|
+
"GiB",
|
|
67
|
+
"TiB",
|
|
68
|
+
"PiB",
|
|
69
|
+
"EiB",
|
|
70
|
+
"ZiB",
|
|
71
|
+
"YiB"
|
|
72
|
+
];
|
|
73
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
74
|
+
return `${Number.parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
|
|
75
|
+
}
|
|
76
|
+
export function usePowerSyncInspectorDiagnostics() {
|
|
77
|
+
const db = usePowerSync();
|
|
78
|
+
const syncStatus = useStatus();
|
|
79
|
+
const { getCurrentSchemaManager } = usePowerSyncInspector();
|
|
80
|
+
const isDiagnosticSchemaSetup = ref(true);
|
|
81
|
+
const connectionOptions = computed(() => db.value.connectionOptions || null);
|
|
82
|
+
const connector = computed(() => db.value.connector || null);
|
|
83
|
+
const hasSynced = ref(syncStatus.value?.hasSynced || false);
|
|
84
|
+
const isConnected = ref(syncStatus.value?.connected || false);
|
|
85
|
+
const isSyncing = ref(false);
|
|
86
|
+
const isDownloading = ref(
|
|
87
|
+
syncStatus.value?.dataFlowStatus.downloading || false
|
|
88
|
+
);
|
|
89
|
+
const isUploading = ref(syncStatus.value?.dataFlowStatus.uploading || false);
|
|
90
|
+
const lastSyncedAt = ref(syncStatus.value?.lastSyncedAt || null);
|
|
91
|
+
const uploadError = ref(syncStatus.value?.dataFlowStatus.uploadError || null);
|
|
92
|
+
const downloadError = ref(
|
|
93
|
+
syncStatus.value?.dataFlowStatus.downloadError || null
|
|
94
|
+
);
|
|
95
|
+
const downloadProgressDetails = ref(
|
|
96
|
+
syncStatus.value?.dataFlowStatus.downloadProgress || null
|
|
97
|
+
);
|
|
98
|
+
const bucketRows = ref(null);
|
|
99
|
+
const tableRows = ref(null);
|
|
100
|
+
const uploadQueueStats = ref(null);
|
|
101
|
+
const totals = computed(() => ({
|
|
102
|
+
buckets: bucketRows.value?.length ?? 0,
|
|
103
|
+
row_count: bucketRows.value?.reduce((total, row) => total + row.row_count, 0) ?? 0,
|
|
104
|
+
downloaded_operations: bucketRows.value?.reduce(
|
|
105
|
+
(total, row) => total + row.downloaded_operations,
|
|
106
|
+
0
|
|
107
|
+
),
|
|
108
|
+
total_operations: bucketRows.value?.reduce(
|
|
109
|
+
(total, row) => total + row.total_operations,
|
|
110
|
+
0
|
|
111
|
+
) ?? 0,
|
|
112
|
+
data_size: formatBytes(
|
|
113
|
+
bucketRows.value?.reduce((total, row) => total + row.data_size, 0) ?? 0
|
|
114
|
+
),
|
|
115
|
+
metadata_size: formatBytes(
|
|
116
|
+
bucketRows.value?.reduce((total, row) => total + row.metadata_size, 0) ?? 0
|
|
117
|
+
),
|
|
118
|
+
download_size: formatBytes(
|
|
119
|
+
bucketRows.value?.reduce((total, row) => total + row.download_size, 0) ?? 0
|
|
120
|
+
)
|
|
121
|
+
}));
|
|
122
|
+
const userID = computedAsync(async () => {
|
|
123
|
+
try {
|
|
124
|
+
const token = (await connector.value.fetchCredentials())?.token;
|
|
125
|
+
if (!token) return null;
|
|
126
|
+
const [_head, body, _signature] = token.split(".");
|
|
127
|
+
const payload = JSON.parse(atob(body ?? ""));
|
|
128
|
+
return payload.sub;
|
|
129
|
+
} catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
const totalDownloadProgress = computed(() => {
|
|
134
|
+
if (!hasSynced.value || isSyncing.value) {
|
|
135
|
+
return ((syncStatus.value?.downloadProgress?.downloadedFraction ?? 0) * 100).toFixed(1);
|
|
136
|
+
}
|
|
137
|
+
return "100";
|
|
138
|
+
});
|
|
139
|
+
const uploadQueueSize = computed(
|
|
140
|
+
() => formatBytes(uploadQueueStats.value?.size ?? 0)
|
|
141
|
+
);
|
|
142
|
+
const uploadQueueCount = computed(() => uploadQueueStats.value?.count ?? 0);
|
|
143
|
+
const clearData = async () => {
|
|
144
|
+
await db.value?.syncStreamImplementation?.disconnect();
|
|
145
|
+
const connector2 = db.value.connector;
|
|
146
|
+
const connectionOptions2 = db.value.connectionOptions;
|
|
147
|
+
await db.value?.disconnectAndClear();
|
|
148
|
+
const schemaManager = getCurrentSchemaManager();
|
|
149
|
+
await schemaManager.clear();
|
|
150
|
+
await schemaManager.refreshSchema(db.value.database);
|
|
151
|
+
await db.value.connect(
|
|
152
|
+
connector2,
|
|
153
|
+
connectionOptions2
|
|
154
|
+
);
|
|
155
|
+
};
|
|
156
|
+
async function refreshState() {
|
|
157
|
+
if (db.value) {
|
|
158
|
+
const { synced_at } = await db.value.get(
|
|
159
|
+
"SELECT powersync_last_synced_at() as synced_at"
|
|
160
|
+
);
|
|
161
|
+
uploadQueueStats.value = await db.value?.getUploadQueueStats(true);
|
|
162
|
+
if (synced_at != null && !syncStatus.value?.dataFlowStatus.downloading) {
|
|
163
|
+
bucketRows.value = await db.value.getAll(BUCKETS_QUERY);
|
|
164
|
+
tableRows.value = await db.value.getAll(TABLES_QUERY);
|
|
165
|
+
} else if (synced_at != null) {
|
|
166
|
+
bucketRows.value = await db.value.getAll(BUCKETS_QUERY_FAST);
|
|
167
|
+
if (tableRows.value == null) {
|
|
168
|
+
tableRows.value = await db.value.getAll(TABLES_QUERY);
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
bucketRows.value = await db.value.getAll(BUCKETS_QUERY_FAST);
|
|
172
|
+
tableRows.value = null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
onMounted(async () => {
|
|
177
|
+
if (!db.value) return;
|
|
178
|
+
const unregisterListener = db.value.registerListener({
|
|
179
|
+
statusChanged: (newStatus) => {
|
|
180
|
+
hasSynced.value = !!newStatus.hasSynced;
|
|
181
|
+
isConnected.value = !!newStatus.connected;
|
|
182
|
+
isDownloading.value = !!newStatus.dataFlowStatus.downloading;
|
|
183
|
+
isUploading.value = !!newStatus.dataFlowStatus.uploading;
|
|
184
|
+
lastSyncedAt.value = newStatus.lastSyncedAt || null;
|
|
185
|
+
uploadError.value = newStatus.dataFlowStatus.uploadError || null;
|
|
186
|
+
downloadError.value = newStatus.dataFlowStatus.downloadError || null;
|
|
187
|
+
downloadProgressDetails.value = newStatus.dataFlowStatus.downloadProgress || null;
|
|
188
|
+
if (newStatus?.hasSynced === void 0 || newStatus?.priorityStatusEntries?.length && newStatus.priorityStatusEntries.length > 0) {
|
|
189
|
+
hasSynced.value = newStatus?.priorityStatusEntries.every(
|
|
190
|
+
(entry) => entry.hasSynced
|
|
191
|
+
) ?? false;
|
|
192
|
+
}
|
|
193
|
+
if (newStatus?.dataFlowStatus.downloading || newStatus?.dataFlowStatus.uploading) {
|
|
194
|
+
isSyncing.value = true;
|
|
195
|
+
} else {
|
|
196
|
+
isSyncing.value = false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
db.value.onChangeWithCallback(
|
|
201
|
+
{
|
|
202
|
+
async onChange(_event) {
|
|
203
|
+
await refreshState();
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
rawTableNames: true,
|
|
208
|
+
tables: [
|
|
209
|
+
"ps_oplog",
|
|
210
|
+
"ps_buckets",
|
|
211
|
+
"ps_data_local__local_bucket_data",
|
|
212
|
+
"ps_crud"
|
|
213
|
+
],
|
|
214
|
+
throttleMs: 500
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
try {
|
|
218
|
+
await refreshState();
|
|
219
|
+
} catch (error) {
|
|
220
|
+
if (error.message === "no such table: local_bucket_data") {
|
|
221
|
+
isDiagnosticSchemaSetup.value = false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
onUnmounted(() => {
|
|
225
|
+
unregisterListener();
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
return {
|
|
229
|
+
db,
|
|
230
|
+
connector,
|
|
231
|
+
connectionOptions,
|
|
232
|
+
isDiagnosticSchemaSetup: readonly(isDiagnosticSchemaSetup),
|
|
233
|
+
syncStatus: readonly(syncStatus),
|
|
234
|
+
hasSynced: readonly(hasSynced),
|
|
235
|
+
isConnected: readonly(isConnected),
|
|
236
|
+
isSyncing: readonly(isSyncing),
|
|
237
|
+
isDownloading: readonly(isDownloading),
|
|
238
|
+
isUploading: readonly(isUploading),
|
|
239
|
+
downloadError: readonly(downloadError),
|
|
240
|
+
uploadError: readonly(uploadError),
|
|
241
|
+
downloadProgressDetails: readonly(downloadProgressDetails),
|
|
242
|
+
lastSyncedAt: readonly(lastSyncedAt),
|
|
243
|
+
totalDownloadProgress: readonly(totalDownloadProgress),
|
|
244
|
+
uploadQueueStats: readonly(uploadQueueStats),
|
|
245
|
+
uploadQueueCount: readonly(uploadQueueCount),
|
|
246
|
+
uploadQueueSize: readonly(uploadQueueSize),
|
|
247
|
+
userID: readonly(userID),
|
|
248
|
+
bucketRows: readonly(bucketRows),
|
|
249
|
+
tableRows: readonly(tableRows),
|
|
250
|
+
totals: readonly(totals),
|
|
251
|
+
clearData,
|
|
252
|
+
formatBytes
|
|
253
|
+
};
|
|
254
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provides a Kysely-wrapped PowerSync database for type-safe database queries.
|
|
3
|
+
*
|
|
4
|
+
* This composable wraps the PowerSync database instance with Kysely's type-safe query builder,
|
|
5
|
+
* allowing you to write type-safe SQL queries with full TypeScript support.
|
|
6
|
+
*
|
|
7
|
+
* @typeParam T - Your database type (from your schema)
|
|
8
|
+
*
|
|
9
|
+
* @returns Kysely database instance (not `{ db }`)
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { usePowerSyncKysely } from '@powersync/nuxt'
|
|
14
|
+
* import { type Database } from '../powersync/AppSchema'
|
|
15
|
+
*
|
|
16
|
+
* // Returns db directly, not { db }
|
|
17
|
+
* const db = usePowerSyncKysely<Database>()
|
|
18
|
+
*
|
|
19
|
+
* // Use Kysely query builder
|
|
20
|
+
* const users = await db.selectFrom('users').selectAll().execute()
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare const usePowerSyncKysely: <T>() => import("@powersync/kysely-driver").PowerSyncKyselyDatabase<T>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { wrapPowerSyncWithKysely } from "@powersync/kysely-driver";
|
|
2
|
+
import { usePowerSync } from "@powersync/vue";
|
|
3
|
+
export const usePowerSyncKysely = () => {
|
|
4
|
+
const powerSync = usePowerSync();
|
|
5
|
+
const db = wrapPowerSyncWithKysely(powerSync.value);
|
|
6
|
+
return db;
|
|
7
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PowerSyncNuxtModuleOptions } from '../module'
|
|
2
|
+
|
|
3
|
+
declare module 'nuxt/schema' {
|
|
4
|
+
interface PublicRuntimeConfig {
|
|
5
|
+
powerSyncModuleOptions: PowerSyncNuxtModuleOptions
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
// It is always important to ensure you import/export something when augmenting a type
|
|
9
|
+
export {}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare var __VLS_20: {};
|
|
2
|
+
type __VLS_Slots = {} & {
|
|
3
|
+
default?: (props: typeof __VLS_20) => any;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
6
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
9
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
10
|
+
new (): {
|
|
11
|
+
$slots: S;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
|
|
4
|
+
<LoadingSpinner v-if="isCssLoading" />
|
|
5
|
+
|
|
6
|
+
<div
|
|
7
|
+
v-show="!isCssLoading"
|
|
8
|
+
flex="~ relative col"
|
|
9
|
+
p="6"
|
|
10
|
+
h="screen"
|
|
11
|
+
n-bg="base"
|
|
12
|
+
class="ps-inspector-ui"
|
|
13
|
+
>
|
|
14
|
+
<!-- Header with title and dark toggle -->
|
|
15
|
+
<div
|
|
16
|
+
flex="~ items-center justify-between"
|
|
17
|
+
mb="3"
|
|
18
|
+
>
|
|
19
|
+
<h1
|
|
20
|
+
flex="~ gap-2 items-center"
|
|
21
|
+
text="3xl"
|
|
22
|
+
font="bold"
|
|
23
|
+
>
|
|
24
|
+
<img
|
|
25
|
+
:src="iconUrl"
|
|
26
|
+
alt="Powersync"
|
|
27
|
+
w="10"
|
|
28
|
+
h="10"
|
|
29
|
+
>
|
|
30
|
+
PowerSync Inspector
|
|
31
|
+
</h1>
|
|
32
|
+
|
|
33
|
+
<!-- Dark Mode Toggle -->
|
|
34
|
+
<NDarkToggle v-slot="{ isDark, toggle }">
|
|
35
|
+
<NButton
|
|
36
|
+
n="sm"
|
|
37
|
+
:icon="isDark.value ? 'carbon:moon' : 'carbon:sun'"
|
|
38
|
+
@click="toggle"
|
|
39
|
+
>
|
|
40
|
+
{{ isDark.value ? "Dark" : "Light" }}
|
|
41
|
+
</NButton>
|
|
42
|
+
</NDarkToggle>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<slot v-if="useDiagnostics" />
|
|
46
|
+
<div v-else>
|
|
47
|
+
<NTip
|
|
48
|
+
n="red6 dark:red5"
|
|
49
|
+
icon="carbon:warning-alt"
|
|
50
|
+
>
|
|
51
|
+
Enable diagnostics in your Nuxt config to use the inspector.
|
|
52
|
+
</NTip>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</template>
|
|
57
|
+
|
|
58
|
+
<script setup>
|
|
59
|
+
import { useRuntimeConfig } from "#imports";
|
|
60
|
+
import { onMounted, ref } from "vue";
|
|
61
|
+
import iconUrl from "./assets/powersync-icon.svg?url";
|
|
62
|
+
const useDiagnostics = useRuntimeConfig().public.powerSyncModuleOptions.useDiagnostics ?? false;
|
|
63
|
+
const isCssLoading = ref(true);
|
|
64
|
+
onMounted(async () => {
|
|
65
|
+
await waitForUnoCSS();
|
|
66
|
+
isCssLoading.value = false;
|
|
67
|
+
});
|
|
68
|
+
const waitForUnoCSS = () => {
|
|
69
|
+
return new Promise((resolve) => {
|
|
70
|
+
const testEl = document.createElement("div");
|
|
71
|
+
testEl.className = "w-10 h-10";
|
|
72
|
+
testEl.style.position = "absolute";
|
|
73
|
+
testEl.style.visibility = "hidden";
|
|
74
|
+
testEl.style.pointerEvents = "none";
|
|
75
|
+
document.body.appendChild(testEl);
|
|
76
|
+
const checkStyles = () => {
|
|
77
|
+
const computed = window.getComputedStyle(testEl);
|
|
78
|
+
const width = computed.getPropertyValue("width");
|
|
79
|
+
const height = computed.getPropertyValue("height");
|
|
80
|
+
if (width === "40px" && height === "40px") {
|
|
81
|
+
document.body.removeChild(testEl);
|
|
82
|
+
resolve(void 0);
|
|
83
|
+
} else {
|
|
84
|
+
requestAnimationFrame(checkStyles);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
requestAnimationFrame(checkStyles);
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
</script>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare var __VLS_20: {};
|
|
2
|
+
type __VLS_Slots = {} & {
|
|
3
|
+
default?: (props: typeof __VLS_20) => any;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
6
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
9
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
10
|
+
new (): {
|
|
11
|
+
$slots: S;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -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;
|