forked from TanStack/db
-
Notifications
You must be signed in to change notification settings - Fork 0
[POC] PowerSync Integration #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
stevensJourney
wants to merge
60
commits into
main
Choose a base branch
from
powersync
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 13 commits
Commits
Show all changes
60 commits
Select commit
Hold shift + click to select a range
27a3728
wip: PowerSync collections
stevensJourney e88623c
Add support for transactions with multiple collection types
stevensJourney 352829e
Optimize transaction waiting
stevensJourney 1c75d3d
Improve test stability
stevensJourney 50f3383
Merge remote-tracking branch 'upstream/main' into powersync
stevensJourney a892acc
Improve cleanup behaviour
stevensJourney 7d9ff73
Add rollback test
stevensJourney d5b3d99
update dependencies
stevensJourney cc42e94
Add live query test
stevensJourney d1de549
Add docs for PowerSync collection
stevensJourney c0a212a
Merge branch 'main' into powersync
stevensJourney ccba6ef
Add Changeset
stevensJourney c887d90
Added schema conversion and validation
stevensJourney 860fa26
ensure observers are ready before proceeding with mutations
stevensJourney ffa68d1
Add logging
stevensJourney 79abf05
Implement batching during initial sync
stevensJourney 237ed35
Update log messages. Avoid requirement for NPM install scripts.
stevensJourney 8d489e9
Schemas Step 1: Infer types from PowerSync schema table.
stevensJourney 4692c8b
Support input schema validations with Zod
stevensJourney fb45f02
update readme
stevensJourney 7030117
Update doc comments. Code cleanup.
stevensJourney 829ce64
More doc cleanup
stevensJourney dd0cbc8
README cleanup
stevensJourney dc0b361
Merge branch 'main' into powersync
stevensJourney e26bf27
Cleanup tests
stevensJourney e207268
Update PowerSync dependencies
stevensJourney e94cadf
Properly constrain types
stevensJourney b7fc0ff
Allow custom input schema types
stevensJourney 8187c6d
Support `orderBy` and `limit` in `currentStateAsChanges` (#701)
kevin-dp 96ad9d3
Fix bug when moving an orderBy window that has an infinite limit (#705)
kevin-dp 36d2439
ci: Version Packages (#702)
github-actions[bot] af6a4e4
docs: document findOne method in live queries guide (#699)
KyleAMathews 5950583
Manual writes should validate against the synced store, not the combi…
KyleAMathews 16dbfe3
fix(query-db-collection): respect QueryClient defaultOptions when not…
KyleAMathews 5ab979c
ci: Version Packages (#711)
github-actions[bot] 3c9526c
fix: dedupe filtering for non-optimistic mutations (#715)
mpotter d8ef559
ci: Version Packages (#716)
github-actions[bot] 970616b
fix(collection): fire status:change event before cleaning up event ha…
KyleAMathews ac42951
ci: Version Packages (#718)
github-actions[bot] 518ecda
chore(deps): update all non-major dependencies (#724)
renovate[bot] c2a5c28
feat: add exact refetch targeting and improve utils.refetch() behavio…
lucasweng 2d4d5e1
ci: Version Packages (#726)
github-actions[bot] fbfa75a
Support better schema type conversions
stevensJourney da9ec60
docuement deserialization errors
stevensJourney c439899
Fix typo in READMe
stevensJourney db3eae5
Add type to README example
stevensJourney 6738247
Feat: Add support for custom parsers/serializers in LocalStorage coll…
sadkebab 7b9c681
ci: Version Packages (#731)
github-actions[bot] 7e9a1d8
Fix flaky test (#735)
KyleAMathews 9e4cbef
Document how to destructure in Svelte (#733)
KyleAMathews f8a979b
Fix: Optimizer Missing Final Step - Combine Remaining WHERE Clauses (…
KyleAMathews 979a66f
Enable auto-indexing for nested field paths (#728)
KyleAMathews d2b569c
Investigate Size Change action minification (#736)
KyleAMathews cb25623
feat: Add paced mutations with timing strategies (#704)
KyleAMathews 48b8e8f
ci: Version Packages (#739)
github-actions[bot] fe165e5
update PowerSync packages
stevensJourney 15e981f
Merge remote-tracking branch 'upstream/main' into powersync
stevensJourney e4024a0
set author to POWERSYNC
stevensJourney 81230f9
rename serlization.ts → serialization.ts
stevensJourney 718c4f9
use MIT license
stevensJourney File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@tanstack/powersync-db-collection": minor | ||
| --- | ||
|
|
||
| Initial Release |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,202 @@ | ||
| --- | ||
| title: PowerSync Collection | ||
| --- | ||
|
|
||
| # PowerSync Collection | ||
|
|
||
| PowerSync collections provide seamless integration between TanStack DB and [PowerSync](https://powersync.com), enabling automatic synchronization between your in-memory TanStack DB collections and PowerSync's SQLite database. This gives you offline-ready persistence, real-time sync capabilities, and powerful conflict resolution. | ||
|
|
||
| ## Overview | ||
|
|
||
| The `@tanstack/powersync-db-collection` package allows you to create collections that: | ||
|
|
||
| - Automatically mirror the state of an underlying PowerSync SQLite database | ||
| - Reactively update when PowerSync records change | ||
| - Support optimistic mutations with rollback on error | ||
| - Provide persistence handlers to keep PowerSync in sync with TanStack DB transactions | ||
| - Use PowerSync's efficient SQLite-based storage engine | ||
| - Work with PowerSync's real-time sync features for offline-first scenarios | ||
| - Leverage PowerSync's built-in conflict resolution and data consistency guarantees | ||
| - Enable real-time synchronization with PostgreSQL, MongoDB and MySQL backends | ||
|
|
||
| ## 1. Installation | ||
|
|
||
| Install the PowerSync collection package along with your preferred framework integration. | ||
| PowerSync currently works with Web, React Native and Node.js. The examples below use the Web SDK. | ||
| See the PowerSync quickstart [docs](https://docs.powersync.com/installation/quickstart-guide) for more details. | ||
|
|
||
| ```bash | ||
| npm install @tanstack/powersync-db-collection @powersync/web @journeyapps/wa-sqlite | ||
| ``` | ||
|
|
||
| ### 2. Create a PowerSync Database and Schema | ||
|
|
||
| ```ts | ||
| import { Schema, Table, column } from "@powersync/web" | ||
|
|
||
| // Define your schema | ||
| const APP_SCHEMA = new Schema({ | ||
| documents: new Table({ | ||
| name: column.text, | ||
| content: column.text, | ||
| created_at: column.text, | ||
| updated_at: column.text, | ||
| }), | ||
| }) | ||
|
|
||
| type Document = (typeof APP_SCHEMA)["types"]["documents"] | ||
|
|
||
| // Initialize PowerSync database | ||
| const db = new PowerSyncDatabase({ | ||
| database: { | ||
| dbFilename: "app.sqlite", | ||
| }, | ||
| schema: APP_SCHEMA, | ||
| }) | ||
| ``` | ||
|
|
||
| ### 3. (optional) Configure Sync with a Backend | ||
|
|
||
| ```ts | ||
| import { | ||
| AbstractPowerSyncDatabase, | ||
| PowerSyncBackendConnector, | ||
| PowerSyncCredentials, | ||
| } from "@powersync/web" | ||
|
|
||
| // TODO implement your logic here | ||
| class Connector implements PowerSyncBackendConnector { | ||
| fetchCredentials: () => Promise<PowerSyncCredentials | null> | ||
|
|
||
| /** Upload local changes to the app backend. | ||
| * | ||
| * Use {@link AbstractPowerSyncDatabase.getCrudBatch} to get a batch of changes to upload. | ||
| * | ||
| * Any thrown errors will result in a retry after the configured wait period (default: 5 seconds). | ||
| */ | ||
| uploadData: (database: AbstractPowerSyncDatabase) => Promise<void> | ||
| } | ||
|
|
||
| // Configure the client to connect to a PowerSync service and your backend | ||
| db.connect(new Connector()) | ||
| ``` | ||
|
|
||
| ### 4. Create a TanStack DB Collection | ||
|
|
||
| There are two ways to create a collection: using type inference or using schema validation. | ||
|
|
||
| #### Option 1: Using Type Inference | ||
|
|
||
| ```ts | ||
| import { createCollection } from "@tanstack/react-db" | ||
| import { powerSyncCollectionOptions } from "@tanstack/powersync-db-collection" | ||
|
|
||
| const documentsCollection = createCollection( | ||
| powerSyncCollectionOptions<Document>({ | ||
| database: db, | ||
| tableName: "documents", | ||
| }) | ||
| ) | ||
| ``` | ||
|
|
||
| #### Option 2: Using Schema Validation | ||
|
|
||
| ```ts | ||
| import { createCollection } from "@tanstack/react-db" | ||
| import { | ||
| powerSyncCollectionOptions, | ||
| convertPowerSyncSchemaToSpecs, | ||
| } from "@tanstack/powersync-db-collection" | ||
|
|
||
| // Convert PowerSync schema to TanStack DB schema | ||
| const schemas = convertPowerSyncSchemaToSpecs(APP_SCHEMA) | ||
|
|
||
| const documentsCollection = createCollection( | ||
| powerSyncCollectionOptions({ | ||
| database: db, | ||
| tableName: "documents", | ||
| schema: schemas.documents, // Use schema for runtime type validation | ||
| }) | ||
| ) | ||
| ``` | ||
|
|
||
| With schema validation, the collection will validate all inputs at runtime to ensure they match the PowerSync schema types. This provides an extra layer of type safety beyond TypeScript's compile-time checks. | ||
|
|
||
| ## Features | ||
|
|
||
| ### Offline-First | ||
|
|
||
| PowerSync collections are offline-first by default. All data is stored locally in a SQLite database, allowing your app to work without an internet connection. Changes are automatically synced when connectivity is restored. | ||
|
|
||
| ### Real-Time Sync | ||
|
|
||
| When connected to a PowerSync backend, changes are automatically synchronized in real-time across all connected clients. The sync process handles: | ||
|
|
||
| - Bi-directional sync with the server | ||
| - Conflict resolution | ||
| - Queue management for offline changes | ||
| - Automatic retries on connection loss | ||
|
|
||
| ### Optimistic Updates | ||
|
|
||
| Updates to the collection are applied optimistically to the local state first, then synchronized with PowerSync and the backend. If an error occurs during sync, the changes are automatically rolled back. | ||
|
|
||
| ## Configuration Options | ||
|
|
||
| The `powerSyncCollectionOptions` function accepts the following options: | ||
|
|
||
| ```ts | ||
| interface PowerSyncCollectionConfig<T> { | ||
| database: PowerSyncDatabase // PowerSync database instance | ||
| tableName: string // Name of the table in PowerSync | ||
| schema?: Schema // Optional schema for validation | ||
| } | ||
| ``` | ||
|
|
||
| ## Advanced Transactions | ||
|
|
||
| When you need more control over transaction handling, such as batching multiple operations or handling complex transaction scenarios, you can use PowerSync's transaction system directly with TanStack DB transactions. | ||
|
|
||
| ```ts | ||
| import { createTransaction } from "@tanstack/react-db" | ||
| import { PowerSyncTransactor } from "@tanstack/powersync-db-collection" | ||
|
|
||
| // Create a transaction that won't auto-commit | ||
| const batchTx = createTransaction({ | ||
| autoCommit: false, | ||
| mutationFn: async ({ transaction }) => { | ||
| // Use PowerSyncTransactor to apply the transaction to PowerSync | ||
| await new PowerSyncTransactor({ database: db }).applyTransaction( | ||
| transaction | ||
| ) | ||
| }, | ||
| }) | ||
|
|
||
| // Perform multiple operations in the transaction | ||
| batchTx.mutate(() => { | ||
| // Add multiple documents in a single transaction | ||
| for (let i = 0; i < 5; i++) { | ||
| documentsCollection.insert({ | ||
| id: crypto.randomUUID(), | ||
| name: `Document ${i}`, | ||
| content: `Content ${i}`, | ||
| created_at: new Date().toISOString(), | ||
| updated_at: new Date().toISOString(), | ||
| }) | ||
| } | ||
| }) | ||
|
|
||
| // Commit the transaction | ||
| await batchTx.commit() | ||
|
|
||
| // Wait for the changes to be persisted | ||
| await batchTx.isPersisted.promise | ||
| ``` | ||
|
|
||
| This approach allows you to: | ||
|
|
||
| - Batch multiple operations into a single transaction | ||
| - Control when the transaction is committed | ||
| - Ensure all operations are atomic | ||
| - Wait for persistence confirmation | ||
| - Handle complex transaction scenarios |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| # @tanstack/powersync-db-collection |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| { | ||
| "name": "@tanstack/powersync-db-collection", | ||
| "description": "PowerSync collection for TanStack DB", | ||
| "version": "0.0.0", | ||
| "dependencies": { | ||
| "@standard-schema/spec": "^1.0.0", | ||
| "@tanstack/db": "workspace:*", | ||
| "@tanstack/store": "^0.7.7", | ||
| "debug": "^4.4.3", | ||
| "p-defer": "^4.0.1" | ||
| }, | ||
| "peerDependencies": { | ||
| "@powersync/common": "^1.39.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@powersync/common": "^1.39.0", | ||
| "@powersync/better-sqlite3": "^0.2.0", | ||
| "@powersync/node": "^0.11.0", | ||
| "@types/debug": "^4.1.12", | ||
| "@vitest/coverage-istanbul": "^3.2.4" | ||
| }, | ||
| "exports": { | ||
| ".": { | ||
| "import": { | ||
| "types": "./dist/esm/index.d.ts", | ||
| "default": "./dist/esm/index.js" | ||
| }, | ||
| "require": { | ||
| "types": "./dist/cjs/index.d.cts", | ||
| "default": "./dist/cjs/index.cjs" | ||
| } | ||
| }, | ||
| "./package.json": "./package.json" | ||
| }, | ||
| "files": [ | ||
| "dist", | ||
| "src" | ||
| ], | ||
| "main": "dist/cjs/index.cjs", | ||
| "module": "dist/esm/index.js", | ||
| "packageManager": "[email protected]", | ||
| "author": "JOURNEYAPPS", | ||
| "license": "Apache-2.0", | ||
|
||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/TanStack/db.git", | ||
| "directory": "packages/powersync-db-collection" | ||
| }, | ||
| "homepage": "https://tanstack.com/db", | ||
| "keywords": [ | ||
| "powersync", | ||
| "realtime", | ||
| "local-first", | ||
| "sync-engine", | ||
| "sync", | ||
| "replication", | ||
| "opfs", | ||
| "indexeddb", | ||
| "localstorage", | ||
| "optimistic", | ||
| "typescript" | ||
| ], | ||
| "scripts": { | ||
| "build": "vite build", | ||
| "dev": "vite build --watch", | ||
| "lint": "eslint . --fix", | ||
| "test": "npx vitest --run" | ||
| }, | ||
| "sideEffects": false, | ||
| "type": "module", | ||
| "types": "dist/esm/index.d.ts" | ||
| } | ||
54 changes: 54 additions & 0 deletions
54
packages/powersync-db-collection/src/PendingOperationStore.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| import pDefer from "p-defer" | ||
| import type { DiffTriggerOperation } from "@powersync/common" | ||
| import type { DeferredPromise } from "p-defer" | ||
|
|
||
| export type PendingOperation = { | ||
| tableName: string | ||
| operation: DiffTriggerOperation | ||
| id: string | ||
| timestamp: string | ||
| } | ||
|
|
||
| /** | ||
| * Optimistic mutations have their optimistic state discarded once transactions have | ||
| * been applied. | ||
| * We need to ensure that an applied transaction has been observed by the sync diff trigger | ||
| * before resoling the transaction application call. | ||
| * This store allows registering a wait for a pending operation to have been observed. | ||
| */ | ||
| export class PendingOperationStore { | ||
| private pendingOperations = new Map<PendingOperation, DeferredPromise<void>>() | ||
|
|
||
| /** | ||
| * Globally accessible PendingOperationStore | ||
| */ | ||
| static GLOBAL = new PendingOperationStore() | ||
|
|
||
| /** | ||
| * @returns A promise which will resolve once the specified operation has been seen. | ||
| */ | ||
| waitFor(operation: PendingOperation): Promise<void> { | ||
| const managedPromise = pDefer<void>() | ||
| this.pendingOperations.set(operation, managedPromise) | ||
| return managedPromise.promise | ||
| } | ||
|
|
||
| /** | ||
| * Marks a set of operations as seen. This will resolve any pending promises. | ||
| */ | ||
| resolvePendingFor(operations: Array<PendingOperation>) { | ||
| for (const operation of operations) { | ||
| for (const [pendingOp, deferred] of this.pendingOperations.entries()) { | ||
| if ( | ||
| pendingOp.tableName == operation.tableName && | ||
| pendingOp.operation == operation.operation && | ||
| pendingOp.id == operation.id && | ||
| pendingOp.timestamp == operation.timestamp | ||
| ) { | ||
| deferred.resolve() | ||
| this.pendingOperations.delete(pendingOp) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.