Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,14 @@ Set these variables in `.env.local`:

PowerSync setup guidance:
https://docs.powersync.com/client-sdk-references/js-web

## Sync Stream SQL — Known Footguns

PowerSync evaluates sync-stream SQL server-side in JavaScript and the client runs SQLite. A handful of SQL operators silently disagree across that boundary — bucket keys diverge and rows fail to sync without any error. As of `@powersync/service` versions prior to the fixes listed below, watch out for these in your `sync-rules.yaml`:

- **`JOIN` clauses** are not currently supported in sync streams and silently sync zero rows. Rewrite as a single-table query with a correlated subquery in the `WHERE` clause. See [powersync-ja/powersync-service#662](https://github.com/powersync-ja/powersync-service/pull/662) for a loud error proposal.
- **`upper()` / `lower()`** apply locale-aware Unicode case folding (length-changing on `ß`, `fi`, Turkish dotted-i) where SQLite is ASCII-only. Avoid as bucket keys over non-ASCII data until [powersync-service#663](https://github.com/powersync-ja/powersync-service/pull/663) lands.
- **`length()`** returns a UTF-16 code-unit count where SQLite returns code points — diverges 2x on emoji, CJK Extension B-G, ancient scripts. Avoid in bucket-parameter filters until [#664](https://github.com/powersync-ja/powersync-service/pull/664).
- **`substring()` / `substr()`** indexes by UTF-16 code units and can split surrogate pairs, returning unpaired-surrogate strings. Avoid on user content with non-BMP code points until [#665](https://github.com/powersync-ja/powersync-service/pull/665).
Comment on lines +32 to +35
Copy link
Copy Markdown

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

🏁 Script executed:

#!/bin/bash
# Description: Verify PowerSync upstream PR links exist and are accessible

echo "Checking PR `#662`..."
gh pr view 662 --repo powersync-ja/powersync-service --json title,url,state

echo "Checking PR `#663`..."
gh pr view 663 --repo powersync-ja/powersync-service --json title,url,state

echo "Checking PR `#664`..."
gh pr view 664 --repo powersync-ja/powersync-service --json title,url,state

echo "Checking PR `#665`..."
gh pr view 665 --repo powersync-ja/powersync-service --json title,url,state

Repository: TanStack/cli

Length of output: 807


🌐 Web query:

PowerSync sync-rules SQL JOIN support and known limitations

💡 Result:

PowerSync handles SQL JOIN support differently depending on the synchronization system being used: Sync Streams or the legacy Sync Rules [1]. Sync Streams (Recommended) Sync Streams support INNER JOINs, though with specific limitations [2][3]. - Supported Syntax: Only JOIN or INNER JOIN are permitted [2][3]. - Unsupported Joins: LEFT, RIGHT, and OUTER joins are not supported [2]. - Selection Constraint: All selected columns must originate from a single table (e.g., SELECT table1.* FROM table1 JOIN table2 ON ...) [2][3]. Selecting columns from multiple tables in a single query is invalid [2][3]. - Condition Constraint: Join conditions must be simple equality comparisons (e.g., table1.column = table2.column) [2][3]. Complex expressions in join conditions (e.g., a.x > b.y) are not supported [2]. Sync Rules (Legacy) Sync Rules do not support SQL JOINs [2][4][5]. For projects using this legacy system, developers typically use workarounds such as: - Denormalization: Adding direct foreign key relationships or auxiliary columns to the schema to avoid the need for joins [4]. - Materialized Views: For Postgres sources, using extensions like pg_ivm to create incrementally maintained materialized views that can be queried directly [4][6]. - Parameter Queries: Using bucket parameters to pass IDs that allow for filtering data without requiring joins [7]. Because Sync Rules are considered legacy, PowerSync recommends migrating to Sync Streams for more complex query requirements, as they provide significantly more expressive capabilities, including CTEs and subqueries alongside the supported JOIN syntax [8][1]. Regardless of the sync method, keep in mind that PowerSync manages data synchronization between the server and the local SQLite database; complex JOINs can still be performed locally within the client-side SQLite database after the data has been synced [6].

Citations:


🌐 Web query:

PowerSync JavaScript SQL evaluator UTF-16 string function behavior

💡 Result:

PowerSync does not implement custom JavaScript-level logic for string function behavior in its SQL evaluator; instead, it relies on SQLite’s native text processing capabilities [1][2]. In the PowerSync architecture, data is synced as SQLite TEXT columns, which are stored as UTF-8 encoded strings [3][4]. Key points regarding string behavior in PowerSync: 1. Storage and Encoding: PowerSync maps most string-like data types (including JSON and various custom types) to the SQLite TEXT storage class, which natively uses UTF-8 encoding [3][4]. 2. SQL Evaluator: Because PowerSync utilizes standard SQLite as its local database engine, string functions (such as length, substring, instr, upper, and lower) behave according to SQLite's built-in definitions [1]. These functions operate on the stored UTF-8 string data [1]. 3. JavaScript Interaction: While PowerSync’s client SDKs are built with JavaScript/TypeScript, the SQL evaluator is essentially "just SQLite" [2]. When your JavaScript code interacts with these functions via PowerSync’s query methods (e.g., execute, getAll), the JavaScript runtime and the SQLite engine handle string passing [5][2]. Standard JavaScript strings are UTF-16, but they are typically converted to UTF-8 when interacting with the underlying SQLite database layer [6][7][2]. 4. No UTF-16 Specific Functions: PowerSync does not provide a specific API for "UTF-16 string functions." Developers should rely on standard SQLite string functions, which are designed to be efficient for the UTF-8 storage format [1]. In summary, you should assume standard SQLite behavior for all string operations rather than JavaScript-specific UTF-16 behavior, as the SQL evaluator functions are executed against the UTF-8 encoded data within the SQLite engine [1][3][2].

Citations:


Fix Sync Streams JOIN wording to match PowerSync docs (and align string-function claims with PR details)

  • PR links are valid: powersync-ja/powersync-service#662, #663, #664, and #665 all exist and resolve to the expected pulls.
  • The README’s JOIN statement (“JOIN clauses are not currently supported in sync streams”) is too broad: PowerSync docs say Sync Streams support JOIN/INNER JOIN with limitations (while other join types like LEFT/RIGHT/OUTER aren’t supported). Reword to describe the specific unsupported JOIN patterns and the actual failure mode (silent zero rows vs loud error) consistent with #662.
  • For upper()/lower(), length(), and substring()/substr(), the PR titles indicate the intended fixes (ASCII-only casing; code points vs code units; code-point indexing), but the README’s detailed behavioral claims should be made to exactly match the PR descriptions (especially the “silent mismatch” mechanics and the edge-case examples) consistent with #663/#664/#665.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/create/src/frameworks/react/add-ons/powersync/README.md.ejs` around
lines 32 - 35, Update the README wording: narrow the "**`JOIN` clauses**" line
to state that Sync Streams support `JOIN`/`INNER JOIN` but do not support
`LEFT`/`RIGHT`/`OUTER` (and describe the specific unsupported patterns) and
explicitly note the actual failure mode per powersync-ja/powersync-service#662
(silent zero-row sync vs proposed loud error), and revise the
`upper()`/`lower()`, `length()`, and `substring()`/`substr()` bullets so their
behavioral claims match the corresponding PRs (`#663`, `#664`, `#665`) — i.e., state
ASCII-only casing for `upper()`/`lower()`, UTF-16 code-unit vs code-point
differences for `length()`, and code-unit indexing/surrogate-splitting behavior
for `substring()`/`substr()` exactly as described in those PRs, removing any
overbroad or mismatched examples.


If your starter uses ASCII-only IDs and small integers, you will not hit these. They surface the moment user data includes German `ß`, emoji, CJK Ext-B characters, ligatures, or you reach for `JOIN`. Reference checker: [silentdrop](https://github.com/sravan27/silentdrop).