From db1ecce418b8bf92a5eee745bcfceb6e0f8f112d Mon Sep 17 00:00:00 2001 From: Amine Date: Thu, 11 Dec 2025 00:28:55 +0800 Subject: [PATCH 1/5] feat: added nuxt module --- .gitignore | 1 + .nvmrc | 2 +- packages/nuxt/README | 667 + packages/nuxt/package.json | 81 + packages/nuxt/src/devtools.ts | 22 + packages/nuxt/src/module.ts | 137 + .../components/BucketsInspectorTab.vue | 707 + .../runtime/components/ConfigInspectorTab.vue | 175 + .../runtime/components/DataInspectorTab.vue | 857 + .../nuxt/src/runtime/components/LogsTab.vue | 434 + .../components/PowerSyncInstanceTab.vue | 9 + .../src/runtime/components/SyncStatusTab.vue | 278 + .../composables/useDiagnosticsLogger.ts | 49 + .../composables/usePowerSyncInspector.ts | 24 + .../usePowerSyncInspectorDiagnostics.ts | 337 + .../runtime/composables/usePowerSyncKysely.ts | 10 + packages/nuxt/src/runtime/index.d.ts | 9 + .../layouts/powersync-inspector-layout.vue | 56 + .../runtime/pages/__powersync-inspector.vue | 182 + packages/nuxt/src/runtime/plugin.client.ts | 18 + packages/nuxt/src/runtime/utils/AppSchema.ts | 27 + .../src/runtime/utils/DynamicSchemaManager.ts | 116 + .../src/runtime/utils/JsSchemaGenerator.ts | 39 + .../runtime/utils/NuxtPowerSyncDatabase.ts | 131 + .../runtime/utils/RecordingStorageAdapter.ts | 99 + .../runtime/utils/RustClientInterceptor.ts | 141 + .../nuxt/src/runtime/utils/TokenConnector.ts | 94 + .../nuxt/src/runtime/utils/addImportsFrom.ts | 5 + packages/nuxt/src/runtime/utils/index.ts | 1 + packages/nuxt/tsconfig.json | 5 + pnpm-lock.yaml | 22445 ++++++++++------ scripts/reset-node-modules.ts | 83 + 32 files changed, 19243 insertions(+), 7998 deletions(-) create mode 100644 packages/nuxt/README create mode 100644 packages/nuxt/package.json create mode 100644 packages/nuxt/src/devtools.ts create mode 100644 packages/nuxt/src/module.ts create mode 100644 packages/nuxt/src/runtime/components/BucketsInspectorTab.vue create mode 100644 packages/nuxt/src/runtime/components/ConfigInspectorTab.vue create mode 100644 packages/nuxt/src/runtime/components/DataInspectorTab.vue create mode 100644 packages/nuxt/src/runtime/components/LogsTab.vue create mode 100644 packages/nuxt/src/runtime/components/PowerSyncInstanceTab.vue create mode 100644 packages/nuxt/src/runtime/components/SyncStatusTab.vue create mode 100644 packages/nuxt/src/runtime/composables/useDiagnosticsLogger.ts create mode 100644 packages/nuxt/src/runtime/composables/usePowerSyncInspector.ts create mode 100644 packages/nuxt/src/runtime/composables/usePowerSyncInspectorDiagnostics.ts create mode 100644 packages/nuxt/src/runtime/composables/usePowerSyncKysely.ts create mode 100644 packages/nuxt/src/runtime/index.d.ts create mode 100644 packages/nuxt/src/runtime/layouts/powersync-inspector-layout.vue create mode 100644 packages/nuxt/src/runtime/pages/__powersync-inspector.vue create mode 100644 packages/nuxt/src/runtime/plugin.client.ts create mode 100644 packages/nuxt/src/runtime/utils/AppSchema.ts create mode 100644 packages/nuxt/src/runtime/utils/DynamicSchemaManager.ts create mode 100644 packages/nuxt/src/runtime/utils/JsSchemaGenerator.ts create mode 100644 packages/nuxt/src/runtime/utils/NuxtPowerSyncDatabase.ts create mode 100644 packages/nuxt/src/runtime/utils/RecordingStorageAdapter.ts create mode 100644 packages/nuxt/src/runtime/utils/RustClientInterceptor.ts create mode 100644 packages/nuxt/src/runtime/utils/TokenConnector.ts create mode 100644 packages/nuxt/src/runtime/utils/addImportsFrom.ts create mode 100644 packages/nuxt/src/runtime/utils/index.ts create mode 100644 packages/nuxt/tsconfig.json create mode 100644 scripts/reset-node-modules.ts diff --git a/.gitignore b/.gitignore index cbf8f9efd..7929470cf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ node_modules lib dist +.nuxt *.tsbuildinfo .vscode .DS_STORE diff --git a/.nvmrc b/.nvmrc index 805b5a4e0..9a2a0e219 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v20.9.0 +v20 diff --git a/packages/nuxt/README b/packages/nuxt/README new file mode 100644 index 000000000..1f7b1edde --- /dev/null +++ b/packages/nuxt/README @@ -0,0 +1,667 @@ + + +
+ PowerSync Logo +

PowerSync Nuxt

+

Local-first apps made simple

+

Effortless offline-first development with PowerSync integration for Nuxt applications.

+
+ +[![npm version][npm-version-src]][npm-version-href] +[![npm downloads][npm-downloads-src]][npm-downloads-href] +[![License][license-src]][license-href] +[![Nuxt][nuxt-src]][nuxt-href] + +PowerSync Nuxt module integrated with the [Nuxt Devtools](https://github.com/nuxt/devtools). + +- [✨  Release Notes](/CHANGELOG.md) + +## Features + +- 🔍 **Built-in Diagnostics** - Direct access to PowerSync instance monitoring and real-time connection insights +- 🗄️ **Data Inspection** - Seamless local data browsing with powerful debugging and troubleshooting tools +- ⚡ **Useful Composables** - Ready-to-use Vue composables for rapid offline-first application development +- 📦 **All-in-One** - Exposes all `@powersync/vue` composables, making this the only required dependency + +## Installation + +This module exposes all `@powersync/vue` composables, so you only need to install `@powersync/nuxt`: + +```bash +# Using pnpm +pnpm add -D @powersync/nuxt vite-plugin-top-level-await vite-plugin-wasm + +# Using yarn +yarn add --dev @powersync/nuxt vite-plugin-top-level-await vite-plugin-wasm + +# Using npm +npm install --save-dev @powersync/nuxt vite-plugin-top-level-await vite-plugin-wasm +``` +> [!NOTE] +> This module works with `Nuxt 4` and should work with `Nuxt 3` but has not been tested. Support for Nuxt 2 is not guaranteed or planned. + +## Quick Start + +1. Add `@powersync/nuxt` to the `modules` section of `nuxt.config.ts`: + +```typescript +import wasm from 'vite-plugin-wasm' +import topLevelAwait from 'vite-plugin-top-level-await' + +export default defineNuxtConfig({ + modules: ['@powersync/nuxt'], + vite: { + plugins: [topLevelAwait()], + optimizeDeps: { + exclude: ['@journeyapps/wa-sqlite', '@powersync/web'], + include: ['@powersync/web > js-logger'], // <-- Include `js-logger` when it isn't installed and imported. + }, + worker: { + format: 'es', + plugins: () => [wasm(), topLevelAwait()], + }, + }, +}) +``` + +> [!WARNING] +> If you are using Tailwind in your project see [Known Issues section](#known-issues) + +2. Create a PowerSync plugin (e.g., `plugins/powersync.client.ts`): + +```typescript +import { NuxtPowerSyncDatabase } from '@powersync/nuxt' +import { createPowerSyncPlugin } from '@powersync/nuxt' +import { AppSchema } from '~/powersync/AppSchema' +import { PowerSyncConnector } from '~/powersync/PowerSyncConnector' + +export default defineNuxtPlugin({ + async setup(nuxtApp) { + const db = new NuxtPowerSyncDatabase({ + database: { + dbFilename: 'your-db-filename.sqlite', + }, + schema: AppSchema, + }) + + const connector = new PowerSyncConnector() + + await db.init() + await db.connect(connector) + + const plugin = createPowerSyncPlugin({ database: db }) + nuxtApp.vueApp.use(plugin) + }, +}) +``` + +At this point, you're all set to use the module composables. The module automatically exposes all `@powersync/vue` composables, so you can use them directly: + +- `usePowerSync()` - Access the PowerSync database instance +- `usePowerSyncQuery()` - Query the database reactively +- `usePowerSyncStatus()` - Monitor sync status +- `usePowerSyncWatchedQuery()` - Watch queries with automatic updates +- And more... (see [API Reference](#api-reference)) + +## Setting up PowerSync + +This guide will walk you through the steps to set up PowerSync in your Nuxt project. + +### Create your Schema + +Create a file called `AppSchema.ts` and add your schema to it. + +```typescript +import { column, Schema, Table } from '@powersync/web' + +const lists = new Table({ + created_at: column.text, + name: column.text, + owner_id: column.text, +}) + +const todos = new Table( + { + list_id: column.text, + created_at: column.text, + completed_at: column.text, + description: column.text, + created_by: column.text, + completed_by: column.text, + completed: column.integer, + }, + { indexes: { list: ['list_id'] } }, +) + +export const AppSchema = new Schema({ + todos, + lists, +}) + +// For types +export type Database = (typeof AppSchema)['types'] +export type TodoRecord = Database['todos'] +export type ListRecord = Database['lists'] +``` + +> **Tip**: Learn more about how to create your schema [here](https://docs.powersync.com/client-sdk-references/javascript-web#1-define-the-schema). + +### Create your Connector + +Create a file called `PowerSyncConnector.ts` and add your connector to it. + +```typescript +import { UpdateType, type PowerSyncBackendConnector } from '@powersync/web' + +export class PowerSyncConnector implements PowerSyncBackendConnector { + async fetchCredentials() { + // Implement fetchCredentials to obtain a JWT from your authentication service. + // See https://docs.powersync.com/installation/authentication-setup + // If you're using Supabase or Firebase, you can re-use the JWT from those clients, see + // - https://docs.powersync.com/installation/authentication-setup/supabase-auth + // - https://docs.powersync.com/installation/authentication-setup/firebase-auth + return { + endpoint: '[Your PowerSync instance URL or self-hosted endpoint]', + // Use a development token (see Authentication Setup https://docs.powersync.com/installation/authentication-setup/development-tokens) to get up and running quickly + token: 'An authentication token', + } + } + + async uploadData(db: any) { + // Implement uploadData to send local changes to your backend service. + // You can omit this method if you only want to sync data from the database to the client + + // See example implementation here: https://docs.powersync.com/client-sdk-references/javascript-web#3-integrate-with-your-backend + // see demos here: https://github.com/powersync-ja/powersync-js/tree/main/demos + return + } +} +``` + +> **Tip**: Learn more about how to create your connector [here](https://docs.powersync.com/client-sdk-references/javascript-web#3-integrate-with-your-backend). + +### Create your PowerSync Plugin + +Finally, putting everything together, create a [plugin](https://nuxt.com/docs/4.x/guide/directory-structure/app/plugins) called `powersync.client.ts` to setup PowerSync. + +```typescript +import { createPowerSyncPlugin } from '@powersync/nuxt' +import { NuxtPowerSyncDatabase } from '@powersync/nuxt' +import { AppSchema } from '~/powersync/AppSchema' +import { PowerSyncConnector } from '~/powersync/PowerSyncConnector' + +export default defineNuxtPlugin({ + async setup(nuxtApp) { + const db = new NuxtPowerSyncDatabase({ + database: { + dbFilename: 'a-db-name.sqlite', + }, + schema: AppSchema, + }) + + const connector = new PowerSyncConnector() + + await db.init() + await db.connect(connector) + + const plugin = createPowerSyncPlugin({ database: db }) + + nuxtApp.vueApp.use(plugin) + }, +}) +``` + +### Kysely ORM (Optional) + +You can use Kysely as your ORM to interact with the database. The module provides a `usePowerSyncKysely()` composable: + +```typescript +import { usePowerSyncKysely } from '@powersync/nuxt' +import { type Database } from '../powersync/AppSchema' + +// In your component or composable +const db = usePowerSyncKysely() + +// Use the db object to interact with the database +const users = await db.selectFrom('users').selectAll().execute() +``` + +### Enabling Diagnostics + +To enable the PowerSync Inspector with diagnostics capabilities: + +1. **Enable diagnostics in your config**: + +```typescript +export default defineNuxtConfig({ + modules: ['@powersync/nuxt'], + powersync: { + useDiagnostics: true, // <- Add this + }, + vite: { + plugins: [topLevelAwait()], + optimizeDeps: { + exclude: ['@journeyapps/wa-sqlite', '@powersync/web'], + include: ['@powersync/web > js-logger'], + }, + worker: { + format: 'es', + plugins: () => [wasm(), topLevelAwait()], + }, + }, +}) +``` + +When `useDiagnostics: true` is set, `NuxtPowerSyncDatabase` automatically: +- Sets up diagnostics recording +- Stores the connector internally (accessible via diagnostics) +- Configures logging for diagnostics + +2. **Extend your schema**: + +If you're using diagnostics, you need to extend your schema with the diagnostics schema to collect diagnostics data: + +```typescript +import { usePowerSyncInspector } from '@powersync/nuxt' +import { Schema } from '@powersync/web' + +const { diagnosticsSchema } = usePowerSyncInspector() + +// Combine with your app schema +const combinedSchema = new Schema([ + ...yourSchema.tables, + ...diagnosticsSchema.tables, +]) +``` + +3. **Accessing PowerSync Inspector**: + +Once diagnostics are enabled, you can access the [PowerSync Inspector](#powersync-inspector): + +- **Direct URL**: `http://localhost:3000/__powersync-inspector` +- **Via Nuxt Devtools**: Open Devtools and look for the PowerSync tab (Instable until proper multitab support for diagnostics in implemented) + + + +## PowerSync Inspector + +PowerSync Inspector is a tool that helps inspect and diagnose the state of your PowerSync client directly from your app in real-time. + +### Setup + +To setup the PowerSync inspector, you need to follow the steps in the [Enabling Diagnostics](#enabling-diagnostics) section. + +Once setup, the inspector can be accessed on the `http://localhost:3000/__powersync-inspector` route or via the [Nuxt Devtools](#nuxt-devtools). + +### Features + +#### Sync Status + +The `Sync Status` tab provides a real-time view of the sync status of your PowerSync client, including: +- Connection status +- Sync progress +- Upload queue statistics +- Error monitoring + +#### Data Inspector + +Browse and inspect your local database tables and data with powerful filtering and search capabilities. + +#### Config Inspector + +View and inspect your PowerSync configuration, connection options, and schema information. + +#### Logs + +Real-time logging of PowerSync operations with syntax highlighting and search functionality. + +#### Nuxt Devtools + +The inspector is also available in the Nuxt Devtools as a tab, providing seamless integration with your development workflow. + +> [!WARNING] +> Multitab support is still not fully supported when diagnostics are enabled which causes the inspector to malfunction in the devtool. see [Known Issues section](#known-issues) + + +## API Reference + +### Module Options + +#### `useDiagnostics` + +Enable diagnostics and the PowerSync Inspector. + +- **Type**: `boolean` +- **Default**: `false` +- **Description**: When set to `true`, enables diagnostics recording and makes the PowerSync Inspector available. + +```typescript +export default defineNuxtConfig({ + modules: ['@powersync/nuxt'], + powersync: { + useDiagnostics: true, + }, +}) +``` + +### PowerSync Vue Composables + +This module automatically exposes all composables from `@powersync/vue`, so you don't need to install `@powersync/vue` separately: + +- `createPowerSyncPlugin(options)` - Create the PowerSync Vue plugin +- `providePowerSync(database)` - Provide PowerSync database to the app +- `usePowerSync()` - Access the PowerSync database instance +- `usePowerSyncQuery(query, params?)` - Query the database reactively +- `usePowerSyncStatus()` - Monitor sync status +- `usePowerSyncWatchedQuery(query, params?)` - Watch queries with automatic updates +- `useQuery(query, params?)` - Query helper +- `useStatus()` - Status helper +- `useWatchedQuerySubscription(query, params?)` - Watch query subscription helper + +All of these composables are available globally in your Nuxt app - no imports needed! + +### Classes + +#### `NuxtPowerSyncDatabase` + +An extended PowerSync database class that includes diagnostic capabilities for use with the PowerSync Inspector. + +**Usage**: + +```typescript +import { NuxtPowerSyncDatabase } from '@powersync/nuxt' + +const db = new NuxtPowerSyncDatabase({ + database: { + dbFilename: 'your-db-filename.sqlite', + }, + schema: yourSchema, +}) +``` + +**Features**: + +- **Automatic Diagnostics**: When `useDiagnostics: true` is set in module config, automatically enables diagnostics recording +- **Connector Storage**: Stores connector internally for inspector access +- **Enhanced VFS**: Uses cooperative sync VFS for improved compatibility +- **Schema Management**: Integrates with dynamic schema management for inspector features +- **Logging**: Automatically configures logging when diagnostics are enabled + +**Note**: The class works with or without diagnostics enabled. When diagnostics are disabled, it behaves like a standard `PowerSyncDatabase`. + +### Module Composables + +#### `usePowerSyncKysely()` + +Provides a Kysely-wrapped PowerSync database for type-safe database queries. + +**Type Parameters**: +- `T` - Your database type (from your schema) + +**Returns**: Kysely database instance (not `{ db }`) + +**Usage**: + +```typescript +import { usePowerSyncKysely } from '@powersync/nuxt' +import { type Database } from '../powersync/AppSchema' + +// Returns db directly, not { db } +const db = usePowerSyncKysely() + +// Use Kysely query builder +const users = await db.selectFrom('users').selectAll().execute() +``` + +#### `useDiagnosticsLogger()` + +Provides a logger configured for PowerSync diagnostics. + +**Returns**: + +```typescript +{ + logger: ILogHandler + logsStorage: Storage + emitter: Emitter +} +``` + +**Usage**: + +```typescript +const { logger } = useDiagnosticsLogger() + +// Logger is automatically configured for diagnostics +// Use it in your PowerSync setup if needed +``` + + +#### `usePowerSyncInspector()` + +A composable for setting up PowerSync Inspector functionality. This composable provides utilities for schema management and diagnostics setup. + +**Returns**: + +```typescript +{ + diagnosticsSchema: Schema + RecordingStorageAdapter: Class + getCurrentSchemaManager: Function +} +``` + +**Properties**: + +- **`diagnosticsSchema`** - The schema for diagnostics data collection. Use this to extend your app schema with diagnostic tables. +- **`RecordingStorageAdapter`** - Used internally. Storage adapter class that records operations for diagnostic purposes. +- **`getCurrentSchemaManager()`** - Used internally. Gets the current schema manager instance for dynamic schema operations. + +**Usage**: + +```typescript +const { diagnosticsSchema } = usePowerSyncInspector() + +// Combine with your app schema +const combinedSchema = new Schema([ + ...yourAppSchema.tables, + ...diagnosticsSchema.tables, +]) +``` + +#### `usePowerSyncInspectorDiagnostics()` + +A comprehensive composable that provides real-time diagnostics data and sync status monitoring for your PowerSync client and local database. This composable can be used to create your own inspector. + +**Returns**: + +```typescript +{ + // Database & Connection + db: Ref + connector: ComputedRef + connectionOptions: ComputedRef + isDiagnosticSchemaSetup: Readonly> + + // Sync Status + syncStatus: Readonly> + hasSynced: Readonly> + isConnected: Readonly> + isSyncing: Readonly> + isDownloading: Readonly> + isUploading: Readonly> + lastSyncedAt: Readonly> + + // Progress & Statistics + totalDownloadProgress: Readonly> + uploadQueueStats: Readonly> + uploadQueueCount: Readonly> + uploadQueueSize: Readonly> + bucketRows: Readonly> + tableRows: Readonly> + totals: Readonly> + + // Error Handling + downloadError: Readonly> + uploadError: Readonly> + downloadProgressDetails: Readonly> + + // User Info + userID: Readonly> + + // Utilities + clearData: Function + formatBytes: Function +} +``` + +**Reactive Properties**: + +- **Connection Status**: `isConnected`, `hasSynced`, `isSyncing`, `isDownloading`, `isUploading`, `lastSyncedAt` +- **Progress Tracking**: `totalDownloadProgress`, `uploadQueueStats`, `uploadQueueCount`, `uploadQueueSize`, `downloadProgressDetails` +- **Data Inspection**: `bucketRows`, `tableRows`, `totals` +- **Error Monitoring**: `downloadError`, `uploadError` +- **Authentication**: `userID` + +**Methods**: + +- **`clearData()`** - Disconnects and clears all local PowerSync data, then reconnects. Useful for resetting the sync state during development or troubleshooting. +- **`formatBytes(bytes, decimals?)`** - Formats byte counts into readable file sizes (e.g., "1.5 MiB"). Default decimals is 2. + +**Usage Examples**: + +```vue + + + +``` + +## Known Issues + +1. Enabling diagnostics makes your app glitch when operating in a multi-tab environment. You can observe this issue when you open the inspector in the devtools, for example. + +2. PowerSync Inspector relies on `unocss` as a transitive dependency. It might clash with your existing setup, for example if you use Tailwind CSS. + +To fix this, you can add the following to your `nuxt.config.ts`: + +```typescript +export default defineNuxtConfig({ + unocss: { + icons: true, + blocklist: [/\$\{.*\}/], + content: { + pipeline: { + exclude: [ + './layouts/*/**', + './pages/*/**', + './components/*/**', + './composables/*/**', + './utils/*/**', + './types/*/**', + ], + }, + }, + }, +}) +``` + +## Development + +```bash +# Install dependencies +npm install + +# Generate type stubs +npm run dev:prepare + +# Develop with playground, with devtools client ui +npm run dev + +# Develop with playground, with bundled client ui +npm run play:prod + +# Run ESLint +npm run lint + +# Run Vitest +npm run test +npm run test:watch + +# Release new version +npm run release +``` + +## Local Testing + +If the playground is not enough for you, you can test the module locally by cloning this repo and pointing the nuxt app you want to test to the local module. + +Don't forget to add a watcher for the module for hot reloading. + +Example (in your nuxt app): + +```typescript +import { defineNuxtConfig } from 'nuxt/config' + +export default defineNuxtConfig({ + modules: ['../../my-location/@powersync/nuxt/src/*'], + watch: ['../../my-location/@powersync/nuxt/src/*'], +}) +``` + + +[npm-version-src]: https://img.shields.io/npm/v/@powersync/nuxt/latest.svg?style=flat&colorA=18181B&colorB=28CF8D +[npm-version-href]: https://npmjs.com/package/@powersync/nuxt + +[npm-downloads-src]: https://img.shields.io/npm/dm/@powersync/nuxt.svg?style=flat&colorA=18181B&colorB=28CF8D +[npm-downloads-href]: https://npmjs.com/package/@powersync/nuxt + +[license-src]: https://img.shields.io/npm/l/@powersync/nuxt.svg?style=flat&colorA=18181B&colorB=28CF8D +[license-href]: https://npmjs.com/package/@powersync/nuxt + +[nuxt-src]: https://img.shields.io/badge/Nuxt-18181B?logo=nuxt.js +[nuxt-href]: https://nuxt.com diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json new file mode 100644 index 000000000..d92ce5365 --- /dev/null +++ b/packages/nuxt/package.json @@ -0,0 +1,81 @@ +{ + "name": "@powersync/nuxt", + "version": "0.1.0", + "publishConfig": { + "registry": "https://registry.npmjs.org/", + "access": "public" + }, + "description": "PowerSync Nuxt module", + "license": "Apache-2.0", + "type": "module", + "repository": { + "type": "git", + "url": "git+https://github.com/powersync-ja/powersync-js.git" + }, + "author": "JOURNEYAPPS", + "bugs": { + "url": "https://github.com/powersync-ja/powersync-js/issues" + }, + "exports": { + ".": { + "types": "./dist/types.d.mts", + "import": "./dist/module.mjs" + } + }, + "main": "./dist/module.mjs", + "typesVersions": { + "*": { + ".": ["./dist/types.d.mts"] + } + }, + "files": ["dist"], + "scripts": { + "prebuild": "nuxt-module-build prepare", + "build": "nuxt-module-build build", + "build:prod": "nuxt-module-build build", + "clean": "rm -rf dist .nuxt", + "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare", + "watch": "nuxt-module-build build --watch", + "test": "vitest run", + "test:watch": "vitest watch", + "test:exports": "attw --pack --profile=esm-only ." + }, + "homepage": "https://docs.powersync.com", + "dependencies": { + "@iconify-json/carbon": "^1.2.13", + "@nuxt/devtools-kit": "^2.6.2", + "@nuxt/devtools-ui-kit": "^2.6.2", + "@nuxt/kit": "^4.0.3", + "@tanstack/vue-table": "^8.21.3", + "@vueuse/nuxt": "^13.9.0", + "consola": "^3.4.2", + "defu": "^6.1.4", + "fuse.js": "^7.1.0", + "mitt": "^3.0.1", + "reka-ui": "^2.5.0", + "shiki": "^3.13.0", + "unocss": "^66.5.2", + "unstorage": "^1.17.1" + }, + "peerDependencies": { + "@journeyapps/wa-sqlite": "^1.2.6", + "@powersync/kysely-driver": "workspace:*", + "@powersync/vue": "workspace:*", + "@powersync/web": "workspace:*" + }, + "devDependencies": { + "@journeyapps/wa-sqlite": "^1.4.0", + "@nuxt/module-builder": "^1.0.2", + "@nuxt/schema": "^4.1.2", + "@nuxt/test-utils": "^3.19.2", + "@powersync/kysely-driver": "workspace:*", + "@powersync/vue": "workspace:*", + "@powersync/web": "workspace:*", + "nuxt": "^4.1.2", + "vite-plugin-top-level-await": "^1.6.0", + "vite-plugin-wasm": "^3.5.0", + "vitest": "^3.2.4", + "vue": "^3.5.20", + "vue-tsc": "^3.0.8" + } + } \ No newline at end of file diff --git a/packages/nuxt/src/devtools.ts b/packages/nuxt/src/devtools.ts new file mode 100644 index 000000000..984a4b447 --- /dev/null +++ b/packages/nuxt/src/devtools.ts @@ -0,0 +1,22 @@ +import type { Nuxt } from 'nuxt/schema' + +export function setupDevToolsUI(nuxt: Nuxt) { + const port = nuxt.options.devServer?.port || 3000 + const DEVTOOLS_UI_ROUTE = `http://localhost:${port}/__powersync-inspector` + + nuxt.hook('devtools:customTabs', (tabs) => { + tabs.push({ + // unique identifier + name: 'powersync-inspector', + // title to display in the tab + title: 'Powersync Inspector', + // any icon from Iconify, or a URL to an image + icon: 'https://cdn.prod.website-files.com/67eea61902e19994e7054ea0/67f910109a12edc930f8ffb6_powersync-icon.svg', + // iframe view + view: { + type: 'iframe', + src: DEVTOOLS_UI_ROUTE, + }, + }) + }) +} diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts new file mode 100644 index 000000000..d331f973d --- /dev/null +++ b/packages/nuxt/src/module.ts @@ -0,0 +1,137 @@ +import { + defineNuxtModule, + createResolver, + addPlugin, + addImports, + extendPages, + // installModule, + addLayout, + addComponentsDir, + installModule, +} from '@nuxt/kit' +import { defu } from 'defu' +import { setupDevToolsUI } from './devtools' +import { addImportsFrom } from './runtime/utils/addImportsFrom' + +type JSONValue + = | string + | number + | boolean + | null + | undefined + | JSONObject + | JSONArray +interface JSONObject { + [key: string]: JSONValue +} +type JSONArray = JSONValue[] + +// Module options TypeScript interface definition +export interface PowerSyncNuxtModuleOptions { + /** + * enable diagnostics + * + * @default "false" + */ + useDiagnostics?: boolean +} + +export default defineNuxtModule({ + meta: { + name: 'powersync-nuxt', + configKey: 'powersync', + }, + // Default configuration options of the Nuxt module + defaults: { + useDiagnostics: false, + }, + async setup(options, nuxt) { + const resolver = createResolver(import.meta.url) + + nuxt.options.runtimeConfig.public.powerSyncModuleOptions = defu( + + nuxt.options.runtimeConfig.public.powerSyncModuleOptions as any, + { + useDiagnostics: options.useDiagnostics, + }, + ) + + await installModule('@nuxt/devtools-ui-kit') + await installModule('@vueuse/nuxt') + + addPlugin(resolver.resolve('./runtime/plugin.client')) + + // expose the composables + addImports({ + name: 'NuxtPowerSyncDatabase', + from: resolver.resolve( + './runtime/utils/NuxtPowerSyncDatabase', + ), + }) + + addImports({ + name: 'usePowerSyncInspector', + from: resolver.resolve('./runtime/composables/usePowerSyncInspector'), + }) + + addImports({ + name: 'usePowerSyncInspectorDiagnostics', + from: resolver.resolve( + './runtime/composables/usePowerSyncInspectorDiagnostics', + ), + }) + + addImports({ + name: 'usePowerSyncKysely', + from: resolver.resolve('./runtime/composables/usePowerSyncKysely'), + }) + + addImports({ + name: 'useDiagnosticsLogger', + from: resolver.resolve('./runtime/composables/useDiagnosticsLogger'), + }) + + // From the runtime directory + addComponentsDir({ + path: resolver.resolve('runtime/components'), + }) + + addLayout( + resolver.resolve('./runtime/layouts/powersync-inspector-layout.vue'), + 'powersync-inspector-layout', + ) + + extendPages((pages) => { + pages.push({ + path: '/__powersync-inspector', + // file: resolver.resolve("#build/pages/__powersync-inspector.vue"), + file: resolver.resolve('./runtime/pages/__powersync-inspector.vue'), + name: 'Powersync Inspector', + }) + }) + + addImportsFrom([ + 'createPowerSyncPlugin', + 'providePowerSync', + 'usePowerSync', + 'usePowerSyncQuery', + 'usePowerSyncStatus', + 'usePowerSyncWatchedQuery', + 'useQuery', + 'useStatus', + 'useWatchedQuerySubscription', + ], '@powersync/vue') + + // Ensure the packages are transpiled + nuxt.options.build.transpile = nuxt.options.build.transpile || [] + nuxt.options.build.transpile.push('reka-ui', '@tanstack/vue-table', '@powersync/web', '@powersync/kysely-driver', '@journeyapps/wa-sqlite') + + nuxt.hooks.hook('prepare:types', ({ references }: { references: any[] }) => { + references.push({ types: '@powersync/web' }) + references.push({ types: '@powersync/kysely-driver' }) + references.push({ types: '@journeyapps/wa-sqlite' }) + }) + + setupDevToolsUI(nuxt) + }, +}) diff --git a/packages/nuxt/src/runtime/components/BucketsInspectorTab.vue b/packages/nuxt/src/runtime/components/BucketsInspectorTab.vue new file mode 100644 index 000000000..893393128 --- /dev/null +++ b/packages/nuxt/src/runtime/components/BucketsInspectorTab.vue @@ -0,0 +1,707 @@ + + + + + diff --git a/packages/nuxt/src/runtime/components/ConfigInspectorTab.vue b/packages/nuxt/src/runtime/components/ConfigInspectorTab.vue new file mode 100644 index 000000000..39d3b725b --- /dev/null +++ b/packages/nuxt/src/runtime/components/ConfigInspectorTab.vue @@ -0,0 +1,175 @@ + + + + + diff --git a/packages/nuxt/src/runtime/components/DataInspectorTab.vue b/packages/nuxt/src/runtime/components/DataInspectorTab.vue new file mode 100644 index 000000000..4f356c5fd --- /dev/null +++ b/packages/nuxt/src/runtime/components/DataInspectorTab.vue @@ -0,0 +1,857 @@ +