diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 17b261d2e..d28b039d5 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -142,18 +142,18 @@ Update `docs.json` when adding, moving, or removing pages. Add redirects for mov ## Sync Streams and Sync Rules -Sync Streams are the default for new documentation. Keep legacy Sync Rules documentation accurate, but do not add new Sync Rules teaching, examples, or proactive references. +Sync Rules are deprecated, but many customers still run them. New documentation covers Sync Streams. Keep the Sync Rules docs accurate, and add to them when that makes them more accurate or helpful, without prioritizing that work. The goal is to avoid noisy Sync Rules references outside their own section, not to stop maintaining it. -When existing content shows both in tabs, preserve equivalent results and filters. Do not add new parallel Sync Rules examples. - -For existing prose that mentions both, use "[Sync Streams](/sync/streams/overview) (or legacy [Sync Rules](/sync/rules/overview))" once per page or major section. Later mentions should omit Sync Rules. +- **Sync Rules section.** `sync/rules/` and `sync/grammar/sync-rules/` hold all Sync Rules content. Each page opens with an `` callout that starts "Sync Rules are deprecated." A split twin or concept page then links to its Sync Streams equivalent. A shared-snippet wrapper instead states that the page applies to both Sync Streams and Sync Rules unless a section says otherwise. The overview carries the full deprecation notice in a ``. The sidebar group stays "Sync Rules (Legacy)". +- **Keep the engines apart.** Never place Sync Streams and Sync Rules content side by side: no engine tabs, no "(or legacy Sync Rules)" asides, no pointers to a Sync Rules equivalent. Outside `sync/rules/`, mention Sync Rules only to state a behavior difference that affects Sync Rules users, and remove other mentions when you edit a page. +- **Shared pages.** A page that applies to both engines keeps one body in `snippets/sync-shared/.mdx`, imported by a Sync Streams wrapper at the original path and a Sync Rules wrapper at `sync/rules/.mdx` that adds the callout and `noindex: true`. Edit the snippet, not the wrappers, and keep it valid for both engines. +- **Counterpart comments.** Every split twin, wrapper, and shared snippet starts with an MDX comment naming its counterpart. Read it before editing, apply a fix to both sides where content is shared, and keep the comment when restructuring. ## Verification - Verify technical claims and run code examples before publication. Select other checks appropriate to the change. - Run `vale ` for changed MDX pages. Add new technical terms to `.github/vale/config/vocabularies/PowerSync/accept.txt`; do not add ordinary misspellings. -- After link or navigation changes, run `npx mintlify broken-links`. Mintlify requires Node 20.17–24; if needed, use `PATH="/opt/homebrew/opt/node@24/bin:$PATH" npx mintlify broken-links`. -- For anchor and snippet checks, use `pnpm check:links`. Validate repository instruction links as file paths, since the site checker does not cover all of them. +- After link or navigation changes, run `pnpm check:links`. It runs the Mintlify path, anchor, and snippet checks through `scripts/check-links.mjs`, resolves anchors defined in imported snippets, and is the same check CI runs. It tells you if your Node version is unsupported. Validate repository instruction links as file paths, since the site checker does not cover them. - Use [the lint command](commands/lint-docs.md) for the check workflow and [the reviewer](agents/document-reviewer.md) for editorial review. Passing linters does not establish technical accuracy or style compliance. ## Git Workflow diff --git a/.claude/agents/document-reviewer.md b/.claude/agents/document-reviewer.md index 06f904eae..74464fbcf 100644 --- a/.claude/agents/document-reviewer.md +++ b/.claude/agents/document-reviewer.md @@ -17,6 +17,7 @@ Apply the canonical standards in three passes and report findings from each: 1. **Accuracy:** claims, platform scope, versions, and consistency with the surrounding page. Before reporting a claim as unverified, check the sources the PR or the user cites, such as the source PR, divergence issue, release notes, or code at the merged commit, and follow the links inside them. If nothing is cited, look up the release the text names. Report a claim as unverified only when no source covers it or a source contradicts it. Give evidence, do not invent problems, and do not approve unverified claims as correct. 2. **Necessity:** list every sentence that describes what the product prints, displays, logs, or says in an error. Treat each one as a finding to remove unless it passes the restating rule in [Content Strategy](../CLAUDE.md#content-strategy), and report it even when the sentence is accurate. Describing visible output is not a mechanism, consequence, or signal. Also flag internal mechanics, rare exceptions, and repetition. Flag missing context only when readers need it, and do not require every entry to explain a mechanism, consequence, signal, action, and trade-off. 3. **Clarity and format:** plain technical English, clear actors and actions, and suitable examples and components. +4. **Sync Rules containment:** flag any Sync Rules mention, example, or tab outside `sync/rules/` and `snippets/sync-shared/` that does not state a behavior difference. Check that split twins, wrappers, and shared snippets keep their counterpart comment. ## Default Output diff --git a/.claude/commands/lint-docs.md b/.claude/commands/lint-docs.md index e0ac914c1..ca9027388 100644 --- a/.claude/commands/lint-docs.md +++ b/.claude/commands/lint-docs.md @@ -1,9 +1,9 @@ --- -allowed-tools: Read, Bash(npx mintlify *), Bash(PATH=* npx mintlify *), Bash(vale *), Bash(git diff *), Bash(git status *) -description: Run Vale and Mintlify broken-link checks for documentation changes and report failures. +allowed-tools: Read, Bash(pnpm check:links), Bash(PATH=* pnpm check:links), Bash(vale *), Bash(git diff *), Bash(git status *) +description: Run Vale and the link check for documentation changes and report failures. --- -1. Read the canonical [Verification](../CLAUDE.md#verification) section for commands, supported Node versions, and vocabulary rules. +1. Read the canonical [Verification](../CLAUDE.md#verification) section for commands and vocabulary rules. 2. Use the requested file scope. Otherwise, identify changed MDX pages with `git diff main --name-only --diff-filter=ACMR -- '*.mdx'` and `git status --short`, including untracked pages. -3. Run `vale ` for each page and `npx mintlify broken-links` for the site. Use the canonical Node fallback if needed. +3. Run `vale ` for each page and `pnpm check:links` once for the site. 4. Report findings by file, suggested fixes, and totals for errors, warnings, and suggestions. Report failed or unavailable checks separately from content findings. diff --git a/.claude/skills/doc-author/SKILL.md b/.claude/skills/doc-author/SKILL.md index 6ac236dc1..c2201dbb4 100644 --- a/.claude/skills/doc-author/SKILL.md +++ b/.claude/skills/doc-author/SKILL.md @@ -18,6 +18,6 @@ Use the canonical Working Process for scope changes and unresolved decisions. 1. Identify the reader, desired outcome, and affected feature or concept. 2. Research the implementation and existing coverage. Read the most relevant related pages and `docs.json`; avoid unnecessary duplication. 3. If a plan is needed, state the proposed pages, structure, and unresolved questions before drafting. -4. Write the update under the canonical standards. Keep the existing structure unless the task requires a change. +4. Write the update under the canonical standards. Write new content for Sync Streams and keep Sync Rules content inside its own section. Keep the existing structure unless the task requires a change. 5. Self-review for accuracy, reader understanding, minimum useful detail, and navigation fit. Run the canonical verification checks relevant to the change. 6. Present the result and any unresolved draft TODOs, or complete the delivery workflow already authorized by the user. diff --git a/.claude/skills/pr-to-docs/SKILL.md b/.claude/skills/pr-to-docs/SKILL.md index d5203a4a8..b8556e124 100644 --- a/.claude/skills/pr-to-docs/SKILL.md +++ b/.claude/skills/pr-to-docs/SKILL.md @@ -33,4 +33,4 @@ Ask before expanding scope, documenting a deprecation that needs migration decis ## 4. Draft and Verify -Apply the canonical writing standards, navigation requirements, and verification checks. Preserve existing structure unless restructuring is part of the approved plan. Flag unresolved facts using the canonical draft-TODO convention and report what must be resolved before publication. +Apply the canonical writing standards, navigation requirements, and verification checks. Document new behavior for Sync Streams. Update Sync Rules pages when the change affects them too, and do not add Sync Rules mentions elsewhere. Preserve existing structure unless restructuring is part of the approved plan. Flag unresolved facts using the canonical draft-TODO convention and report what must be resolved before publication. diff --git a/client-sdks/advanced/custom-types-arrays-and-json.mdx b/client-sdks/advanced/custom-types-arrays-and-json.mdx index edca592fe..ad163c5bc 100644 --- a/client-sdks/advanced/custom-types-arrays-and-json.mdx +++ b/client-sdks/advanced/custom-types-arrays-and-json.mdx @@ -7,7 +7,7 @@ PowerSync supports JSON/JSONB and array columns. They are synced as JSON text an ## JSON and JSONB -The PowerSync Service treats JSON and JSONB columns as text and provides many helpers for working with JSON in [Sync Streams](/sync/streams/overview) (or legacy [Sync Rules](/sync/rules/overview)). +The PowerSync Service treats JSON and JSONB columns as text and provides many helpers for working with JSON in [Sync Streams](/sync/streams/overview). **Note:** Native Postgres arrays, JSON arrays, and JSONB arrays are effectively all equivalent in PowerSync. @@ -22,36 +22,20 @@ ADD COLUMN custom_payload json; ### Sync Streams - - - PowerSync treats JSON columns as text. Use `json_extract()` and other JSON functions in stream queries. Subscribe per list to sync only that list's todos: - - ```yaml - config: - edition: 3 - streams: - my_json_todos: - auto_subscribe: true - with: - owned_lists: SELECT id AS list_id FROM lists WHERE owner_id = auth.user_id() - query: SELECT * FROM todos WHERE json_extract(custom_payload, '$.json_list') IN owned_lists - ``` +PowerSync treats JSON columns as text. Use `json_extract()` and other JSON functions in stream queries. Subscribe per list to sync only that list's todos: + +```yaml +config: + edition: 3 +streams: + my_json_todos: + auto_subscribe: true + with: + owned_lists: SELECT id AS list_id FROM lists WHERE owner_id = auth.user_id() + query: SELECT * FROM todos WHERE json_extract(custom_payload, '$.json_list') IN owned_lists +``` - The client subscribes once per list (e.g. `db.syncStream('my_json_todos', { list_id: listId }).subscribe()`). - - - PowerSync treats JSON columns as text and provides transformation functions in Sync Rules such as `json_extract()`. - - ```yaml - bucket_definitions: - my_json_todos: - # Separate bucket per To-Do list - parameters: SELECT id AS list_id FROM lists WHERE owner_id = request.user_id() - data: - - SELECT * FROM todos WHERE json_extract(custom_payload, '$.json_list') = bucket.list_id - ``` - - +The client subscribes once per list (e.g. `db.syncStream('my_json_todos', { list_id: listId }).subscribe()`). ### Client SDK @@ -198,7 +182,7 @@ You can write the entire updated column value as a string, or, with `trackPrevio PowerSync treats array columns as JSON text. This means that the SQLite JSON operators can be used on any array columns. -Additionally, array membership is supported in [Sync Streams](/sync/streams/overview) (or legacy [Sync Rules](/sync/rules/overview)) so you can sync rows based on whether a parameter value appears in an array column. +Additionally, array membership is supported in [Sync Streams](/sync/streams/overview) so you can sync rows based on whether a parameter value appears in an array column. **Note:** Native Postgres arrays, JSON arrays, and JSONB arrays are effectively all equivalent in PowerSync. @@ -219,33 +203,17 @@ Array columns are converted to text by the PowerSync Service. A text array as de **Array Membership** - - - Sync rows where a subscription parameter value is in the row's array column using `IN`: - - ```yaml - config: - edition: 3 - streams: - custom_todos: - query: SELECT * FROM todos WHERE subscription.parameter('list_id') IN unique_identifiers - ``` +Sync rows where a subscription parameter value is in the row's array column using `IN`: - The client subscribes per list (e.g. `db.syncStream('custom_todos', { list_id: listId }).subscribe()`). - - - It's possible to sync rows dynamically based on the contents of array columns using the `IN` operator: - - ```yaml - bucket_definitions: - custom_todos: - # Separate bucket per To-Do list - parameters: SELECT id AS list_id FROM lists WHERE owner_id = request.user_id() - data: - - SELECT * FROM todos WHERE bucket.list_id IN unique_identifiers - ``` - - +```yaml +config: + edition: 3 +streams: + custom_todos: + query: SELECT * FROM todos WHERE subscription.parameter('list_id') IN unique_identifiers +``` + +The client subscribes per list (e.g. `db.syncStream('custom_todos', { list_id: listId }).subscribe()`). See these additional details when using the `IN` operator: [Operators](/sync/supported-sql#operators) @@ -416,31 +384,15 @@ create type location_address AS ( ### Sync Streams - - - The custom type column is serialized as JSON and you can use `json_extract()` and other JSON functions in stream queries: - - ```yaml - config: - edition: 3 - streams: - todos_by_city: - query: SELECT * FROM todos WHERE json_extract(location, '$.city') = subscription.parameter('city') - ``` - - - Custom type columns are converted to text by the PowerSync Service. - Depending on whether the `custom_postgres_types` [compatibility option](/sync/advanced/compatibility) is enabled, - PowerSync would sync the row as: - - - `{"street":"1000 S Colorado Blvd.","city":"Denver","state":"CO","zip":80211}` if the option is enabled. - - `("1000 S Colorado Blvd.",Denver,CO,80211)` if the option is disabled. +The custom type column is serialized as JSON and you can use `json_extract()` and other JSON functions in stream queries: - You can use regular string and JSON manipulation functions in Sync Rules. This means that individual values of the type - can be synced with `json_extract` if the `custom_postgres_types` compatibility option is enabled. - Without the option, the entire column must be synced as text. - - +```yaml +config: + edition: 3 +streams: + todos_by_city: + query: SELECT * FROM todos WHERE json_extract(location, '$.city') = subscription.parameter('city') +``` ### Client SDK diff --git a/client-sdks/advanced/gis-data-postgis.mdx b/client-sdks/advanced/gis-data-postgis.mdx index f3ac3fd7d..7d0ccef38 100644 --- a/client-sdks/advanced/gis-data-postgis.mdx +++ b/client-sdks/advanced/gis-data-postgis.mdx @@ -115,7 +115,7 @@ The data looks exactly how it’s stored in the Postgres database i.e. Example use case: Extract x (long) and y (lat) values from a PostGIS type, to use these values independently in an application. -PowerSync supports the following PostGIS functions in Sync Streams (or legacy Sync Rules): [Operators and Functions](/sync/supported-sql#functions) +PowerSync supports the following PostGIS functions in Sync Streams: [Operators and Functions](/sync/supported-sql#functions) 1. `ST_AsGeoJSON` 2. `ST_AsText` @@ -126,25 +126,12 @@ PowerSync supports the following PostGIS functions in Sync Streams (or legacy Sy IMPORTANT NOTE: These functions will only work if your Postgres instance has the PostGIS extension installed and you’re storing values as type `geography` or `geometry`. - - - ```yaml - config: - edition: 3 - streams: - global: - queries: - - SELECT * FROM lists - - SELECT *, st_x(location) as longitude, st_y(location) as latitude FROM todos - ``` - - - ```yaml - bucket_definitions: - global: - data: - - SELECT * FROM lists - - SELECT *, st_x(location) as longitude, st_y(location) as latitude from todos - ``` - - +```yaml +config: + edition: 3 +streams: + global: + queries: + - SELECT * FROM lists + - SELECT *, st_x(location) as longitude, st_y(location) as latitude FROM todos +``` diff --git a/client-sdks/advanced/pre-seeded-sqlite.mdx b/client-sdks/advanced/pre-seeded-sqlite.mdx index b431f7687..dbaaefab5 100644 --- a/client-sdks/advanced/pre-seeded-sqlite.mdx +++ b/client-sdks/advanced/pre-seeded-sqlite.mdx @@ -20,34 +20,19 @@ If you're interested in seeing an end-to-end example, we've prepared a demo repo ## Main Concepts ### Generate a Scoped JWT Token -In most cases you'd want to pre-seed the SQLite database with user specific data and not all data from the source database, as you normally would when using PowerSync. For this you would need to generate JWT tokens that include the necessary properties to satisfy the conditions of the queries in your Sync Streams (or legacy Sync Rules). +In most cases you'd want to pre-seed the SQLite database with user specific data and not all data from the source database, as you normally would when using PowerSync. For this you would need to generate JWT tokens that include the necessary properties to satisfy the conditions of the queries in your Sync Streams. Let's say we have the following Sync Config: - - - ```yaml - sync_config: - content: | - config: - edition: 3 - streams: - store_products: - query: SELECT * FROM products WHERE store_id = auth.parameter('store_id') - ``` - - - ```yaml - sync_config: - content: | - bucket_definitions: - store_products: - parameters: SELECT id as store_id FROM stores WHERE id = request.jwt() ->> 'store_id' - data: - - SELECT * FROM products WHERE store_id = bucket.store_id - ``` - - +```yaml +sync_config: + content: | + config: + edition: 3 + streams: + store_products: + query: SELECT * FROM products WHERE store_id = auth.parameter('store_id') +``` In the example above the `store_id` is part of the JWT payload and is used to filter products by store for a user. Given this we would want to do the following: 1. Query the source database, directly from the Node.js application, for all the store ids you'd want a pre-seeded SQLite database for. diff --git a/client-sdks/advanced/raw-tables.mdx b/client-sdks/advanced/raw-tables.mdx index 44fdb9d8a..636a32a65 100644 --- a/client-sdks/advanced/raw-tables.mdx +++ b/client-sdks/advanced/raw-tables.mdx @@ -761,7 +761,7 @@ In PowerSync's [JSON-based view system](/architecture/client-architecture#client ### Adding Raw Tables as a New Table -When you're adding new tables to your Sync Streams (or legacy Sync Rules), clients will start to sync data on those tables - even if the tables aren't mentioned in the client's schema yet. So at the time you're introducing a new raw table to your app, it's possible that PowerSync has already synced some data for that table, which would be stored in `ps_untyped`. When adding regular tables, PowerSync will automatically extract rows from `ps_untyped`. With raw tables, that step is your responsibility. To copy data, run these statements in a transaction after creating the table: +When you're adding new tables to your Sync Streams, clients will start to sync data on those tables - even if the tables aren't mentioned in the client's schema yet. So at the time you're introducing a new raw table to your app, it's possible that PowerSync has already synced some data for that table, which would be stored in `ps_untyped`. When adding regular tables, PowerSync will automatically extract rows from `ps_untyped`. With raw tables, that step is your responsibility. To copy data, run these statements in a transaction after creating the table: ``` INSERT INTO my_table (id, my_column, ...) diff --git a/client-sdks/advanced/sequential-id-mapping.mdx b/client-sdks/advanced/sequential-id-mapping.mdx index d3d5aa57f..632c4324e 100644 --- a/client-sdks/advanced/sequential-id-mapping.mdx +++ b/client-sdks/advanced/sequential-id-mapping.mdx @@ -29,7 +29,7 @@ Before we get started, let's outline the changes we will have to make: - Update your Sync Streams (or legacy Sync Rules) to use the UUID column instead of the integer ID. + Update your Sync Streams to use the UUID column instead of the integer ID. @@ -183,40 +183,24 @@ We will create the following two triggers that cover either scenario of updating We now have triggers in place that will handle the mapping for our updated schema and -can move on to updating your Sync Streams/Sync Rules to use the UUID column instead of the integer ID. +can move on to updating your Sync Streams to use the UUID column instead of the integer ID. ## Update Sync Streams As sequential IDs can only be created on the backend source database, we need to use UUIDs in the client. The Sync Config is updated to use the `uuid` column as the `id` column for the `lists` and `todos` tables, explicitly defining which columns to select so that `list_id` (the integer ID) is no longer exposed to the client. - - - ```yaml - config: - edition: 3 - streams: - user_lists: - auto_subscribe: true - with: - user_lists_param: SELECT id FROM lists WHERE owner_id = auth.user_id() - queries: - - "SELECT lists.uuid AS id, lists.created_at, lists.name, lists.owner_id FROM lists WHERE lists.id IN user_lists_param" - - "SELECT todos.uuid AS id, todos.created_at, todos.completed_at, todos.description, todos.completed, todos.created_by, todos.list_uuid FROM todos WHERE todos.list_id = user_lists_param" - ``` - - - ```yaml sync-config.yaml {4, 7-8} - bucket_definitions: - user_lists: - # Separate bucket per todo list - parameters: select id from lists where owner_id = request.user_id() - data: - # Explicitly define all the columns - - select uuid as id, created_at, name, owner_id from lists where id = bucket.id - - select uuid as id, created_at, completed_at, description, completed, created_by, list_uuid from todos where list_id = bucket.id - ``` - - +```yaml +config: + edition: 3 +streams: + user_lists: + auto_subscribe: true + with: + user_lists_param: SELECT id FROM lists WHERE owner_id = auth.user_id() + queries: + - "SELECT lists.uuid AS id, lists.created_at, lists.name, lists.owner_id FROM lists WHERE lists.id IN user_lists_param" + - "SELECT todos.uuid AS id, todos.created_at, todos.completed_at, todos.description, todos.completed, todos.created_by, todos.list_uuid FROM todos WHERE todos.list_id = user_lists_param" +``` We can now move on to updating the client to use UUIDs. diff --git a/client-sdks/frameworks/expo-go-support.mdx b/client-sdks/frameworks/expo-go-support.mdx index 00980919b..2f658c08d 100644 --- a/client-sdks/frameworks/expo-go-support.mdx +++ b/client-sdks/frameworks/expo-go-support.mdx @@ -109,7 +109,7 @@ export default function HomeScreen() { After adding PowerSync to your app: -1. [**Define what data to sync by setting up Sync Rules**](/sync/rules/overview) +1. [**Define what data to sync by setting up Sync Streams**](/sync/streams/overview) 2. [**Implement your SQLite client schema**](/client-sdks/reference/react-native-and-expo#1-define-the-client-side-schema) 3. [**Connect to PowerSync and your backend**](/client-sdks/reference/react-native-and-expo#3-integrate-with-your-backend) diff --git a/client-sdks/infinite-scrolling.mdx b/client-sdks/infinite-scrolling.mdx index 591bf382a..b5f9d2ed4 100644 --- a/client-sdks/infinite-scrolling.mdx +++ b/client-sdks/infinite-scrolling.mdx @@ -17,13 +17,11 @@ This means that in many cases, you can sync a sufficient amount of data to let a | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | It works offline and is low-latency (data loads quickly from the local database). We don't need to load data from the backend via the network when the user reaches the bottom of the page/feed/list. | There will be cases where this approach won't work because the total volume of data might become too large for the local database - for example, when there's a wide range of tables that the user needs to be able to infinite scroll. Your app allows the user to apply filters to the displayed data, which results in fewer pages displayed from a large dataset, and therefore limited scrolling. | -### 2) Control data sync using subscription or client parameters +### 2) Control data sync using subscription parameters -**Sync Streams** (recommended): Use [subscription parameters](/sync/streams/parameters#subscription-parameters) to subscribe to specific data on demand. For example, a client can subscribe to a specific "page" of data when the user scrolls to it. This is more flexible than client parameters — each subscription is independent and multiple tabs/views can subscribe with different parameters simultaneously. +Use [subscription parameters](/sync/streams/parameters#subscription-parameters) to subscribe to specific data on demand. For example, a client can subscribe to a specific "page" of data when the user scrolls to it. Each subscription is independent, so multiple tabs or views can subscribe with different parameters at the same time. Subscription parameters come from the client, so use them to select data, not for access control. Keep filtering by [auth parameters](/sync/streams/parameters#auth-parameters) such as `auth.user_id()`. -**Sync Rules** (legacy): PowerSync supports the use of [client parameters](/sync/rules/client-parameters) which are specified directly by the client. The app can dynamically change these parameters on the client-side and they can be accessed in Sync Rules on the server-side. The developer can use these parameters to limit/control which data is synced, but since they are not trusted (because they are not passed via the JWT authentication token) they should not be used for access control. You should still filter data by e.g. user ID for access control purposes (using [token parameters](/sync/rules/parameter-queries) from the JWT). - -Usage example: To lazy-load/lazy-sync data for infinite scrolling, you could split your data into 'pages' and use a subscription parameter (Sync Streams) or client parameter (Sync Rules) to specify which pages to sync to a user. +Usage example: To lazy-load/lazy-sync data for infinite scrolling, you could split your data into 'pages' and use a subscription parameter to specify which pages to sync to a user. | Pros | Cons | | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | @@ -39,7 +37,7 @@ In this scenario we can sync a smaller number of rows to the user initially. If ### 4) Client-side triggers a server-side function to flag data to sync -You could add a flag to certain records in your backend source database which are used by your [Sync Streams](/sync/streams/overview) or [Sync Rules](/sync/rules/overview) to determine which records to sync to specific users. Then your app could make an API call which triggers a function that updates the flags on certain records, causing more records to be synced to the user. +You could add a flag to certain records in your backend source database which are used by your [Sync Streams](/sync/streams/overview) to determine which records to sync to specific users. Then your app could make an API call which triggers a function that updates the flags on certain records, causing more records to be synced to the user. ## Questions? diff --git a/docs.json b/docs.json index 50a4eec91..cb659808b 100644 --- a/docs.json +++ b/docs.json @@ -235,7 +235,28 @@ "sync/rules/data-queries", "sync/rules/many-to-many-join-tables", "sync/rules/client-parameters", - "sync/grammar/sync-rules/index" + { + "group": "Reference", + "pages": [ + "sync/rules/supported-sql", + "sync/grammar/sync-rules/index", + "sync/rules/types" + ] + }, + { + "group": "Advanced", + "pages": [ + "sync/rules/prioritized-sync", + "sync/rules/client-id", + "sync/rules/case-sensitivity", + "sync/rules/storage-version-4", + "sync/rules/sync-data-by-time", + "sync/rules/schemas-and-connections", + "sync/rules/multiple-client-versions", + "sync/rules/partitioned-tables", + "sync/rules/sharded-databases" + ] + } ] } ] @@ -800,10 +821,6 @@ "source": "/usage/sync-rules/operators-and-functions", "destination": "/sync/supported-sql" }, - { - "source": "/sync/rules/supported-sql", - "destination": "/sync/supported-sql" - }, { "source": "/usage/sync-rules/advanced-topics", "destination": "/sync/advanced/overview" diff --git a/handling-writes/custom-conflict-resolution.mdx b/handling-writes/custom-conflict-resolution.mdx index ce369b90c..b5e932608 100644 --- a/handling-writes/custom-conflict-resolution.mdx +++ b/handling-writes/custom-conflict-resolution.mdx @@ -34,7 +34,7 @@ When data changes on the server: 1. **Source database updates** - Direct writes or changes from other clients 2. **PowerSync Service detects changes** - Through replication stream -3. **Clients download updates** - Based on their Sync Streams (or legacy Sync Rules) +3. **Clients download updates** - Based on their Sync Streams 4. **Local SQLite updates** - Changes merge into the client's database **Conflicts arise when**: Multiple clients modify the same row (or fields) before syncing, or when a client's changes conflict with server-side rules. @@ -505,32 +505,17 @@ CREATE TABLE write_conflicts ( ### Step 2: Sync Conflicts to Clients -**Sync Streams / Sync Rules:** - - - - ```yaml - config: - edition: 3 - streams: - user_data: - queries: - - SELECT * FROM tasks WHERE user_id = auth.user_id() - - SELECT * FROM write_conflicts WHERE user_id = auth.user_id() AND NOT resolved - ``` - - - ```yaml - bucket_definitions: - user_data: - parameters: - - SELECT request.user_id() as user_id - data: - - SELECT * FROM tasks WHERE user_id = bucket.user_id - - SELECT * FROM write_conflicts WHERE user_id = bucket.user_id AND resolved = FALSE - ``` - - +**Sync Streams:** + +```yaml +config: + edition: 3 +streams: + user_data: + queries: + - SELECT * FROM tasks WHERE user_id = auth.user_id() + - SELECT * FROM write_conflicts WHERE user_id = auth.user_id() AND NOT resolved +``` ### Step 3: Record Conflicts in Backend @@ -863,7 +848,7 @@ For scenarios where you just need to record changes without tracking their statu How it works: - Mark the table as `insertOnly: true` in your client schema -- Don't include the `field_changes` table in your Sync Rules +- Don't include the `field_changes` table in your Sync Streams - Changes are uploaded to the server but never downloaded back to clients **Client schema:** @@ -893,7 +878,7 @@ For scenarios where you want to show sync status temporarily but don't need a pe How it works: - Use a normal table on the client (not `insertOnly`) -- Don't include the `field_changes` table in your Sync Rules +- Don't include the `field_changes` table in your Sync Streams - Pending changes stay on the client until they're uploaded and the server processes them - Once the server processes a change and PowerSync syncs the next checkpoint, the change automatically disappears from the client @@ -932,7 +917,7 @@ function SyncIndicator({ taskId }: { taskId: string }) { **When to use:** Showing "syncing..." indicators, temporary status tracking without long-term storage overhead, cases where you want automatic cleanup after sync. -**Tradeoff:** Can't show detailed server-side error messages (unless the server writes to a separate errors table that *is* in Sync Rules). No long-term history on the client. +**Tradeoff:** Can't show detailed server-side error messages (unless the server writes to a separate errors table that *is* in Sync Streams). No long-term history on the client. ## Strategy 7: Cumulative Operations (Inventory) diff --git a/integrations/neon.mdx b/integrations/neon.mdx index 554d57f77..2230273bf 100644 --- a/integrations/neon.mdx +++ b/integrations/neon.mdx @@ -137,65 +137,40 @@ PowerSync uses logical replication to sync data from your Neon database. ### Configure Sync Streams -[Sync Streams](/sync/streams/overview) (or legacy [Sync Rules](/sync/rules/overview)) allow developers to control which data gets synced to which user devices using a SQL-like syntax in a YAML file. For the demo app, we're going to specify that each user can only see their own notes (plus any shared notes). +[Sync Streams](/sync/streams/overview) allow developers to control which data gets synced to which user devices using a SQL-like syntax in a YAML file. For the demo app, we're going to specify that each user can only see their own notes (plus any shared notes). -1. In the PowerSync Dashboard, select your project and instance and go to the **Sync Streams** view (shown as **Sync Rules** if using legacy Sync Rules). +1. In the PowerSync Dashboard, select your project and instance and go to the **Sync Streams** view. 2. Edit the Sync Config in the editor and replace the contents with the below: - - - ```yaml - config: - edition: 3 - - streams: - user_notes: - auto_subscribe: true - # Sync notes and paragraphs belonging to the authenticated user - queries: - - SELECT * FROM notes WHERE owner_id = auth.user_id() - - SELECT paragraphs.* FROM paragraphs - INNER JOIN notes ON notes.id = paragraphs.note_id - WHERE notes.owner_id = auth.user_id() - shared_notes: - auto_subscribe: true - # Sync all shared notes to all users (not recommended for production) - queries: - - SELECT * FROM notes WHERE shared = TRUE - - SELECT paragraphs.* FROM paragraphs - INNER JOIN notes ON notes.id = paragraphs.note_id - WHERE notes.shared = TRUE - ``` - - - ```yaml - config: - edition: 2 - - bucket_definitions: - by_user: - # Only sync rows belonging to the user - parameters: SELECT id as note_id FROM notes WHERE owner_id = request.user_id() - data: - - SELECT * FROM notes WHERE id = bucket.note_id - - SELECT * FROM paragraphs WHERE note_id = bucket.note_id - # Sync all shared notes to all users (not recommended for production) - shared_notes: - parameters: SELECT id as note_id from notes where shared = TRUE - data: - - SELECT * FROM notes WHERE id = bucket.note_id - - SELECT * FROM paragraphs WHERE note_id = bucket.note_id - ``` - - +```yaml +config: + edition: 3 + +streams: + user_notes: + auto_subscribe: true + # Sync notes and paragraphs belonging to the authenticated user + queries: + - SELECT * FROM notes WHERE owner_id = auth.user_id() + - SELECT paragraphs.* FROM paragraphs + INNER JOIN notes ON notes.id = paragraphs.note_id + WHERE notes.owner_id = auth.user_id() + shared_notes: + auto_subscribe: true + # Sync all shared notes to all users (not recommended for production) + queries: + - SELECT * FROM notes WHERE shared = TRUE + - SELECT paragraphs.* FROM paragraphs + INNER JOIN notes ON notes.id = paragraphs.note_id + WHERE notes.shared = TRUE +``` 3. Click **"Validate"** and ensure there are no errors. This validates your Sync Config against your Postgres database. 4. Click **"Deploy"** to deploy your Sync Config. - For additional information on PowerSync's Sync Streams, refer to the [Sync Streams](/sync/streams/overview) documentation. -- For legacy Sync Rules, refer to the [Sync Rules](/sync/rules/overview) documentation. ## Test Everything (Using Our Demo App) @@ -239,11 +214,11 @@ Once signed in to the demo app, you should see a blank list of notes, so go ahea ### Test Sync (Optional) -During development, you can use the **Sync Test** feature in the PowerSync Dashboard to validate your Sync Rules: +During development, you can use the **Sync Test** feature in the PowerSync Dashboard to validate your Sync Streams: 1. Click on **"Sync Test"** in the PowerSync Dashboard. 2. Enter the UUID of a user in your Neon Auth database to generate a test JWT. -3. Click **"Launch Sync Diagnostics Client"** to test the Sync Rules. +3. Click **"Launch Sync Diagnostics Client"** to test the Sync Streams. For more information, explore the [PowerSync docs](/) or join us on [our community Discord](https://discord.gg/powersync) where our team is always available to answer questions. diff --git a/integrations/serverpod.mdx b/integrations/serverpod.mdx index 05f404b9b..029208004 100644 --- a/integrations/serverpod.mdx +++ b/integrations/serverpod.mdx @@ -265,7 +265,7 @@ For security, it is crucial each user only has access to their own bucket. This 1. When a client connects to PowerSync, it fetches an authentication token from your Serverpod instance. 2. Your Dart backend logic returns a JWT describing what data the user should have access to. -3. In the `sync_rules` section, you reference properties of the created JWTs to control data visible to the connecting clients. +3. In the `sync_config` section, you reference properties of the created JWTs to control data visible to the connecting clients. In this guide, we will use a single virtual user for everything. For real projects, follow [Serverpod documentation on authentication](https://docs.serverpod.dev/concepts/authentication/setup). diff --git a/integrations/supabase/guide.mdx b/integrations/supabase/guide.mdx index 5ce4b29b3..55dfe23fb 100644 --- a/integrations/supabase/guide.mdx +++ b/integrations/supabase/guide.mdx @@ -9,7 +9,7 @@ import SupabaseConnection from '/snippets/supabase-database-connection.mdx'; import PostgresPowerSyncUser from '/snippets/postgres-powersync-user.mdx'; import PostgresPowerSyncPublication from '/snippets/postgres-powersync-publication.mdx'; -This guide shows you how to configure PowerSync with [Supabase](https://supabase.com/). PowerSync syncs selected Supabase Postgres data into client-side SQLite. Client writes (mutations) are commited locally, queued by the SDK, and uploaded via Supabase's client libraries. Your app reads and writes from this local SQLite, so it stays highly responsive even in poor network conditions. +This guide shows you how to configure PowerSync with [Supabase](https://supabase.com/). PowerSync syncs selected Supabase Postgres data into client-side SQLite. Client writes (mutations) are committed locally, queued by the SDK, and uploaded via Supabase's client libraries. Your app reads and writes from this local SQLite, so it stays highly responsive even in poor network conditions.