Skip to content

feat(create): add React PowerSync scaffolding add-on#407

Open
devagrawal09 wants to merge 1 commit intoTanStack:mainfrom
devagrawal09:feat/powersync-react-addon
Open

feat(create): add React PowerSync scaffolding add-on#407
devagrawal09 wants to merge 1 commit intoTanStack:mainfrom
devagrawal09:feat/powersync-react-addon

Conversation

@devagrawal09
Copy link
Contributor

@devagrawal09 devagrawal09 commented Mar 13, 2026

Summary

  • add a new built-in React add-on at packages/create/src/frameworks/react/add-ons/powersync
  • scaffold PowerSync metadata, env vars, package additions, provider wiring, schema/connector stubs, demo route, README, and logo
  • add vite-plugin integration (powersyncVite()) so generated apps include PowerSync-required Vite worker/dependency config

Verification

  • pnpm build
  • pnpm test:unit
  • pnpm nx affected --target=test:e2e --parallel=3
  • node packages/cli/dist/index.js create powersync-app --framework react --add-ons powersync --target-dir /tmp/powersync-ci-verify-YDFAth --no-install --no-git --examples
  • in generated app:
    • pnpm install
    • pnpm build

Notes

  • BackendConnector is intentionally scaffold-level and still requires project-specific auth + upload implementation.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added PowerSync add-on for React framework enabling offline-first database synchronization
    • Includes demo implementation with todo list example showing data insertion and sync status
    • Configured with environment setup and secure credential management
  • Documentation

    • Added setup and integration guide for PowerSync add-on

@changeset-bot
Copy link

changeset-bot bot commented Mar 13, 2026

⚠️ No Changeset found

Latest commit: 69afd29

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2026

📝 Walkthrough

Walkthrough

A new PowerSync add-on for React has been introduced, providing offline-first database synchronization capabilities. The package includes schema definitions, a backend connector for data sync, a React context provider, environment configuration, Vite plugin setup, package dependencies, documentation, and a demo route with sample UI.

Changes

Cohort / File(s) Summary
Documentation & Configuration
README.md.ejs, info.json, package.json.ejs
Added add-on metadata, dependency declarations (@powersync/react, @powersync/web, @journeyapps/wa-sqlite), setup instructions, and integration points.
Environment & Build Setup
_dot_env.local.append, powersync-vite-plugin.ts
Configured environment variables (VITE_POWERSYNC_URL, VITE_POWERSYNC_TOKEN) and Vite plugin to handle PowerSync worker format and dependency exclusions.
PowerSync Core Integration
AppSchema.ts, BackendConnector.ts, provider.tsx
Defined database schema with todos table, implemented backend connector for credentials and CRUD data upload, and created React context provider wrapping PowerSyncDatabase with WASQLiteOpenFactory.
Demo Route
powersync.tsx
Added demo component with local todo insertion, query functionality, connection status display, and typed record rendering using PowerSync hooks.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Bouncing with joy, PowerSync joins our refrain,
SQLite and sync through the digital terrain,
Providers and schemas, and backends so bright,
Demo routes dancing from morning to night,
Database harmony at rapid-hop pace! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main change: adding a React PowerSync scaffolding add-on to the create package.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@devagrawal09 devagrawal09 marked this pull request as ready for review March 13, 2026 20:52
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/create/src/frameworks/react/add-ons/powersync/assets/src/lib/powersync/BackendConnector.ts`:
- Around line 47-50: The catch block in BackendConnector's upload flow (in
BackendConnector.ts) currently calls await transaction.complete() on error which
finalizes the transaction and discards failed work; remove that call from the
catch, keep or improve the console.error logging, and re-throw the caught error
so PowerSync's retry mechanism can run (ensure transaction.complete() is only
invoked on successful completion—e.g. after the try or guarded in a finally only
when no error occurred).

In
`@packages/create/src/frameworks/react/add-ons/powersync/assets/src/routes/demo/powersync.tsx`:
- Around line 68-74: The todo input lacks a programmatic label; update the form
in the component that uses addTodo, setDescription, and description to provide
an accessible label—either add a visible <label> tied to the input via
htmlFor/id or add an aria-label/aria-labelledby attribute on the input—so screen
readers can identify the field (ensure the id you add matches the htmlFor if
using a label and keep event handler setDescription and value={description}
unchanged).
- Around line 25-39: The addTodo function currently awaits powerSync.execute but
has no error handling, causing failures to be silent; wrap the call to
powerSync.execute in a try/catch inside addTodo and on error show user feedback
(e.g., set an error state or call a notification/toast), log the error (include
the thrown error) and ensure form state is restored (e.g., keep description or
reset via setDescription only on success); reference the addTodo function, the
powerSync.execute call, and setDescription when implementing this fix.

In `@packages/create/src/frameworks/react/add-ons/powersync/info.json`:
- Around line 19-23: The manifest marks VITE_POWERSYNC_TOKEN as a secret but
VITE_* vars are exposed to the client; update the JSON entry for
"VITE_POWERSYNC_TOKEN" to set "secret": false (or remove the key) so it is not
advertised as safe for secrets, and instead add or document a non-client key
(e.g., "POWERSYNC_TOKEN") that is marked "secret": true for server-only usage;
ensure the change targets the "VITE_POWERSYNC_TOKEN" JSON object in info.json
and add a note in the manifest/comments that VITE_* variables are public in
Vite.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0564509f-44b4-4d73-982e-0c839b0b7a89

📥 Commits

Reviewing files that changed from the base of the PR and between b8f4e63 and 69afd29.

⛔ Files ignored due to path filters (1)
  • packages/create/src/frameworks/react/add-ons/powersync/small-logo.svg is excluded by !**/*.svg
📒 Files selected for processing (9)
  • packages/create/src/frameworks/react/add-ons/powersync/README.md.ejs
  • packages/create/src/frameworks/react/add-ons/powersync/assets/_dot_env.local.append
  • packages/create/src/frameworks/react/add-ons/powersync/assets/powersync-vite-plugin.ts
  • packages/create/src/frameworks/react/add-ons/powersync/assets/src/integrations/powersync/provider.tsx
  • packages/create/src/frameworks/react/add-ons/powersync/assets/src/lib/powersync/AppSchema.ts
  • packages/create/src/frameworks/react/add-ons/powersync/assets/src/lib/powersync/BackendConnector.ts
  • packages/create/src/frameworks/react/add-ons/powersync/assets/src/routes/demo/powersync.tsx
  • packages/create/src/frameworks/react/add-ons/powersync/info.json
  • packages/create/src/frameworks/react/add-ons/powersync/package.json.ejs

Comment on lines +47 to +50
} catch (error) {
console.error('PowerSync uploadData failed', error)
await transaction.complete()
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

PowerSync JavaScript SDK uploadData error handling best practices

💡 Result:

PowerSync JS SDK uploadData() error-handling best practices

  1. Treat uploadData() as a “commit” step; keep your backend write endpoint synchronous

    • Your backend should apply writes before returning (don’t enqueue for later), otherwise the client can advance checkpoints without the server reflecting the change, causing temporary “disappearing/reappearing” UI effects. [1]
  2. Use HTTP status codes to control retry vs. unblock

    • Return/raise an error (typically HTTP 5xx) only for transient failures (network issues, backend DB temporarily unavailable). PowerSync will keep the change(s) in the upload queue and retry (docs describe retries at ~5-second intervals, preserving order). [1], [2]
    • Avoid 4xx for validation/conflict cases because it can block the client upload queue. Instead, return HTTP 2xx and surface the validation/conflict information via:
      • error details in the 2xx response body, and/or
      • writing errors into a separate table/collection that is synced back to the client for UI display. [1], [2]
  3. Batch uploads and make your backend idempotent

    • Prefer a batch endpoint for higher write volumes. [1]
    • Ensure your mutation application is idempotent (safe to retry) because transient failures can lead to retries of the same logical change set. [2]
  4. Propagate “business rule failures” asynchronously (don’t block the queue)

    • For conflicts/validation failures, use the pattern in PowerSync’s guidance: mark/surface failures to the UI without stopping the upload pipeline (see conflict/validation handling guidance). [2], [3]
  5. Log and classify errors using PowerSync references

    • When debugging sync-related failures, cross-check against PowerSync’s error code reference to distinguish service/rules issues from app/backend issues. [4]

Key references

  • uploadData() is invoked automatically by the client SDK as it processes the local upload queue. [5]
  • Backend integration overview and retry behavior expectations. [6]

Sources: [1] [2] [3] [4] [5] [6]


🏁 Script executed:

# Read the full BackendConnector.ts file to see the complete uploadData method
cat -n "packages/create/src/frameworks/react/add-ons/powersync/assets/src/lib/powersync/BackendConnector.ts"

Repository: TanStack/cli

Length of output: 1787


Calling transaction.complete() after an error prevents PowerSync retries and causes data loss.

The catch block calls await transaction.complete(), which finalizes the transaction even when the upload fails. PowerSync's retry mechanism requires errors to be thrown and propagated; once the transaction is marked complete, the failed operations are discarded and never synced to the backend.

When the TODO stubs are replaced with actual backend calls, this pattern will silently lose data on transient failures. Re-throw the error instead to let PowerSync retry:

🐛 Proposed fix
     } catch (error) {
       console.error('PowerSync uploadData failed', error)
-      await transaction.complete()
+      throw error
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (error) {
console.error('PowerSync uploadData failed', error)
await transaction.complete()
}
} catch (error) {
console.error('PowerSync uploadData failed', error)
throw error
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/create/src/frameworks/react/add-ons/powersync/assets/src/lib/powersync/BackendConnector.ts`
around lines 47 - 50, The catch block in BackendConnector's upload flow (in
BackendConnector.ts) currently calls await transaction.complete() on error which
finalizes the transaction and discards failed work; remove that call from the
catch, keep or improve the console.error logging, and re-throw the caught error
so PowerSync's retry mechanism can run (ensure transaction.complete() is only
invoked on successful completion—e.g. after the try or guarded in a finally only
when no error occurred).

Comment on lines +25 to +39
async function addTodo(event: React.FormEvent<HTMLFormElement>) {
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('')
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle insert failures so submit doesn’t fail silently.

Line 33 can reject, but addTodo has no catch path or user feedback.

Minimal error handling
+  const [error, setError] = useState<string | null>(null)
@@
   async function addTodo(event: React.FormEvent<HTMLFormElement>) {
     event.preventDefault()
@@
-    await powerSync.execute(
-      'INSERT INTO todos (id, created_at, description, completed) VALUES (?, ?, ?, ?)',
-      [crypto.randomUUID(), new Date().toISOString(), nextDescription, 0],
-    )
-
-    setDescription('')
+    try {
+      setError(null)
+      await powerSync.execute(
+        'INSERT INTO todos (id, created_at, description, completed) VALUES (?, ?, ?, ?)',
+        [crypto.randomUUID(), new Date().toISOString(), nextDescription, 0],
+      )
+      setDescription('')
+    } catch {
+      setError('Failed to insert row. Please retry.')
+    }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function addTodo(event: React.FormEvent<HTMLFormElement>) {
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('')
}
const [error, setError] = useState<string | null>(null)
async function addTodo(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault()
const nextDescription = description.trim()
if (!nextDescription) {
return
}
try {
setError(null)
await powerSync.execute(
'INSERT INTO todos (id, created_at, description, completed) VALUES (?, ?, ?, ?)',
[crypto.randomUUID(), new Date().toISOString(), nextDescription, 0],
)
setDescription('')
} catch {
setError('Failed to insert row. Please retry.')
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/create/src/frameworks/react/add-ons/powersync/assets/src/routes/demo/powersync.tsx`
around lines 25 - 39, The addTodo function currently awaits powerSync.execute
but has no error handling, causing failures to be silent; wrap the call to
powerSync.execute in a try/catch inside addTodo and on error show user feedback
(e.g., set an error state or call a notification/toast), log the error (include
the thrown error) and ensure form state is restored (e.g., keep description or
reset via setDescription only on success); reference the addTodo function, the
powerSync.execute call, and setDescription when implementing this fix.

Comment on lines +68 to +74
<form className="mt-4 flex flex-col gap-3 sm:flex-row" onSubmit={addTodo}>
<input
className="min-w-0 flex-1 rounded-2xl border border-[var(--line)] bg-white px-4 py-3 text-sm text-[var(--sea-ink)] outline-none"
onChange={(event) => setDescription(event.target.value)}
placeholder="Write to the local PowerSync database"
value={description}
/>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add an explicit label for the todo input.

Line 69 renders an <input> without a programmatic label, which hurts screen-reader usability.

Accessibility fix
+          <label className="sr-only" htmlFor="powersync-todo-description">
+            Todo description
+          </label>
           <input
+            id="powersync-todo-description"
             className="min-w-0 flex-1 rounded-2xl border border-[var(--line)] bg-white px-4 py-3 text-sm text-[var(--sea-ink)] outline-none"
             onChange={(event) => setDescription(event.target.value)}
             placeholder="Write to the local PowerSync database"
             value={description}
           />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<form className="mt-4 flex flex-col gap-3 sm:flex-row" onSubmit={addTodo}>
<input
className="min-w-0 flex-1 rounded-2xl border border-[var(--line)] bg-white px-4 py-3 text-sm text-[var(--sea-ink)] outline-none"
onChange={(event) => setDescription(event.target.value)}
placeholder="Write to the local PowerSync database"
value={description}
/>
<form className="mt-4 flex flex-col gap-3 sm:flex-row" onSubmit={addTodo}>
<label className="sr-only" htmlFor="powersync-todo-description">
Todo description
</label>
<input
id="powersync-todo-description"
className="min-w-0 flex-1 rounded-2xl border border-[var(--line)] bg-white px-4 py-3 text-sm text-[var(--sea-ink)] outline-none"
onChange={(event) => setDescription(event.target.value)}
placeholder="Write to the local PowerSync database"
value={description}
/>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/create/src/frameworks/react/add-ons/powersync/assets/src/routes/demo/powersync.tsx`
around lines 68 - 74, The todo input lacks a programmatic label; update the form
in the component that uses addTodo, setDescription, and description to provide
an accessible label—either add a visible <label> tied to the input via
htmlFor/id or add an aria-label/aria-labelledby attribute on the input—so screen
readers can identify the field (ensure the id you add matches the htmlFor if
using a label and keep event handler setDescription and value={description}
unchanged).

Comment on lines +19 to +23
"name": "VITE_POWERSYNC_TOKEN",
"description": "Development token for local testing",
"required": false,
"secret": true,
"file": ".env.local"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In current Vite docs, are VITE_* env vars exposed to client bundles and therefore unsuitable for sensitive secrets?

💡 Result:

Yes. Vite’s current docs say that only env vars prefixed with VITE_ are exposed to your Vite-processed client code via import.meta.env, and because anything exposed this way ends up in the client bundle, VITE_* variables should not contain sensitive information. (vite.dev)

Citations:


VITE_POWERSYNC_TOKEN must not be marked as secret.

VITE_* env variables are exposed to the client bundle in Vite and therefore unsuitable for sensitive information. Marking this variable as "secret": true is misleading and creates a security risk by implying it is safe to store secrets there.

Suggested manifest adjustment
     {
-      "name": "VITE_POWERSYNC_TOKEN",
-      "description": "Development token for local testing",
+      "name": "VITE_POWERSYNC_DEV_TOKEN",
+      "description": "Client-visible development token (never use production secrets)",
       "required": false,
-      "secret": true,
+      "secret": false,
       "file": ".env.local"
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"name": "VITE_POWERSYNC_TOKEN",
"description": "Development token for local testing",
"required": false,
"secret": true,
"file": ".env.local"
"name": "VITE_POWERSYNC_DEV_TOKEN",
"description": "Client-visible development token (never use production secrets)",
"required": false,
"secret": false,
"file": ".env.local"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create/src/frameworks/react/add-ons/powersync/info.json` around
lines 19 - 23, The manifest marks VITE_POWERSYNC_TOKEN as a secret but VITE_*
vars are exposed to the client; update the JSON entry for "VITE_POWERSYNC_TOKEN"
to set "secret": false (or remove the key) so it is not advertised as safe for
secrets, and instead add or document a non-client key (e.g., "POWERSYNC_TOKEN")
that is marked "secret": true for server-only usage; ensure the change targets
the "VITE_POWERSYNC_TOKEN" JSON object in info.json and add a note in the
manifest/comments that VITE_* variables are public in Vite.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant