@powersync/nuxt 0.0.0-dev-20260128023420

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/LICENSE +201 -0
  2. package/README +374 -0
  3. package/dist/module.d.mts +39 -0
  4. package/dist/module.json +9 -0
  5. package/dist/module.mjs +160 -0
  6. package/dist/runtime/assets/powersync-icon.svg +14 -0
  7. package/dist/runtime/components/BucketsInspectorTab.d.vue.ts +3 -0
  8. package/dist/runtime/components/BucketsInspectorTab.vue +646 -0
  9. package/dist/runtime/components/BucketsInspectorTab.vue.d.ts +3 -0
  10. package/dist/runtime/components/ConfigInspectorTab.d.vue.ts +3 -0
  11. package/dist/runtime/components/ConfigInspectorTab.vue +121 -0
  12. package/dist/runtime/components/ConfigInspectorTab.vue.d.ts +3 -0
  13. package/dist/runtime/components/DataInspectorTab.d.vue.ts +3 -0
  14. package/dist/runtime/components/DataInspectorTab.vue +678 -0
  15. package/dist/runtime/components/DataInspectorTab.vue.d.ts +3 -0
  16. package/dist/runtime/components/LoadingSpinner.d.vue.ts +3 -0
  17. package/dist/runtime/components/LoadingSpinner.vue +12 -0
  18. package/dist/runtime/components/LoadingSpinner.vue.d.ts +3 -0
  19. package/dist/runtime/components/LogsTab.d.vue.ts +3 -0
  20. package/dist/runtime/components/LogsTab.vue +325 -0
  21. package/dist/runtime/components/LogsTab.vue.d.ts +3 -0
  22. package/dist/runtime/components/PowerSyncInstanceTab.d.vue.ts +3 -0
  23. package/dist/runtime/components/PowerSyncInstanceTab.vue +9 -0
  24. package/dist/runtime/components/PowerSyncInstanceTab.vue.d.ts +3 -0
  25. package/dist/runtime/components/SyncStatusTab.d.vue.ts +3 -0
  26. package/dist/runtime/components/SyncStatusTab.vue +272 -0
  27. package/dist/runtime/components/SyncStatusTab.vue.d.ts +3 -0
  28. package/dist/runtime/composables/useDiagnosticsLogger.d.ts +27 -0
  29. package/dist/runtime/composables/useDiagnosticsLogger.js +41 -0
  30. package/dist/runtime/composables/usePowerSyncInspector.d.ts +42 -0
  31. package/dist/runtime/composables/usePowerSyncInspector.js +19 -0
  32. package/dist/runtime/composables/usePowerSyncInspectorDiagnostics.d.ts +153 -0
  33. package/dist/runtime/composables/usePowerSyncInspectorDiagnostics.js +254 -0
  34. package/dist/runtime/composables/usePowerSyncKysely.d.ts +23 -0
  35. package/dist/runtime/composables/usePowerSyncKysely.js +7 -0
  36. package/dist/runtime/index.d.ts +9 -0
  37. package/dist/runtime/layouts/powersync-inspector-layout.d.vue.ts +13 -0
  38. package/dist/runtime/layouts/powersync-inspector-layout.vue +90 -0
  39. package/dist/runtime/layouts/powersync-inspector-layout.vue.d.ts +13 -0
  40. package/dist/runtime/pages/__powersync-inspector.d.vue.ts +3 -0
  41. package/dist/runtime/pages/__powersync-inspector.vue +153 -0
  42. package/dist/runtime/pages/__powersync-inspector.vue.d.ts +3 -0
  43. package/dist/runtime/plugin.client.d.ts +2 -0
  44. package/dist/runtime/plugin.client.js +11 -0
  45. package/dist/runtime/utils/AppSchema.d.ts +27 -0
  46. package/dist/runtime/utils/AppSchema.js +23 -0
  47. package/dist/runtime/utils/DynamicSchemaManager.d.ts +15 -0
  48. package/dist/runtime/utils/DynamicSchemaManager.js +91 -0
  49. package/dist/runtime/utils/JsSchemaGenerator.d.ts +8 -0
  50. package/dist/runtime/utils/JsSchemaGenerator.js +28 -0
  51. package/dist/runtime/utils/NuxtPowerSyncDatabase.d.ts +40 -0
  52. package/dist/runtime/utils/NuxtPowerSyncDatabase.js +117 -0
  53. package/dist/runtime/utils/RecordingStorageAdapter.d.ts +13 -0
  54. package/dist/runtime/utils/RecordingStorageAdapter.js +76 -0
  55. package/dist/runtime/utils/RustClientInterceptor.d.ts +24 -0
  56. package/dist/runtime/utils/RustClientInterceptor.js +102 -0
  57. package/dist/runtime/utils/TokenConnector.d.ts +14 -0
  58. package/dist/runtime/utils/TokenConnector.js +62 -0
  59. package/dist/runtime/utils/addImportsFrom.d.ts +1 -0
  60. package/dist/runtime/utils/addImportsFrom.js +4 -0
  61. package/dist/runtime/utils/index.d.ts +1 -0
  62. package/dist/runtime/utils/index.js +1 -0
  63. package/dist/types.d.mts +9 -0
  64. package/package.json +90 -0
@@ -0,0 +1,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,2 @@
1
+ declare const _default: import("#app").Plugin<Record<string, unknown>> & import("#app").ObjectPlugin<Record<string, unknown>>;
2
+ 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,8 @@
1
+ import type { Schema } from '@powersync/web';
2
+ export declare class JsSchemaGenerator {
3
+ generate(schema: Schema): string;
4
+ private generateTables;
5
+ private generateTable;
6
+ private generateColumn;
7
+ private generateAppSchema;
8
+ }
@@ -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
+ }