@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,153 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
flex="~ justify-between"
|
|
4
|
+
border="b"
|
|
5
|
+
border-color="gray-100"
|
|
6
|
+
py="3"
|
|
7
|
+
mb="3"
|
|
8
|
+
>
|
|
9
|
+
<div flex="~ gap-2">
|
|
10
|
+
<NTip
|
|
11
|
+
:n="`${isConnected ? 'green' : isSyncing ? 'blue' : 'red'} xs`"
|
|
12
|
+
:icon="`${isConnected ? 'carbon:plug-filled' : isSyncing ? 'carbon:plug' : 'carbon:connection-signal-off'}`"
|
|
13
|
+
>
|
|
14
|
+
Powersync is
|
|
15
|
+
{{
|
|
16
|
+
isConnected ? "connected" : isSyncing ? "connecting..." : "disconnected"
|
|
17
|
+
}}
|
|
18
|
+
</NTip>
|
|
19
|
+
<NTip
|
|
20
|
+
:n="`${isSyncing ? 'blue' : hasSynced ? 'green' : 'yellow'} xs`"
|
|
21
|
+
:icon="`${isSyncing ? 'carbon:data-unreal' : hasSynced ? 'carbon:checkmark-filled' : 'carbon:async'}`"
|
|
22
|
+
>
|
|
23
|
+
{{ isSyncing ? "Syncing" : hasSynced ? "Synced" : "Not Synced" }}
|
|
24
|
+
</NTip>
|
|
25
|
+
<NTip
|
|
26
|
+
:n="`${isUploading ? 'green' : 'gray'} xs`"
|
|
27
|
+
:icon="isUploading ? 'carbon:cloud-upload' : 'carbon:pause-outline'"
|
|
28
|
+
>
|
|
29
|
+
{{ isUploading ? "Uploading" : "Upload Idle" }}
|
|
30
|
+
</NTip>
|
|
31
|
+
<NTip
|
|
32
|
+
:n="`${isDownloading ? 'green' : 'gray'} xs`"
|
|
33
|
+
:icon="isDownloading ? 'carbon:cloud-download' : 'carbon:pause-outline'"
|
|
34
|
+
>
|
|
35
|
+
{{ isDownloading ? "Downloading" : "Download Idle" }}
|
|
36
|
+
</NTip>
|
|
37
|
+
<NBadge
|
|
38
|
+
flex="~ gap-2 items-center"
|
|
39
|
+
n="gray xs"
|
|
40
|
+
icon="carbon:server-time"
|
|
41
|
+
>
|
|
42
|
+
Last Synced:
|
|
43
|
+
{{ lastSyncedFormatted }}
|
|
44
|
+
</NBadge>
|
|
45
|
+
<NBadge
|
|
46
|
+
flex="~ gap-2 items-center"
|
|
47
|
+
n="gray xs"
|
|
48
|
+
icon="carbon:user-admin"
|
|
49
|
+
>
|
|
50
|
+
Logged in as: {{ userID }}
|
|
51
|
+
</NBadge>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div flex="~ gap-2">
|
|
55
|
+
<NButton
|
|
56
|
+
n="red sm"
|
|
57
|
+
icon="carbon:clean"
|
|
58
|
+
@click="resync"
|
|
59
|
+
>
|
|
60
|
+
Prune & Re-sync
|
|
61
|
+
</NButton>
|
|
62
|
+
<NDarkToggle />
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div flex="~ gap-4">
|
|
67
|
+
<NTip
|
|
68
|
+
v-if="downloadError || uploadError"
|
|
69
|
+
n="red sm"
|
|
70
|
+
mb="3"
|
|
71
|
+
icon="carbon:warning-hex-filled"
|
|
72
|
+
>
|
|
73
|
+
{{ downloadError?.message ?? uploadError?.message }}
|
|
74
|
+
</NTip>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div
|
|
78
|
+
flex="~ gap-2"
|
|
79
|
+
mb="3"
|
|
80
|
+
>
|
|
81
|
+
<NSelectTabs
|
|
82
|
+
v-model="selectedTab"
|
|
83
|
+
n="amber6 dark:amber5"
|
|
84
|
+
cursor="pointer"
|
|
85
|
+
:options="tabs"
|
|
86
|
+
@update:model-value="handleTabChange"
|
|
87
|
+
/>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<!-- switching tabs -->
|
|
91
|
+
<SyncStatusTab v-if="selectedTab === 'sync-status'" />
|
|
92
|
+
<DataInspectorTab v-if="selectedTab === 'data'" />
|
|
93
|
+
<ConfigInspectorTab v-if="selectedTab === 'config'" />
|
|
94
|
+
<BucketsInspectorTab v-if="selectedTab === 'buckets'" />
|
|
95
|
+
<LogsTab v-if="selectedTab === 'logs'" />
|
|
96
|
+
<PowerSyncInstanceTab v-if="selectedTab === 'powersync-instance'" />
|
|
97
|
+
</template>
|
|
98
|
+
|
|
99
|
+
<script setup>
|
|
100
|
+
import { ref, onMounted, computed } from "vue";
|
|
101
|
+
import { useTimeAgo } from "@vueuse/core";
|
|
102
|
+
import { definePageMeta, usePowerSyncInspectorDiagnostics, useRoute, useRouter } from "#imports";
|
|
103
|
+
import SyncStatusTab from "../components/SyncStatusTab.vue";
|
|
104
|
+
import DataInspectorTab from "../components/DataInspectorTab.vue";
|
|
105
|
+
import ConfigInspectorTab from "../components/ConfigInspectorTab.vue";
|
|
106
|
+
import LogsTab from "../components/LogsTab.vue";
|
|
107
|
+
import BucketsInspectorTab from "../components/BucketsInspectorTab.vue";
|
|
108
|
+
import PowerSyncInstanceTab from "../components/PowerSyncInstanceTab.vue";
|
|
109
|
+
definePageMeta({
|
|
110
|
+
layout: "powersync-inspector-layout"
|
|
111
|
+
});
|
|
112
|
+
const {
|
|
113
|
+
db,
|
|
114
|
+
isConnected,
|
|
115
|
+
isSyncing,
|
|
116
|
+
hasSynced,
|
|
117
|
+
isDownloading,
|
|
118
|
+
isUploading,
|
|
119
|
+
downloadError,
|
|
120
|
+
uploadError,
|
|
121
|
+
lastSyncedAt,
|
|
122
|
+
userID,
|
|
123
|
+
clearData
|
|
124
|
+
} = usePowerSyncInspectorDiagnostics();
|
|
125
|
+
onMounted(async () => {
|
|
126
|
+
await db.value?.waitForFirstSync();
|
|
127
|
+
});
|
|
128
|
+
const route = useRoute();
|
|
129
|
+
const router = useRouter();
|
|
130
|
+
const selectedTab = ref(route.query.tab || "sync-status");
|
|
131
|
+
const handleTabChange = (tab) => {
|
|
132
|
+
router.push({ query: { tab } });
|
|
133
|
+
};
|
|
134
|
+
const tabs = [
|
|
135
|
+
{ label: "Sync Status", value: "sync-status" },
|
|
136
|
+
{ label: "Data Inspector", value: "data" },
|
|
137
|
+
{ label: "Buckets Inspector", value: "buckets" },
|
|
138
|
+
{ label: "Config Inspector", value: "config" },
|
|
139
|
+
{ label: "Client Logs", value: "logs" }
|
|
140
|
+
// { label: 'PowerSync Instance', value: 'powersync-instance' },
|
|
141
|
+
];
|
|
142
|
+
const lastSyncedFormatted = computed(
|
|
143
|
+
() => lastSyncedAt.value ? useTimeAgo(new Date(lastSyncedAt.value)) : "NA"
|
|
144
|
+
);
|
|
145
|
+
const resync = async () => {
|
|
146
|
+
await clearData();
|
|
147
|
+
await db.value?.waitForFirstSync();
|
|
148
|
+
};
|
|
149
|
+
</script>
|
|
150
|
+
|
|
151
|
+
<style>
|
|
152
|
+
#__nuxt,#nuxt-test,body,html{background-color:transparent}
|
|
153
|
+
</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,11 @@
|
|
|
1
|
+
import { defineNuxtPlugin, useRoute, useRuntimeConfig } from "#app";
|
|
2
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
3
|
+
const runtimeConfig = useRuntimeConfig();
|
|
4
|
+
nuxtApp.vueApp.config.globalProperties.$powerSyncModuleOptions = runtimeConfig.public.powerSyncModuleOptions;
|
|
5
|
+
const route = useRoute();
|
|
6
|
+
nuxtApp.hook("page:finish", () => {
|
|
7
|
+
if (route.path.startsWith("/__powersync-inspector")) {
|
|
8
|
+
import("virtual:uno.css");
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Schema, Table } from '@powersync/web';
|
|
2
|
+
export declare const LOCAL_BUCKET_DATA_TABLE_NAME = "local_bucket_data";
|
|
3
|
+
export declare const LOCAL_SCHEMA_TABLE_NAME = "local_schema";
|
|
4
|
+
export declare const local_bucket_data: Table<{
|
|
5
|
+
total_operations: import("@powersync/common").BaseColumnType<number | null>;
|
|
6
|
+
last_op: import("@powersync/common").BaseColumnType<string | null>;
|
|
7
|
+
download_size: import("@powersync/common").BaseColumnType<number | null>;
|
|
8
|
+
downloaded_operations: import("@powersync/common").BaseColumnType<number | null>;
|
|
9
|
+
downloading: import("@powersync/common").BaseColumnType<number | null>;
|
|
10
|
+
}>;
|
|
11
|
+
export declare const local_schema: Table<{
|
|
12
|
+
data: import("@powersync/common").BaseColumnType<string | null>;
|
|
13
|
+
}>;
|
|
14
|
+
export declare const DiagnosticsAppSchema: Schema<{
|
|
15
|
+
local_bucket_data: Table<{
|
|
16
|
+
total_operations: import("@powersync/common").BaseColumnType<number | null>;
|
|
17
|
+
last_op: import("@powersync/common").BaseColumnType<string | null>;
|
|
18
|
+
download_size: import("@powersync/common").BaseColumnType<number | null>;
|
|
19
|
+
downloaded_operations: import("@powersync/common").BaseColumnType<number | null>;
|
|
20
|
+
downloading: import("@powersync/common").BaseColumnType<number | null>;
|
|
21
|
+
}>;
|
|
22
|
+
local_schema: Table<{
|
|
23
|
+
data: import("@powersync/common").BaseColumnType<string | null>;
|
|
24
|
+
}>;
|
|
25
|
+
}>;
|
|
26
|
+
export type Database = (typeof DiagnosticsAppSchema)['types'];
|
|
27
|
+
export type LocalBucketData = Database['local_bucket_data'];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { column, Schema, Table } from "@powersync/web";
|
|
2
|
+
export const LOCAL_BUCKET_DATA_TABLE_NAME = "local_bucket_data";
|
|
3
|
+
export const LOCAL_SCHEMA_TABLE_NAME = "local_schema";
|
|
4
|
+
export const local_bucket_data = new Table(
|
|
5
|
+
{
|
|
6
|
+
total_operations: column.integer,
|
|
7
|
+
last_op: column.text,
|
|
8
|
+
download_size: column.integer,
|
|
9
|
+
downloaded_operations: column.integer,
|
|
10
|
+
downloading: column.integer
|
|
11
|
+
},
|
|
12
|
+
{ localOnly: true }
|
|
13
|
+
);
|
|
14
|
+
export const local_schema = new Table(
|
|
15
|
+
{
|
|
16
|
+
data: column.text
|
|
17
|
+
},
|
|
18
|
+
{ localOnly: true }
|
|
19
|
+
);
|
|
20
|
+
export const DiagnosticsAppSchema = new Schema({
|
|
21
|
+
local_bucket_data,
|
|
22
|
+
local_schema
|
|
23
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DBAdapter, SyncDataBatch } from '@powersync/web';
|
|
2
|
+
import { Schema } from '@powersync/web';
|
|
3
|
+
/**
|
|
4
|
+
* Record fields from downloaded data, then build a schema from it.
|
|
5
|
+
*/
|
|
6
|
+
export declare class DynamicSchemaManager {
|
|
7
|
+
private tables;
|
|
8
|
+
private dirty;
|
|
9
|
+
constructor();
|
|
10
|
+
clear(): Promise<void>;
|
|
11
|
+
updateFromOperations(batch: SyncDataBatch): Promise<void>;
|
|
12
|
+
refreshSchema(db: DBAdapter): Promise<void>;
|
|
13
|
+
buildSchema(): Schema;
|
|
14
|
+
schemaToString(): string;
|
|
15
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Column, ColumnType, OpTypeEnum, Schema, Table } from "@powersync/web";
|
|
2
|
+
import { DiagnosticsAppSchema as AppSchema } from "./AppSchema.js";
|
|
3
|
+
import { JsSchemaGenerator } from "./JsSchemaGenerator.js";
|
|
4
|
+
export class DynamicSchemaManager {
|
|
5
|
+
tables;
|
|
6
|
+
dirty = false;
|
|
7
|
+
constructor() {
|
|
8
|
+
const tables = localStorage.getItem("powersync_dynamic_schema");
|
|
9
|
+
this.tables = tables ? JSON.parse(tables) : {};
|
|
10
|
+
}
|
|
11
|
+
async clear() {
|
|
12
|
+
this.tables = {};
|
|
13
|
+
this.dirty = true;
|
|
14
|
+
localStorage.removeItem("powersync_dynamic_schema");
|
|
15
|
+
}
|
|
16
|
+
async updateFromOperations(batch) {
|
|
17
|
+
let schemaDirty = false;
|
|
18
|
+
for (const bucket of batch.buckets) {
|
|
19
|
+
for (const op of bucket.data) {
|
|
20
|
+
if (op.op.value == OpTypeEnum.PUT && op.data != null) {
|
|
21
|
+
this.tables[op.object_type] ??= {};
|
|
22
|
+
const table = this.tables[op.object_type];
|
|
23
|
+
const data = JSON.parse(op.data);
|
|
24
|
+
for (const [key, value] of Object.entries(data)) {
|
|
25
|
+
if (key == "id") {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (table) {
|
|
29
|
+
const existing = table[key];
|
|
30
|
+
if (typeof value == "number" && Number.isInteger(value) && existing != ColumnType.REAL && existing != ColumnType.TEXT) {
|
|
31
|
+
if (table[key] != ColumnType.INTEGER) {
|
|
32
|
+
schemaDirty = true;
|
|
33
|
+
}
|
|
34
|
+
table[key] = ColumnType.INTEGER;
|
|
35
|
+
} else if (typeof value == "number" && existing != ColumnType.TEXT) {
|
|
36
|
+
if (table[key] != ColumnType.REAL) {
|
|
37
|
+
schemaDirty = true;
|
|
38
|
+
}
|
|
39
|
+
table[key] = ColumnType.REAL;
|
|
40
|
+
} else if (typeof value == "string") {
|
|
41
|
+
if (table[key] != ColumnType.TEXT) {
|
|
42
|
+
schemaDirty = true;
|
|
43
|
+
}
|
|
44
|
+
table[key] = ColumnType.TEXT;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (schemaDirty) {
|
|
52
|
+
localStorage.setItem(
|
|
53
|
+
"powersync_dynamic_schema",
|
|
54
|
+
JSON.stringify(this.tables)
|
|
55
|
+
);
|
|
56
|
+
this.dirty = true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async refreshSchema(db) {
|
|
60
|
+
if (this.dirty) {
|
|
61
|
+
const json = this.buildSchema().toJSON();
|
|
62
|
+
await db.execute("SELECT powersync_replace_schema(?)", [
|
|
63
|
+
JSON.stringify(json)
|
|
64
|
+
]);
|
|
65
|
+
this.dirty = false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
buildSchema() {
|
|
69
|
+
const base = AppSchema;
|
|
70
|
+
const tables = [...base.tables];
|
|
71
|
+
for (const [key, value] of Object.entries(this.tables)) {
|
|
72
|
+
const table = new Table({
|
|
73
|
+
name: key,
|
|
74
|
+
columns: Object.entries(value).map(
|
|
75
|
+
([cname, ctype]) => new Column({
|
|
76
|
+
name: cname,
|
|
77
|
+
type: ctype
|
|
78
|
+
})
|
|
79
|
+
)
|
|
80
|
+
});
|
|
81
|
+
tables.push(table);
|
|
82
|
+
}
|
|
83
|
+
return new Schema(tables);
|
|
84
|
+
}
|
|
85
|
+
schemaToString() {
|
|
86
|
+
const filtered = this.buildSchema().tables.filter(
|
|
87
|
+
(table) => !table.localOnly
|
|
88
|
+
);
|
|
89
|
+
return new JsSchemaGenerator().generate(new Schema(filtered));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export class JsSchemaGenerator {
|
|
2
|
+
generate(schema) {
|
|
3
|
+
const tables = schema.tables;
|
|
4
|
+
return this.generateTables(tables) + "\n\n" + this.generateAppSchema(tables);
|
|
5
|
+
}
|
|
6
|
+
generateTables(tables) {
|
|
7
|
+
return tables.map((table) => this.generateTable(table.name, table.columns)).join("\n\n");
|
|
8
|
+
}
|
|
9
|
+
generateTable(name, columns) {
|
|
10
|
+
return `export const ${name} = new Table(
|
|
11
|
+
{
|
|
12
|
+
// id column (text) is automatically included
|
|
13
|
+
${columns.map((column) => this.generateColumn(column)).join(",\n ")}
|
|
14
|
+
},
|
|
15
|
+
{ indexes: {} }
|
|
16
|
+
);`;
|
|
17
|
+
}
|
|
18
|
+
generateColumn(column) {
|
|
19
|
+
return `${column.name}: column.${column.type.toLowerCase()}`;
|
|
20
|
+
}
|
|
21
|
+
generateAppSchema(tables) {
|
|
22
|
+
return `export const AppSchema = new Schema({
|
|
23
|
+
${tables.map((table) => table.name).join(",\n ")}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export type Database = (typeof AppSchema)['types'];`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { PowerSyncDatabase, type DisconnectAndClearOptions, type PowerSyncBackendConnector, type PowerSyncConnectionOptions, type RequiredAdditionalConnectionOptions, type StreamingSyncImplementation, type WebPowerSyncDatabaseOptions } from '@powersync/web';
|
|
2
|
+
/**
|
|
3
|
+
* An extended PowerSync database class that includes diagnostic capabilities for use with the PowerSync Inspector.
|
|
4
|
+
*
|
|
5
|
+
* This class automatically configures diagnostics when `useDiagnostics: true` is set in the module configuration.
|
|
6
|
+
* It provides enhanced VFS support, schema management, and logging capabilities for the inspector.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { NuxtPowerSyncDatabase } from '@powersync/nuxt'
|
|
11
|
+
*
|
|
12
|
+
* const db = new NuxtPowerSyncDatabase({
|
|
13
|
+
* database: {
|
|
14
|
+
* dbFilename: 'your-db-filename.sqlite',
|
|
15
|
+
* },
|
|
16
|
+
* schema: yourSchema,
|
|
17
|
+
* })
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @remarks
|
|
21
|
+
* - When diagnostics are enabled, automatically uses cooperative sync VFS for improved compatibility
|
|
22
|
+
* - Stores connector internally for inspector access
|
|
23
|
+
* - Integrates with dynamic schema management for inspector features
|
|
24
|
+
* - Automatically configures logging when diagnostics are enabled
|
|
25
|
+
* - When diagnostics are disabled, behaves like a standard `PowerSyncDatabase`
|
|
26
|
+
*/
|
|
27
|
+
export declare class NuxtPowerSyncDatabase extends PowerSyncDatabase {
|
|
28
|
+
private schemaManager;
|
|
29
|
+
private _connector;
|
|
30
|
+
private _connectionOptions;
|
|
31
|
+
private useDiagnostics;
|
|
32
|
+
get dbOptions(): WebPowerSyncDatabaseOptions;
|
|
33
|
+
get connector(): PowerSyncBackendConnector | null;
|
|
34
|
+
get connectionOptions(): PowerSyncConnectionOptions | null;
|
|
35
|
+
constructor(options: WebPowerSyncDatabaseOptions);
|
|
36
|
+
protected generateSyncStreamImplementation(connector: PowerSyncBackendConnector, options: RequiredAdditionalConnectionOptions): StreamingSyncImplementation;
|
|
37
|
+
connect(connector: PowerSyncBackendConnector, options?: PowerSyncConnectionOptions): Promise<void>;
|
|
38
|
+
disconnect(): Promise<void>;
|
|
39
|
+
disconnectAndClear(options?: DisconnectAndClearOptions): Promise<void>;
|
|
40
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_SYNC_CLIENT_IMPLEMENTATION,
|
|
3
|
+
PowerSyncDatabase,
|
|
4
|
+
Schema,
|
|
5
|
+
SharedWebStreamingSyncImplementation,
|
|
6
|
+
SyncClientImplementation,
|
|
7
|
+
WebRemote,
|
|
8
|
+
WebStreamingSyncImplementation
|
|
9
|
+
} from "@powersync/web";
|
|
10
|
+
import { RecordingStorageAdapter } from "./RecordingStorageAdapter.js";
|
|
11
|
+
import { usePowerSyncInspector } from "../composables/usePowerSyncInspector.js";
|
|
12
|
+
import { useDiagnosticsLogger } from "../composables/useDiagnosticsLogger.js";
|
|
13
|
+
import { ref } from "vue";
|
|
14
|
+
import { useRuntimeConfig } from "#app";
|
|
15
|
+
import { RustClientInterceptor } from "./RustClientInterceptor.js";
|
|
16
|
+
export class NuxtPowerSyncDatabase extends PowerSyncDatabase {
|
|
17
|
+
schemaManager;
|
|
18
|
+
_connector = null;
|
|
19
|
+
_connectionOptions = null;
|
|
20
|
+
useDiagnostics = false;
|
|
21
|
+
get dbOptions() {
|
|
22
|
+
return this.options;
|
|
23
|
+
}
|
|
24
|
+
get connector() {
|
|
25
|
+
return this._connector ?? super.connector;
|
|
26
|
+
}
|
|
27
|
+
get connectionOptions() {
|
|
28
|
+
return this._connectionOptions ?? super.connectionOptions;
|
|
29
|
+
}
|
|
30
|
+
constructor(options) {
|
|
31
|
+
const useDiagnostics = useRuntimeConfig().public.powerSyncModuleOptions.useDiagnostics ?? false;
|
|
32
|
+
if (useDiagnostics) {
|
|
33
|
+
const { logger } = useDiagnosticsLogger();
|
|
34
|
+
const { getCurrentSchemaManager, diagnosticsSchema } = usePowerSyncInspector();
|
|
35
|
+
const currentSchemaManager = getCurrentSchemaManager();
|
|
36
|
+
options.flags = {
|
|
37
|
+
...options.flags,
|
|
38
|
+
enableMultiTabs: true,
|
|
39
|
+
broadcastLogs: true
|
|
40
|
+
};
|
|
41
|
+
options.logger = logger;
|
|
42
|
+
options.schema = new Schema([...options.schema.tables, ...diagnosticsSchema.tables]);
|
|
43
|
+
super(options);
|
|
44
|
+
this.schemaManager = currentSchemaManager;
|
|
45
|
+
this.useDiagnostics = true;
|
|
46
|
+
} else {
|
|
47
|
+
super(options);
|
|
48
|
+
this.useDiagnostics = false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
generateSyncStreamImplementation(connector, options) {
|
|
52
|
+
if (this.useDiagnostics) {
|
|
53
|
+
const { logger } = useDiagnosticsLogger();
|
|
54
|
+
const { getCurrentSchemaManager } = usePowerSyncInspector();
|
|
55
|
+
const currentSchemaManager = getCurrentSchemaManager();
|
|
56
|
+
const schemaManager = currentSchemaManager || this.schemaManager;
|
|
57
|
+
const clientImplementation = this.connectionOptions?.clientImplementation ?? DEFAULT_SYNC_CLIENT_IMPLEMENTATION;
|
|
58
|
+
const adapter = clientImplementation === SyncClientImplementation.RUST ? new RustClientInterceptor(
|
|
59
|
+
ref(this),
|
|
60
|
+
new WebRemote(connector, logger),
|
|
61
|
+
ref(schemaManager)
|
|
62
|
+
) : new RecordingStorageAdapter(
|
|
63
|
+
ref(this),
|
|
64
|
+
ref(schemaManager)
|
|
65
|
+
);
|
|
66
|
+
if (this.options.flags?.enableMultiTabs) {
|
|
67
|
+
if (!this.resolvedFlags.broadcastLogs) {
|
|
68
|
+
const warning = `
|
|
69
|
+
Multiple tabs are enabled, but broadcasting of logs is disabled.
|
|
70
|
+
Logs for shared sync worker will only be available in the shared worker context
|
|
71
|
+
`;
|
|
72
|
+
logger ? logger.warn(warning) : console.warn(warning);
|
|
73
|
+
}
|
|
74
|
+
return new SharedWebStreamingSyncImplementation({
|
|
75
|
+
...options,
|
|
76
|
+
adapter,
|
|
77
|
+
remote: new WebRemote(connector, logger),
|
|
78
|
+
uploadCrud: async () => {
|
|
79
|
+
await this.waitForReady();
|
|
80
|
+
await connector.uploadData(this);
|
|
81
|
+
},
|
|
82
|
+
logger,
|
|
83
|
+
db: this.database
|
|
84
|
+
});
|
|
85
|
+
} else {
|
|
86
|
+
return new WebStreamingSyncImplementation({
|
|
87
|
+
...options,
|
|
88
|
+
adapter,
|
|
89
|
+
remote: new WebRemote(connector, logger),
|
|
90
|
+
uploadCrud: async () => {
|
|
91
|
+
await this.waitForReady();
|
|
92
|
+
await connector.uploadData(this);
|
|
93
|
+
},
|
|
94
|
+
identifier: "dbFilename" in this.options.database ? this.options.database.dbFilename : "diagnostics-sync",
|
|
95
|
+
logger
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
return super.generateSyncStreamImplementation(connector, options);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async connect(connector, options) {
|
|
103
|
+
this._connector = connector;
|
|
104
|
+
this._connectionOptions = options ?? null;
|
|
105
|
+
await super.connect(connector, options);
|
|
106
|
+
}
|
|
107
|
+
async disconnect() {
|
|
108
|
+
this._connector = null;
|
|
109
|
+
this._connectionOptions = null;
|
|
110
|
+
await super.disconnect();
|
|
111
|
+
}
|
|
112
|
+
async disconnectAndClear(options) {
|
|
113
|
+
this._connector = null;
|
|
114
|
+
this._connectionOptions = null;
|
|
115
|
+
await super.disconnectAndClear(options);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Checkpoint, ColumnType, PowerSyncDatabase, SyncDataBatch } from '@powersync/web';
|
|
2
|
+
import { SqliteBucketStorage } from '@powersync/web';
|
|
3
|
+
import type { DynamicSchemaManager } from './DynamicSchemaManager.js';
|
|
4
|
+
import type { Ref } from 'vue';
|
|
5
|
+
export declare class RecordingStorageAdapter extends SqliteBucketStorage {
|
|
6
|
+
private rdb;
|
|
7
|
+
private schemaManager;
|
|
8
|
+
tables: Record<string, Record<string, ColumnType>>;
|
|
9
|
+
constructor(db: Ref<PowerSyncDatabase>, schemaManager: Ref<DynamicSchemaManager>);
|
|
10
|
+
setTargetCheckpoint(checkpoint: Checkpoint): Promise<void>;
|
|
11
|
+
syncLocalDatabase(checkpoint: Checkpoint, priority?: number): Promise<import("@powersync/common").SyncLocalDatabaseResult>;
|
|
12
|
+
saveSyncData(batch: SyncDataBatch): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { AbstractPowerSyncDatabase, SqliteBucketStorage } from "@powersync/web";
|
|
2
|
+
export class RecordingStorageAdapter extends SqliteBucketStorage {
|
|
3
|
+
rdb;
|
|
4
|
+
schemaManager;
|
|
5
|
+
tables = {};
|
|
6
|
+
constructor(db, schemaManager) {
|
|
7
|
+
super(
|
|
8
|
+
db.value.database,
|
|
9
|
+
AbstractPowerSyncDatabase.transactionMutex
|
|
10
|
+
);
|
|
11
|
+
this.rdb = db.value.database;
|
|
12
|
+
this.schemaManager = schemaManager.value;
|
|
13
|
+
}
|
|
14
|
+
async setTargetCheckpoint(checkpoint) {
|
|
15
|
+
await super.setTargetCheckpoint(checkpoint);
|
|
16
|
+
await this.rdb.writeTransaction(async (tx) => {
|
|
17
|
+
for (const bucket of checkpoint.buckets) {
|
|
18
|
+
await tx.execute(
|
|
19
|
+
`INSERT OR REPLACE INTO local_bucket_data(id, total_operations, last_op, download_size, downloading, downloaded_operations)
|
|
20
|
+
VALUES (
|
|
21
|
+
?,
|
|
22
|
+
?,
|
|
23
|
+
IFNULL((SELECT last_op FROM local_bucket_data WHERE id = ?), '0'),
|
|
24
|
+
IFNULL((SELECT download_size FROM local_bucket_data WHERE id = ?), 0),
|
|
25
|
+
IFNULL((SELECT downloading FROM local_bucket_data WHERE id = ?), TRUE),
|
|
26
|
+
IFNULL((SELECT downloaded_operations FROM local_bucket_data WHERE id = ?), TRUE)
|
|
27
|
+
)`,
|
|
28
|
+
[
|
|
29
|
+
bucket.bucket,
|
|
30
|
+
bucket.count,
|
|
31
|
+
bucket.bucket,
|
|
32
|
+
bucket.bucket,
|
|
33
|
+
bucket.bucket,
|
|
34
|
+
bucket.bucket
|
|
35
|
+
]
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
async syncLocalDatabase(checkpoint, priority) {
|
|
41
|
+
const r = await super.syncLocalDatabase(checkpoint, priority);
|
|
42
|
+
setTimeout(() => {
|
|
43
|
+
this.schemaManager.refreshSchema(this.rdb);
|
|
44
|
+
}, 60);
|
|
45
|
+
if (r.checkpointValid) {
|
|
46
|
+
await this.rdb.execute(
|
|
47
|
+
"UPDATE local_bucket_data SET downloading = FALSE"
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return r;
|
|
51
|
+
}
|
|
52
|
+
async saveSyncData(batch) {
|
|
53
|
+
await super.saveSyncData(batch);
|
|
54
|
+
await this.rdb.writeTransaction(async (tx) => {
|
|
55
|
+
for (const bucket of batch.buckets) {
|
|
56
|
+
const size = JSON.stringify(bucket.data).length;
|
|
57
|
+
await tx.execute(
|
|
58
|
+
`UPDATE local_bucket_data SET
|
|
59
|
+
download_size = IFNULL(download_size, 0) + ?,
|
|
60
|
+
last_op = ?,
|
|
61
|
+
downloading = ?,
|
|
62
|
+
downloaded_operations = IFNULL(downloaded_operations, 0) + ?
|
|
63
|
+
WHERE id = ?`,
|
|
64
|
+
[
|
|
65
|
+
size,
|
|
66
|
+
bucket.next_after,
|
|
67
|
+
bucket.has_more,
|
|
68
|
+
bucket.data.length,
|
|
69
|
+
bucket.bucket
|
|
70
|
+
]
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
await this.schemaManager.updateFromOperations(batch);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AbstractRemote, ColumnType, PowerSyncDatabase } from '@powersync/web';
|
|
2
|
+
import { PowerSyncControlCommand, SqliteBucketStorage } from '@powersync/web';
|
|
3
|
+
import type { DynamicSchemaManager } from './DynamicSchemaManager.js';
|
|
4
|
+
import type { Ref } from 'vue';
|
|
5
|
+
/**
|
|
6
|
+
* Tracks per-byte and per-operation progress for the Rust client.
|
|
7
|
+
*
|
|
8
|
+
* While per-operation progress is reported by the SDK as well, we need those counters for each bucket. Since that
|
|
9
|
+
* information is internal to the Rust client and inaccessible to JavaScript, this intercepts the raw
|
|
10
|
+
* `powersync_control` calls to decode sync lines and derive progress information.
|
|
11
|
+
*/
|
|
12
|
+
export declare class RustClientInterceptor extends SqliteBucketStorage {
|
|
13
|
+
private remote;
|
|
14
|
+
private schemaManager;
|
|
15
|
+
private rdb;
|
|
16
|
+
private lastStartedCheckpoint;
|
|
17
|
+
tables: Record<string, Record<string, ColumnType>>;
|
|
18
|
+
constructor(db: Ref<PowerSyncDatabase>, remote: AbstractRemote, schemaManager: Ref<DynamicSchemaManager>);
|
|
19
|
+
control(op: PowerSyncControlCommand, payload: string | Uint8Array | ArrayBuffer | null): Promise<string>;
|
|
20
|
+
private processTextLine;
|
|
21
|
+
private processBinaryLine;
|
|
22
|
+
private processParsedLine;
|
|
23
|
+
private trackCheckpoint;
|
|
24
|
+
}
|