feat(create): add React PowerSync scaffolding add-on#407
feat(create): add React PowerSync scaffolding add-on#407devagrawal09 wants to merge 1 commit intoTanStack:mainfrom
Conversation
|
📝 WalkthroughWalkthroughA 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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
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. Comment |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
packages/create/src/frameworks/react/add-ons/powersync/small-logo.svgis excluded by!**/*.svg
📒 Files selected for processing (9)
packages/create/src/frameworks/react/add-ons/powersync/README.md.ejspackages/create/src/frameworks/react/add-ons/powersync/assets/_dot_env.local.appendpackages/create/src/frameworks/react/add-ons/powersync/assets/powersync-vite-plugin.tspackages/create/src/frameworks/react/add-ons/powersync/assets/src/integrations/powersync/provider.tsxpackages/create/src/frameworks/react/add-ons/powersync/assets/src/lib/powersync/AppSchema.tspackages/create/src/frameworks/react/add-ons/powersync/assets/src/lib/powersync/BackendConnector.tspackages/create/src/frameworks/react/add-ons/powersync/assets/src/routes/demo/powersync.tsxpackages/create/src/frameworks/react/add-ons/powersync/info.jsonpackages/create/src/frameworks/react/add-ons/powersync/package.json.ejs
| } catch (error) { | ||
| console.error('PowerSync uploadData failed', error) | ||
| await transaction.complete() | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
PowerSync JavaScript SDK uploadData error handling best practices
💡 Result:
PowerSync JS SDK uploadData() error-handling best practices
-
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]
-
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
4xxfor validation/conflict cases because it can block the client upload queue. Instead, return HTTP2xxand surface the validation/conflict information via:- error details in the
2xxresponse body, and/or - writing errors into a separate table/collection that is synced back to the client for UI display. [1], [2]
- error details in the
- Return/raise an error (typically HTTP
-
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]
-
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]
-
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.
| } 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).
| 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('') | ||
| } |
There was a problem hiding this comment.
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.
| 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.
| <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} | ||
| /> |
There was a problem hiding this comment.
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.
| <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).
| "name": "VITE_POWERSYNC_TOKEN", | ||
| "description": "Development token for local testing", | ||
| "required": false, | ||
| "secret": true, | ||
| "file": ".env.local" |
There was a problem hiding this comment.
🧩 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.
| "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.
Summary
packages/create/src/frameworks/react/add-ons/powersyncvite-pluginintegration (powersyncVite()) so generated apps include PowerSync-required Vite worker/dependency configVerification
pnpm buildpnpm test:unitpnpm nx affected --target=test:e2e --parallel=3node packages/cli/dist/index.js create powersync-app --framework react --add-ons powersync --target-dir /tmp/powersync-ci-verify-YDFAth --no-install --no-git --examplespnpm installpnpm buildNotes
BackendConnectoris intentionally scaffold-level and still requires project-specific auth + upload implementation.Summary by CodeRabbit
Release Notes
New Features
Documentation