Skip to content

Universal PowerSync Diagnostics (POC) - #1094

Open
khawarizmus wants to merge 53 commits into
mainfrom
diagnostics-poc
Open

khawarizmus wants to merge 53 commits into
mainfrom
diagnostics-poc

Conversation

@khawarizmus

@khawarizmus khawarizmus commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

This PR implements a live-attach diagnostics tool built once and reused across different hosts. It shows the live state of the PowerSync client side.

How it works

The agent runs next to your database. It reads the database and pushes snapshots. The UI rebuilds its state from these snapshots. On the web, the agent runs in your app page. Vite DevTools loads it after you trust the tab. On node, the agent runs in the same process as the database. A React Native app connects to the standalone window over the network (RN has not been tested yet).

The MCP endpoint exposes the same functions as the tools, allowing AI agents to directly query the client app and retrieve diagnostic information.

New packages

  • @powersync/diagnostics-core. The protocol. It is one interface, SdkIntegration, plus the data shapes. The UI talks only to this interface. Each SDK implements it. The package also has the JavaScript agent and the iframe bridge.
  • @powersync/diagnostics-ui. The UI. Rendered from an SdkIntegration. It runs in an iframe or in a standalone window.
  • @powersync/diagnostics. The host integration, built on devframe. It gives a dock in Vite DevTools, a tab in Nuxt DevTools, a dev server for Node apps, a standalone window via CLI (powersync-devtools), and MCP tools for AI agents.

SDK changes

  • @powersync/common. a diagnostics option on connect(). It turns on the SQLite core diagnostics stream. This gives pre-bucket download stats and inferred column types.
  • @powersync/shared-internals: the sync client keeps the connector for the whole connection. It forwards the core diagnostics events.
  • @powersync/web. a new @powersync/web/devtools subpath. It lists the open databases in the page. The agent finds them without any app code.
  • @powersync/nuxt. breaking. NuxtPowerSyncDatabase is removed. Use PowerSyncDatabase from @powersync/web and pass connect(connector, { diagnostics: true }). The module shows the PowerSync tab on Nuxt DevTools 3 and the dock on Nuxt DevTools 4.

Tested by hand

  • Nuxt 4.5 with Nuxt DevTools 3 and with Nuxt DevTools 4 (Vite devtools). With and without compatibilityVersion: 5.
  • React with Vite 8.3 (devtools: true) and with Vite 7 (@vitejs/devtools plugin).
  • The standalone window (using React with Vite): no app tab, one tab, several tabs, closing tabs, reload.
  • MCP from curl and from Claude, on the Vite dev server and on the Node server.
  • A Node app with its own DevTools window.
  • The CLI window with a pre-shared token, from a Node process.
  • Production builds of the React app and the Nuxt demo not shipping any diagnostics code in the production build.

Claude code was used to steer the discovery and development. The code was reviewed, tested and modified manually.

@changeset-bot

changeset-bot Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 170d577

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@powersync/diagnostics-core Patch
@powersync/diagnostics-ui Patch
@powersync/diagnostics Patch
@powersync/web Minor
@powersync/node Minor
@powersync/shared-internals Minor
@powersync/nuxt Major
@powersync/common Minor
@powersync/adapter-sql-js Patch
@powersync/tanstack-react-query Patch
@powersync/diagnostics-app Patch
@powersync/capacitor Patch
@powersync/react-native Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@simolus3 simolus3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To share more context here (I've also shared that offline with more context @khawarizmus). I think the idea is awesome, my initial architectural complaints are:

  1. It's not clear whether we need a browser extension, so remove that from this PR which is already very big (you can open a follow-up PR for that separately).
  2. I think a JSON-based protocol over "any two way channel" is the wrong abstraction here. It e.g. forces us to re-invent an RPC protocol when some implementations we'll use (say the Dart VM service protocol) are already RPC based.
  3. None of this should require any changes to an existing SDK package (aside from forwarding core diagnostics events, but even that should likely be a separate PR to validate that the basic integration can work without SDK changes). We can likely inject an agent by writing a vite devtools package instead. Or initially, we could also expose it as a seperate package a user would depend on to expose the channel (e.g. a enablePowerSyncDiagnostics(port: MessagePort, db: BasePowerSyncDatabase)).
  4. Related to 2 and 3, the interface the diagnostics tool expects an SDK to provide should be an actual TypeScript interface with async methods and event listeners. It is the responsibility of each integration (vite, postMessage iframe, Dart) to figure out how to implement that interface by doing the serialization itself.

Comment thread packages/diagnostics-ui/playground/mockDatabase.ts Fixed

@simolus3 simolus3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't take a look at the UI yet, but the architecture of how devtools are served and integrated looks pretty good at this point. I'll also try to scaffold a Dart integration over the vm service protocol.

Comment thread packages/diagnostics-core/src/bridge.ts Outdated
Comment thread packages/diagnostics-core/src/index.ts Outdated
Comment thread packages/diagnostics-core/PROTOCOL.md Outdated
Comment thread packages/shared-internals/src/client/sync/stream/core-instruction.ts Outdated
@bean1352
bean1352 added this pull request to stack #1110 September 17, 2026 10:58
@khawarizmus
khawarizmus marked this pull request as ready for review September 22, 2026 10:46
Comment thread tools/diagnostics-ui/src/composables/theme.ts
Comment thread packages/diagnostics-core/src/shapes.ts Outdated
Comment thread packages/diagnostics-core/src/shapes.ts Outdated
Comment thread packages/diagnostics-core/src/integration.ts Outdated
Comment thread tools/diagnostics-core/src/integration.ts
Comment thread packages/diagnostics/README.md Outdated
Comment thread packages/diagnostics/README.md Outdated
Comment thread packages/diagnostics/README.md Outdated
Comment thread tools/diagnostics/README.md
Comment thread packages/diagnostics/README.md Outdated
…lient implementations

# Conflicts:
#	pnpm-lock.yaml

# Conflicts:
#	pnpm-lock.yaml
…ream, UI components, and schema inspection features
…t and enhance connection handling in extension
…ostMessageTransport, enhance package metadata, and add Chrome types
…play and integrate virtual scrolling for upload queue
…t detail component, and improved data handling features
Add dependency-free `shapes.ts` and `integration.ts` as the tool-owned
protocol; move PROTOCOL.md into diagnostics-core and rewrite it to
interface + shapes.

@simolus3 simolus3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not review UI package code, I only tested it in the node demo and by integrating it into the Dart SDK. I reviewed SDK changes and the diagnostics-core package in detail, those look good to me.

I think having a vite web demo somewhere in this repository would be helpful to try this with a real web SDK. Perhaps example-vite which already uses a recent vite version? Some of our other demos use very old dependencies which might be annoying to upgrade.

I assume there's currently no way to surface diagnostics events in the node SDK, right? I think that's okay for now, but it should be documented somewhere.

Comment on lines +58 to +63
/**
* 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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This documentatation comment should probably mention that these diagnostics are only used internally, and exposed in via devtools? We could also add a link to devtools documentation once we have it.

Otherwise users might expect these to show up in public APIs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was addressed

Comment thread packages/web/src/devtools/registry.ts Outdated
* exist, so it adds nothing to a production bundle beyond the references themselves.
*/
const databases = new Set<WebPowerSyncDatabase>();
const listeners = new Set<(databases: readonly WebPowerSyncDatabase[]) => void>();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To save a tiny bit of duplicate code, we could use a BaseObserver singleton for this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is done

});
// PowerSync DevTools: a live view of this client in the browser. Development only.
if (process.env.NODE_ENV !== 'production') {
const devtools = await enablePowerSyncDiagnostics(db);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me, this needed a db as any because connector and connectionOptions are (deliberately!) not part of the public CommonPowerSyncDatabase interface.

Can we have createIntegration and enablePowerSyncDiagnostics take a generic CommonPowerSyncDatabase and cast to BasePowerSyncDatabase internally?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was addressed

}

/** Maps a numeric SDK log level to its name. */
function logLevelName(level: number): string {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should import LogLevels from @powersync/common instead of hardcoding them.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was addressed

* Serves the app's first live database to any iframe that asks. Call once per page.
* Returns a function that stops serving.
*/
export function serveDiagnostics(): () => void {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't figure out ift this is still used, it looks like nothing imports it anymore? Has this been replaced by client.ts which is what the vite plugin injects?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still used by Nuxt, the module loads it for Nuxt DevTools 3, which has no devframe hub (packages/nuxt/src/runtime/plugin.client.ts imports @powersync/diagnostics/page).

if (import.meta.dev && moduleOptions.useDiagnostics && moduleOptions.diagnosticsTransport === 'page') {
    import('@powersync/diagnostics/page').catch((error) => {
      console.error('[powersync-diagnostics] failed to load the diagnostics client', error);
    });
  }

@khawarizmus

Copy link
Copy Markdown
Contributor Author

I think having a vite web demo somewhere in this repository would be helpful to try this with a real web SDK. Perhaps example-vite which already uses a recent vite version? Some of our other demos use very old dependencies which might be annoying to upgrade.

I have added an example in the react-supabase-todolist demo. I brought it on par with the Nuxt demo in terms of setup.

I assume there's currently no way to surface diagnostics events in the Node SDK, right? I think that's okay for now, but it should be documented somewhere.

We decided to add the diagnostics event functionality to Node instead of suppressing it.

I have also moved the packages to tools/ folder

…oduction installs without dev dependencies start

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Product Visibility This requires documentation changes and or announcing.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants