Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1865d0a
feat(diagnostics-core): add core diagnostics package with agent and c…
khawarizmus Sep 2, 2026
85658a7
chore(diagnostics-ui): update pnpm lockfile, add diagnostics UI packa…
khawarizmus Sep 2, 2026
e4b8dd7
feat(diagnostics-extension): add Chrome DevTools extension for PowerS…
khawarizmus Sep 2, 2026
6a83c77
feat(schema): implement serialization for schema, tables, columns, an…
khawarizmus Sep 2, 2026
6b92b8d
refactor(inspector): remove unused components and composables; stream…
khawarizmus Sep 2, 2026
5071650
test(schema): add comprehensive tests for Schema serialization, inclu…
khawarizmus Sep 3, 2026
82d6c71
feat(diagnostics): enhance diagnostics capabilities with new event st…
khawarizmus Sep 4, 2026
b0a03d7
feat(diagnostics): introduce DiagnosticsAgent and related components …
khawarizmus Sep 4, 2026
3e6c254
fix(connection-manager): add currentConnector to track active connect…
khawarizmus Sep 4, 2026
d1c5001
fix(diagnostics): implement resync functionality for DiagnosticsClien…
khawarizmus Sep 4, 2026
396f99e
feat(diagnostics): update diagnostics core and UI packages with new P…
khawarizmus Sep 4, 2026
8b73bc5
feat(diagnostics-ui): integrate theme management and enhance UI compo…
khawarizmus Sep 4, 2026
56847be
feat(diagnostics-ui): add new components for enhanced diagnostics dis…
khawarizmus Sep 4, 2026
2c61ab0
feat(diagnostics-ui): enhance diagnostics UI with new logs tab, bucke…
khawarizmus Sep 4, 2026
58d0a97
feat(diagnostics-ui): improve diagnostics UI with tab persistence, re…
khawarizmus Sep 7, 2026
7f3a0f3
chore: revert some unrelated changes
khawarizmus Sep 8, 2026
e6f46ea
refactored(diagnostics): dropped public interface changes and documen…
khawarizmus Sep 10, 2026
c3e92ba
feat(diagnostics-core): define the SdkIntegration and protocol shapes
khawarizmus Sep 14, 2026
03144bc
refactor(diagnostics): move the protocol to the diagnostics package; …
khawarizmus Sep 15, 2026
966da2b
refactor(diagnostics): add package READMEs, drop NuxtPowerSyncDatabase
khawarizmus Sep 15, 2026
cd772a3
fix(diagnostics): stream subscription timestamps, PowerSync dock icon
khawarizmus Sep 16, 2026
d61565d
refactor(diagnostics): address review on the protocol package
khawarizmus Sep 16, 2026
3896fe4
feat(diagnostics): ship PowerSync DevTools as a devframe definition
khawarizmus Sep 17, 2026
a3c11d6
fix(diagnostics): Nuxt devtools now mounts on Vite devtools properly
khawarizmus Sep 18, 2026
2b966bd
feat(diagnostics): MCP origin knob for node and CLI and spell out too…
khawarizmus Sep 18, 2026
154cc11
fix(diagnostics): announce source attach and detach, surface tool err…
khawarizmus Sep 18, 2026
bd5b558
fix(diagnostics): keep the Nuxt plugin quiet in production, note Vite…
khawarizmus Sep 18, 2026
57dd929
chore(diagnostics): trim traces, type the Nuxt runtime options, mark …
khawarizmus Sep 18, 2026
af11011
feat(diagnostics): CLI --token for connectAgent clients, location gua…
khawarizmus Sep 18, 2026
31465e5
build: make CI green for the diagnostics packages
khawarizmus Sep 18, 2026
bc0edf9
chore: update dependencies
khawarizmus Sep 22, 2026
732cc9b
chore: updated capacitor deps
khawarizmus Sep 22, 2026
21248fe
chore: pinned some dependencies
khawarizmus Sep 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/angry-yaks-push.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@powersync/diagnostics-ui': minor
---

Initial release: the shared PowerSync diagnostics UI, rendered from an `SdkIntegration`.
5 changes: 5 additions & 0 deletions .changeset/new-keys-joke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@powersync/web': minor
---

Add a `@powersync/web/devtools` subpath that lists the page's open databases for development tooling.
5 changes: 5 additions & 0 deletions .changeset/pretty-owls-punch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@powersync/shared-internals': patch
---

Fix `expiresAt` and `lastSyncedAt` on sync stream subscriptions being invalid dates.
5 changes: 5 additions & 0 deletions .changeset/shaggy-tools-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@powersync/shared-internals': minor
---

Wire the core diagnostics event stream into the sync client and keep the active `connector` available for the whole connection lifetime.
5 changes: 5 additions & 0 deletions .changeset/shiny-items-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@powersync/diagnostics-core': minor
---

Initial release: the `SdkIntegration` protocol, the JavaScript agent, and the comlink iframe bridge for live-attach diagnostics.
5 changes: 5 additions & 0 deletions .changeset/silent-dots-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@powersync/diagnostics': minor
---

Initial release: PowerSync DevTools as a devframe definition, mounted as a Vite DevTools dock, a node dev server, a standalone window, and MCP tools.
5 changes: 5 additions & 0 deletions .changeset/silly-apricots-clap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@powersync/nuxt': minor
---

**Breaking:** `NuxtPowerSyncDatabase` is removed; use `PowerSyncDatabase` with `connect(connector, { diagnostics: true })`. Diagnostics now come from `@powersync/diagnostics`: a PowerSync tab on Nuxt DevTools 3, the devframe dock with MCP tools on Nuxt DevTools 4.
5 changes: 5 additions & 0 deletions .changeset/strange-peaches-jog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@powersync/common': minor
---

Add a `diagnostics` sync option that enables the SQLite core's diagnostics event stream.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
node_modules
.env
lib
# Source folders named lib are not build output.
!packages/*/src/lib/
!tools/diagnostics-app/src/lib/
dist
.nuxt
Expand Down
2 changes: 1 addition & 1 deletion demos/nuxt-supabase-todolist/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const appIsReady = ref(false)
provide('appIsReady', readonly(appIsReady))

const powerSync = usePowerSync()
const syncStatus = usePowerSyncStatus()
const syncStatus = useStatus()

const user = useSupabaseUser()
const { logger: powerSyncLogger } = useDiagnosticsLogger()
Expand Down
23 changes: 22 additions & 1 deletion demos/nuxt-supabase-todolist/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
import type { Plugin } from 'vite';

/**
* Nuxt's `nuxt:replace` plugin rewrites `import.meta.*` defines with a text search, including inside
* string literals. Vite prepends its env module, which lists those defines as object keys, to workers
* spawned with `@vite-ignore` options such as the PowerSync worker. The replaced keys are not valid
* JavaScript and the worker fails to transform. This plugin runs before `nuxt:replace` and turns the
* keys into computed keys the text search cannot match.
*/
function workerEnvDefinesWorkaround(): Plugin {
return {
name: 'powersync-demo:worker-env-defines',
transform(code, id) {
if (!id.includes('worker_file&type=ignore')) return;
return { code: code.replace(/"import\.meta\.(\w+)":/g, '["import.meta" + ".$1"]:'), map: null };
}
};
}

export default defineNuxtConfig({
modules: ['@powersync/nuxt', '@nuxt/eslint', '@nuxt/ui', '@nuxtjs/supabase'],
ssr: false,
Expand Down Expand Up @@ -27,7 +46,9 @@ export default defineNuxtConfig({

worker: {
format: 'es'
}
},

plugins: [workerEnvDefinesWorkaround()]
},

unocss: {
Expand Down
6 changes: 4 additions & 2 deletions demos/nuxt-supabase-todolist/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"private": true,
"name": "powersync-nuxt-supabase-todolist",
"type": "module",
"packageManager": "pnpm@11.21.0",
"scripts": {
"dev": "nuxt dev",
"build": "nuxt build",
Expand All @@ -18,8 +20,8 @@
},
"devDependencies": {
"@iconify-json/carbon": "^1.2.18",
"@nuxt/devtools": "^1.0.6",
"@nuxt/devtools": "^3.4.2",
"@nuxt/eslint": "^1.9.0",
"nuxt": "4.1.3"
"nuxt": "4.5.2"
}
}
3 changes: 2 additions & 1 deletion demos/nuxt-supabase-todolist/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ const user = asyncComputed(async () => await client.auth.getUser().then(res => r
const toast = useToast()

const db = usePowerSyncKysely<Database>()
const { clearData } = usePowerSyncInspectorDiagnostics()
const powerSync = usePowerSync()
const clearData = () => powerSync.value.disconnectAndClear()

const taskQuery = computed(() => db.selectFrom('tasks')
.where('user_id', '=', user.value?.id ?? '')
Expand Down
6 changes: 4 additions & 2 deletions demos/nuxt-supabase-todolist/plugins/powersync.client.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { PowerSyncDatabase } from '@powersync/web'
import {
AppSchemaWithDiagnostics,
} from '~/powersync/AppSchema'
import { SupabaseConnector } from '~/powersync/SuperbaseConnector'
export default defineNuxtPlugin({
async setup(nuxtApp) {
const db = new NuxtPowerSyncDatabase({
const db = new PowerSyncDatabase({
database: {
dbFilename: 'a-db-name.sqlite',
},
Expand All @@ -15,7 +16,8 @@ export default defineNuxtPlugin({

await db.init()

await db.connect(connector)
// Enables the core diagnostics stream (per-bucket totals in the diagnostics UI).
await db.connect(connector, { diagnostics: true })

const plugin = createPowerSyncPlugin({ database: db })

Expand Down
1 change: 1 addition & 0 deletions packages/capacitor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"rollup": "^4.53.2",
"rollup-plugin-dts": "catalog:",
"swiftlint": "^2.0.0",
"vite": "catalog:",
"vitest": "catalog:",
"@vitest/browser-preview": "catalog:"
},
Expand Down
1 change: 1 addition & 0 deletions packages/common/etc/common.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1062,6 +1062,7 @@ export interface SyncOptions {
checkpointMode?: CheckpointMode;
connectionMethod?: SyncStreamConnectionMethod;
crudUploadThrottleMs?: number;
diagnostics?: boolean;
// (undocumented)
fetchStrategy?: FetchStrategy;
includeDefaultStreams?: boolean;
Expand Down
7 changes: 7 additions & 0 deletions packages/common/src/client/sync/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ export interface SyncOptions {
* The mode used to request checkpoints from the service (used after uploading local data).
*/
checkpointMode?: CheckpointMode;

/**
* Enables the core extension's diagnostics event stream — detailed per-bucket download stats
* (including per-bucket `target_count`) and inferred column types — delivered as
* `HandleDiagnostics` instructions. Off by default.
*/
diagnostics?: boolean;
}

/**
Expand Down
94 changes: 94 additions & 0 deletions packages/diagnostics-core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# PowerSync Diagnostics — core

This package (`packages/diagnostics-core`) defines the protocol between the [PowerSync](https://powersync.com) diagnostics tool and a PowerSync SDK, and ships the pieces that implement it for JavaScript.

The main entrypoint imports nothing from any PowerSync SDK. The protocol is owned by the tool, and each SDK implements it in its own language.

## The protocol in one picture

The contract is one interface, `SdkIntegration`, plus the data shapes it returns and emits.

```
┌── Any host (DevTools dock · Flutter DevTools · window) ──────────┐
│ UI ──calls──▶ SdkIntegration ◀──implemented by── SDK side │
└──────────────────────────────────────────────────────────────────┘
```

- The **UI** calls `SdkIntegration` methods and renders what comes back.
- Each **SDK** provides one implementation. It reaches the live client with whatever that environment already has.

The tool never carries a wire format of its own. Every method is asynchronous, so the interface can sit directly on an RPC boundary. Request/response correlation, connection and reconnection are the implementation's concern.

The reference definition is the TypeScript itself: [`src/integration.ts`](./src/integration.ts) for the interface and the pushed events, [`src/shapes.ts`](./src/shapes.ts) for every data shape and action. Their doc comments are the specification.

## What is in the package

`@powersync/diagnostics-core`:

- **`SdkIntegration`** and **`DiagnosticsEvent`** — the interface an SDK implements so the diagnostics UI can inspect a live client: run SQL, read the schema and connection info, observe sync state, and run control actions.
- **The data shapes** — `SyncState`, `StreamState`, `BucketState`, `SchemaPayload`, and the rest. Plain JSON, epoch milliseconds, `null` for "does not apply".
- **The iframe bridge** — `exposeIntegration`, `connectIntegration`, `attachIframe`, and `awaitIntegration` move an integration across a `postMessage` boundary with [comlink](https://github.com/GoogleChromeLabs/comlink). The UI always runs in an iframe; the integration lives on the other side.
- **`SourceAwareIntegration`** — the optional extension for a host that fronts several databases (a dev server that several app tabs attach to): it reports the attached databases and switches between them. Single-database hosts, and the iframe bridge, implement the base interface only.
- **`createDiagnosticsStores`** — reactive stores derived from an integration's events, for the UI. On a `SourceAwareIntegration` they follow the selected database and start over when it changes or detaches.

`@powersync/diagnostics-core/js` (JavaScript hosts only):

- **`JsAgent`** — the JavaScript implementation. It runs in the app page next to a live database and reads it through a structural `LiveDatabase` interface, so this package still imports no SDK. The seam is type-checked where a concrete database is passed in, in `@powersync/diagnostics`.
- **`toSyncState`** and **`toStreamStates`** — the mapping from the SDK's sync status to the protocol shapes.

## Who uses it

Most apps do not use this package directly. Use [`@powersync/diagnostics`](https://github.com/powersync-ja/powersync-js/tree/main/packages/diagnostics), which runs the agent and serves the UI for you.

Use this package directly when you build a new host or a new SDK integration.

## Implement the protocol for a new SDK

An implementation needs to:

1. Run read and write SQL and return rows (`runQuery`).
2. Return the core schema payload the client already sends to the core (`getSchema`).
3. Read connection metadata (`getInfo`). Derive `userId` from the token when the SDK has no accessor.
4. Read pending upload stats (`getUploadQueueStats`), through a method or SQL on `ps_crud`.
5. Map the SDK's sync status to `SyncState` and `StreamState`, and push both on every change (`observeEvents`).
6. Read `ps_buckets` and push `BucketState[]` when internal tables change; fold in `target_count` from core events.
7. Run the control actions (`action`).
8. Optionally forward log records and core diagnostics events.

Where the implementation runs is up to the environment. On the web it runs **in the app page**, next to the database, and is bridged to the UI iframe over `postMessage`. In Flutter DevTools it runs **in the DevTools extension**, reaching the app over the VM service, and is bridged to the same UI iframe the same way. The Dart SDK's existing VM-service commands map directly: `select`/`execute` → `runQuery`, `schema` → `getSchema`, `status-listen` → `currentSyncStatus` + `observeEvents`, `list` → `getInfo`.

Then hand the implementation to the UI. On the web this means serving it on a `MessagePort` to the UI iframe:

```ts
import { attachIframe, type SdkIntegration } from '@powersync/diagnostics-core';

const integration: SdkIntegration = createMyIntegration();
const frame = document.querySelector('iframe#diagnostics')!;
const stop = attachIframe(integration, frame);
```

The UI side calls `awaitIntegration()` and receives the port.

## Enablement per SDK

- **JavaScript** — never shipped to production. The integration is loaded only by the development tooling (`@powersync/diagnostics`) when a dev server runs. The SDK carries only what the core needs: the `diagnostics` sync option that switches on the core event stream.
- **Dart** — on by default in debug builds, off in release builds, as the Dart SDK already does.

## Run the JavaScript agent yourself

```ts
import { exposeIntegration } from '@powersync/diagnostics-core';
import { JsAgent } from '@powersync/diagnostics-core/js';

const agent = new JsAgent(db, {
sdk: '@powersync/web',
connection: {
getConnector: () => db.connector,
getConnectionOptions: () => db.connectionOptions
}
});

const channel = new MessageChannel();
exposeIntegration(agent, channel.port1);
// Post channel.port2 to the UI iframe.
```
50 changes: 50 additions & 0 deletions packages/diagnostics-core/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "@powersync/diagnostics-core",
"version": "0.1.0",
"type": "module",
"description": "The PowerSync diagnostics protocol (SdkIntegration and its data shapes), the iframe bridge, and the JavaScript agent.",
"author": "PowerSync",
"license": "Apache-2.0",
"homepage": "https://docs.powersync.com",
"repository": {
"type": "git",
"url": "git+https://github.com/powersync-ja/powersync-js.git"
},
"bugs": {
"url": "https://github.com/powersync-ja/powersync-js/issues"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public"
},
"main": "lib/src/index.js",
"types": "lib/src/index.d.ts",
"exports": {
".": {
"types": "./lib/src/index.d.ts",
"default": "./lib/src/index.js"
},
"./js": {
"types": "./lib/src/js.d.ts",
"default": "./lib/src/js.js"
}
},
"scripts": {
"build": "tsc -b",
"build:prod": "tsc -b",
"clean": "rm -rf lib dist tsconfig.tsbuildinfo",
"watch": "tsc --build -w"
},
"files": [
"lib",
"src"
],
"devDependencies": {
"@types/node": "catalog:",
"typescript": "catalog:"
},
"dependencies": {
"comlink": "catalog:",
"nanostores": "^1.5.2"
}
}
Loading
Loading