From 69afd2983c46ee56f75be1c645dd7b7b16672b7f Mon Sep 17 00:00:00 2001 From: Dev Agrawal Date: Thu, 12 Mar 2026 23:26:57 -0500 Subject: [PATCH 1/2] feat(create): add React PowerSync scaffolding add-on --- .../react/add-ons/powersync/README.md.ejs | 26 ++++ .../powersync/assets/_dot_env.local.append | 3 + .../powersync/assets/powersync-vite-plugin.ts | 17 +++ .../src/integrations/powersync/provider.tsx | 26 ++++ .../assets/src/lib/powersync/AppSchema.ts | 17 +++ .../src/lib/powersync/BackendConnector.ts | 52 ++++++++ .../assets/src/routes/demo/powersync.tsx | 113 ++++++++++++++++++ .../react/add-ons/powersync/info.json | 47 ++++++++ .../react/add-ons/powersync/package.json.ejs | 7 ++ .../react/add-ons/powersync/small-logo.svg | 6 + 10 files changed, 314 insertions(+) create mode 100644 packages/create/src/frameworks/react/add-ons/powersync/README.md.ejs create mode 100644 packages/create/src/frameworks/react/add-ons/powersync/assets/_dot_env.local.append create mode 100644 packages/create/src/frameworks/react/add-ons/powersync/assets/powersync-vite-plugin.ts create mode 100644 packages/create/src/frameworks/react/add-ons/powersync/assets/src/integrations/powersync/provider.tsx create mode 100644 packages/create/src/frameworks/react/add-ons/powersync/assets/src/lib/powersync/AppSchema.ts create mode 100644 packages/create/src/frameworks/react/add-ons/powersync/assets/src/lib/powersync/BackendConnector.ts create mode 100644 packages/create/src/frameworks/react/add-ons/powersync/assets/src/routes/demo/powersync.tsx create mode 100644 packages/create/src/frameworks/react/add-ons/powersync/info.json create mode 100644 packages/create/src/frameworks/react/add-ons/powersync/package.json.ejs create mode 100644 packages/create/src/frameworks/react/add-ons/powersync/small-logo.svg diff --git a/packages/create/src/frameworks/react/add-ons/powersync/README.md.ejs b/packages/create/src/frameworks/react/add-ons/powersync/README.md.ejs new file mode 100644 index 00000000..2144ac15 --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/powersync/README.md.ejs @@ -0,0 +1,26 @@ +# PowerSync + +This project includes the PowerSync Web SDK and React hooks. + +## Environment + +Set these variables in `.env.local`: + +- `VITE_POWERSYNC_URL` +- `VITE_POWERSYNC_TOKEN` for local development only + +## What The Add-on Includes + +- `src/lib/powersync/AppSchema.ts` +- `src/lib/powersync/BackendConnector.ts` +- `src/integrations/powersync/provider.tsx` +- `src/routes/demo/powersync.tsx` + +## Next Steps + +1. Replace the development token flow in `src/lib/powersync/BackendConnector.ts` with your real auth flow. +2. Update the sample schema in `src/lib/powersync/AppSchema.ts` to match your synced tables. +3. Implement the upload logic in `uploadData()` so local mutations are written back to your backend. + +PowerSync setup guidance: +https://docs.powersync.com/client-sdk-references/js-web diff --git a/packages/create/src/frameworks/react/add-ons/powersync/assets/_dot_env.local.append b/packages/create/src/frameworks/react/add-ons/powersync/assets/_dot_env.local.append new file mode 100644 index 00000000..eb177e04 --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/powersync/assets/_dot_env.local.append @@ -0,0 +1,3 @@ + +VITE_POWERSYNC_URL= +VITE_POWERSYNC_TOKEN= diff --git a/packages/create/src/frameworks/react/add-ons/powersync/assets/powersync-vite-plugin.ts b/packages/create/src/frameworks/react/add-ons/powersync/assets/powersync-vite-plugin.ts new file mode 100644 index 00000000..916b8900 --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/powersync/assets/powersync-vite-plugin.ts @@ -0,0 +1,17 @@ +import type { Plugin } from 'vite' + +export default function powersyncVite(): Plugin { + return { + name: 'powersync-vite', + config() { + return { + optimizeDeps: { + exclude: ['@powersync/web'], + }, + worker: { + format: 'es', + }, + } + }, + } +} diff --git a/packages/create/src/frameworks/react/add-ons/powersync/assets/src/integrations/powersync/provider.tsx b/packages/create/src/frameworks/react/add-ons/powersync/assets/src/integrations/powersync/provider.tsx new file mode 100644 index 00000000..33330fba --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/powersync/assets/src/integrations/powersync/provider.tsx @@ -0,0 +1,26 @@ +import type { ReactNode } from 'react' +import { PowerSyncContext } from '@powersync/react' +import { PowerSyncDatabase, WASQLiteOpenFactory } from '@powersync/web' + +import { AppSchema } from '#/lib/powersync/AppSchema' +import { BackendConnector } from '#/lib/powersync/BackendConnector' + +const db = new PowerSyncDatabase({ + database: new WASQLiteOpenFactory({ + dbFilename: 'powersync.db', + }), + schema: AppSchema, + flags: { + disableSSRWarning: true, + }, +}) + +void db.connect(new BackendConnector()) + +export default function PowerSyncProvider({ + children, +}: { + children: ReactNode +}) { + return {children} +} diff --git a/packages/create/src/frameworks/react/add-ons/powersync/assets/src/lib/powersync/AppSchema.ts b/packages/create/src/frameworks/react/add-ons/powersync/assets/src/lib/powersync/AppSchema.ts new file mode 100644 index 00000000..dee96e4f --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/powersync/assets/src/lib/powersync/AppSchema.ts @@ -0,0 +1,17 @@ +import { Schema, Table, column } from '@powersync/web' + +const todos = new Table( + { + created_at: column.text, + description: column.text, + completed: column.integer, + }, + { indexes: { created_at: ['created_at'] } }, +) + +export const AppSchema = new Schema({ + todos, +}) + +export type Database = (typeof AppSchema)['types'] +export type TodoRecord = Database['todos'] diff --git a/packages/create/src/frameworks/react/add-ons/powersync/assets/src/lib/powersync/BackendConnector.ts b/packages/create/src/frameworks/react/add-ons/powersync/assets/src/lib/powersync/BackendConnector.ts new file mode 100644 index 00000000..a491cfc8 --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/powersync/assets/src/lib/powersync/BackendConnector.ts @@ -0,0 +1,52 @@ +import { + type AbstractPowerSyncDatabase, + type PowerSyncBackendConnector, + UpdateType, +} from '@powersync/web' + +export class BackendConnector implements PowerSyncBackendConnector { + private readonly powersyncUrl = import.meta.env.VITE_POWERSYNC_URL + private readonly powersyncToken = import.meta.env.VITE_POWERSYNC_TOKEN + + async fetchCredentials() { + if (!this.powersyncUrl || !this.powersyncToken) { + return null + } + + return { + endpoint: this.powersyncUrl, + token: this.powersyncToken, + } + } + + async uploadData(database: AbstractPowerSyncDatabase): Promise { + const transaction = await database.getNextCrudTransaction() + + if (!transaction) { + return + } + + try { + for (const op of transaction.crud) { + const record = { ...op.opData, id: op.id } + + switch (op.op) { + case UpdateType.PUT: + console.info('TODO: create record remotely', record) + break + case UpdateType.PATCH: + console.info('TODO: patch record remotely', record) + break + case UpdateType.DELETE: + console.info('TODO: delete record remotely', record) + break + } + } + + await transaction.complete() + } catch (error) { + console.error('PowerSync uploadData failed', error) + await transaction.complete() + } + } +} diff --git a/packages/create/src/frameworks/react/add-ons/powersync/assets/src/routes/demo/powersync.tsx b/packages/create/src/frameworks/react/add-ons/powersync/assets/src/routes/demo/powersync.tsx new file mode 100644 index 00000000..c6376e8f --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/powersync/assets/src/routes/demo/powersync.tsx @@ -0,0 +1,113 @@ +import { useState } from 'react' +import { createFileRoute } from '@tanstack/react-router' +import { usePowerSync, useQuery, useStatus } from '@powersync/react' + +export const Route = createFileRoute('/demo/powersync')({ + component: PowerSyncDemo, +}) + +type TodoRow = { + id: string + created_at: string + description: string + completed: number +} + +function PowerSyncDemo() { + const powerSync = usePowerSync() + const status = useStatus() + const { data } = useQuery( + 'SELECT id, created_at, description, completed FROM todos ORDER BY created_at DESC', + ) + const todos = (data ?? []) as Array + const [description, setDescription] = useState('') + + async function addTodo(event: React.FormEvent) { + event.preventDefault() + + const nextDescription = description.trim() + if (!nextDescription) { + return + } + + await powerSync.execute( + 'INSERT INTO todos (id, created_at, description, completed) VALUES (?, ?, ?, ?)', + [crypto.randomUUID(), new Date().toISOString(), nextDescription, 0], + ) + + setDescription('') + } + + return ( +
+
+
+

+ Offline Sync +

+

PowerSync

+

+ This demo writes to the local SQLite database immediately. Replace the sample + schema and backend connector with your real PowerSync configuration. +

+
+ +
+

+ Connection State +

+
+            {JSON.stringify(status, null, 2)}
+          
+
+ +
+

+ Local Todos +

+
+ setDescription(event.target.value)} + placeholder="Write to the local PowerSync database" + value={description} + /> + +
+ +
    + {todos.length === 0 ? ( +
  • + No rows yet. Insert one locally, then wire `uploadData()` to send it upstream. +
  • + ) : ( + todos.map((todo) => ( +
  • +
    +
    +

    {todo.description}

    +

    + {todo.created_at} +

    +
    + + {todo.completed ? 'done' : 'pending'} + +
    +
  • + )) + )} +
+
+
+
+ ) +} diff --git a/packages/create/src/frameworks/react/add-ons/powersync/info.json b/packages/create/src/frameworks/react/add-ons/powersync/info.json new file mode 100644 index 00000000..573529a2 --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/powersync/info.json @@ -0,0 +1,47 @@ +{ + "name": "PowerSync", + "description": "Add PowerSync offline sync to your application.", + "phase": "add-on", + "type": "add-on", + "category": "database", + "color": "#2563EB", + "priority": 55, + "link": "https://docs.powersync.com/client-sdk-references/js-web", + "modes": ["file-router"], + "envVars": [ + { + "name": "VITE_POWERSYNC_URL", + "description": "PowerSync instance URL", + "required": true, + "file": ".env.local" + }, + { + "name": "VITE_POWERSYNC_TOKEN", + "description": "Development token for local testing", + "required": false, + "secret": true, + "file": ".env.local" + } + ], + "integrations": [ + { + "type": "vite-plugin", + "path": "powersync-vite-plugin.ts", + "jsName": "powersyncVite", + "code": "powersyncVite()" + }, + { + "type": "provider", + "path": "src/integrations/powersync/provider.tsx", + "jsName": "PowerSyncProvider" + } + ], + "routes": [ + { + "url": "/demo/powersync", + "name": "PowerSync", + "path": "src/routes/demo/powersync.tsx", + "jsName": "PowerSyncDemo" + } + ] +} diff --git a/packages/create/src/frameworks/react/add-ons/powersync/package.json.ejs b/packages/create/src/frameworks/react/add-ons/powersync/package.json.ejs new file mode 100644 index 00000000..255a5730 --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/powersync/package.json.ejs @@ -0,0 +1,7 @@ +{ + "dependencies": { + "@journeyapps/wa-sqlite": "^1.2.6", + "@powersync/react": "^1.7.4", + "@powersync/web": "^1.26.1" + } +} diff --git a/packages/create/src/frameworks/react/add-ons/powersync/small-logo.svg b/packages/create/src/frameworks/react/add-ons/powersync/small-logo.svg new file mode 100644 index 00000000..bb7d8271 --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/powersync/small-logo.svg @@ -0,0 +1,6 @@ + + + + + + From 40e64062952ff71da5b55ef47909e90f43026792 Mon Sep 17 00:00:00 2001 From: Dev Agrawal Date: Wed, 1 Apr 2026 19:19:42 -0500 Subject: [PATCH 2/2] fix(create): address PowerSync add-on review feedback --- .../src/lib/powersync/BackendConnector.ts | 2 +- .../assets/src/routes/demo/powersync.tsx | 26 +++++++++++++++---- .../react/add-ons/powersync/info.json | 3 +-- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/packages/create/src/frameworks/react/add-ons/powersync/assets/src/lib/powersync/BackendConnector.ts b/packages/create/src/frameworks/react/add-ons/powersync/assets/src/lib/powersync/BackendConnector.ts index a491cfc8..1d34191a 100644 --- a/packages/create/src/frameworks/react/add-ons/powersync/assets/src/lib/powersync/BackendConnector.ts +++ b/packages/create/src/frameworks/react/add-ons/powersync/assets/src/lib/powersync/BackendConnector.ts @@ -46,7 +46,7 @@ export class BackendConnector implements PowerSyncBackendConnector { await transaction.complete() } catch (error) { console.error('PowerSync uploadData failed', error) - await transaction.complete() + throw error } } } diff --git a/packages/create/src/frameworks/react/add-ons/powersync/assets/src/routes/demo/powersync.tsx b/packages/create/src/frameworks/react/add-ons/powersync/assets/src/routes/demo/powersync.tsx index c6376e8f..797f7c46 100644 --- a/packages/create/src/frameworks/react/add-ons/powersync/assets/src/routes/demo/powersync.tsx +++ b/packages/create/src/frameworks/react/add-ons/powersync/assets/src/routes/demo/powersync.tsx @@ -21,6 +21,7 @@ function PowerSyncDemo() { ) const todos = (data ?? []) as Array const [description, setDescription] = useState('') + const [error, setError] = useState(null) async function addTodo(event: React.FormEvent) { event.preventDefault() @@ -30,12 +31,18 @@ function PowerSyncDemo() { return } - await powerSync.execute( - 'INSERT INTO todos (id, created_at, description, completed) VALUES (?, ?, ?, ?)', - [crypto.randomUUID(), new Date().toISOString(), nextDescription, 0], - ) + try { + setError(null) + await powerSync.execute( + 'INSERT INTO todos (id, created_at, description, completed) VALUES (?, ?, ?, ?)', + [crypto.randomUUID(), new Date().toISOString(), nextDescription, 0], + ) - setDescription('') + setDescription('') + } catch (error) { + console.error('Failed to insert PowerSync todo', error) + setError('Failed to insert row. Please try again.') + } } return ( @@ -65,8 +72,17 @@ function PowerSyncDemo() {

Local Todos

+ {error ? ( +

+ {error} +

+ ) : null}
+ setDescription(event.target.value)} placeholder="Write to the local PowerSync database" diff --git a/packages/create/src/frameworks/react/add-ons/powersync/info.json b/packages/create/src/frameworks/react/add-ons/powersync/info.json index 573529a2..a0995e72 100644 --- a/packages/create/src/frameworks/react/add-ons/powersync/info.json +++ b/packages/create/src/frameworks/react/add-ons/powersync/info.json @@ -17,9 +17,8 @@ }, { "name": "VITE_POWERSYNC_TOKEN", - "description": "Development token for local testing", + "description": "Client-visible development token for local testing; VITE_* env vars are exposed to the browser", "required": false, - "secret": true, "file": ".env.local" } ],