Skip to content

Commit cfee1d4

Browse files
authored
Merge pull request #512 from powersync-ja/bucket-priorities-in-yjs
Adopt bucket priorities in yjs demo
2 parents fcb9d58 + db7d0d3 commit cfee1d4

File tree

9 files changed

+125
-883
lines changed

9 files changed

+125
-883
lines changed

demos/yjs-react-supabase-text-collab/README.md

+8-35
Original file line numberDiff line numberDiff line change
@@ -23,28 +23,9 @@ This demo app uses Supabase as its Postgres database and backend:
2323

2424
1. [Create a new project on the Supabase dashboard](https://supabase.com/dashboard/projects).
2525
2. Go to the Supabase SQL Editor for your new project and execute the SQL statements in [`database.sql`](database.sql) to create the database schema, database functions, and publication needed for PowerSync.
26+
3. Enable "anonymous sign-ins" for the project [here](https://supabase.com/dashboard/project/_/auth/providers).
2627

27-
### 3. Auth setup and Supabase edge functions
28-
29-
For ease of demoing, this demo app uses anonymous authentication. The below instructions are derived from the [powersync-jwks-example README](https://github.com/powersync-ja/powersync-jwks-example):
30-
31-
1. Install [Deno](https://deno.com/) and [Supabase CLI](https://supabase.com/docs/guides/cli/getting-started) if you don't have them already.
32-
2. Clone the [powersync-jwks-example](https://github.com/powersync-ja/powersync-jwks-example) repo.
33-
3. In the [powersync-jwks-example](https://github.com/powersync-ja/powersync-jwks-example) repo directory, run the script to generate a keypair:
34-
35-
```bash
36-
deno run generate-keys.ts
37-
```
38-
39-
4. Then use `supabase secrets set` as shown in the terminal output to update the generated keys on Supabase (`POWERSYNC_PUBLIC_KEY` and `POWERSYNC_PRIVATE_KEY`). You will need the project ref of the Supabase project you created previously.
40-
5. Switch back to the `powersync-supabase-yjs-text-collab-demo` repo directory and deploy the `powersync-jwks` and `powersync-auth-anonymous` edge functions to Supabase: (Note that the Supabase CLI requires [Docker Desktop](https://docs.docker.com/desktop/) to be installed for this step)
41-
42-
```bash
43-
supabase functions deploy --no-verify-jwt powersync-jwks
44-
supabase functions deploy powersync-auth-anonymous
45-
```
46-
47-
### 4. Create new project on PowerSync and connect to Supabase/Postgres
28+
### 3. Create new project on PowerSync and connect to Supabase/Postgres
4829

4930
If you don't have a PowerSync account yet, [sign up here](https://accounts.journeyapps.com/portal/free-trial?powersync=true).
5031

@@ -57,24 +38,16 @@ Then, in the [PowerSync dashboard](https://powersync.journeyapps.com/), create a
5738
5. Input the credentials from the project you created in Supabase. In the Supabase dashboard, under your project you can go to "Project Settings" and then "Database" and choose "URI" under "Connection string", untick the "Use connection pooling" option, and then copy & paste the connection string into the PowerSync dashboard "URI" field, and then enter your database password at the "Password" field.
5839
6. Click the "Test connection" button and you should see "Connection success!"
5940
7. Click on the "Credentials" tab of the "Edit Instance" dialog.
60-
8. Tick the "Use Supabase Auth" checkbox
61-
9. Enter the URL of your `powersync-jwks` edge function at the "JWKS URI" field. It should be in the format `https://<supabase-project-ref>.supabase.co/functions/v1/powersync-jwks`. If needed, you can find the URL in the Supabase dashboard by going to your project and then to "Edge Functions".
62-
10. Click "Save" to save all the changes to your PowerSync instance. The instance will now be deployed — this may take a minute or two.
63-
64-
### 5. Set `POWERSYNC_URL` secret on Supabase
65-
66-
1. Now that your PowerSync instance is created, you should see a URL for it on the right side of the PowerSync dashboard in the "Deploy logs" panel. Click the button next to the URL to copy it.
67-
2. Use the copied value to set the `POWERSYNC_URL` secret to be used by your Supabase edge functions: (make sure there is no trailing slash at the end of the URL)
68-
69-
`supabase secrets set POWERSYNC_URL=https://<powersync-instance-id>.powersync.journeyapps.com`
41+
8. Tick the "Use Supabase Auth" checkbox and configure the JWT secret.
42+
9. Click "Save" to save all the changes to your PowerSync instance. The instance will now be deployed — this may take a minute or two.
7043

71-
### 6. Create Sync Rules on PowerSync
44+
### 4. Create Sync Rules on PowerSync
7245

7346
1. Open the [`sync-rules.yaml`](sync-rules.yaml) in this repo and copy the contents.
7447
2. In the [PowerSync dashboard](https://powersync.journeyapps.com/), paste that into the 'sync-rules.yaml' editor panel.
7548
3. Click the "Deploy sync rules" button and select your PowerSync instance from the drop-down list.
7649

77-
### 7. Set up local environment variables
50+
### 5. Set up local environment variables
7851

7952
To set up the environment variables for the demo app:
8053

@@ -87,9 +60,9 @@ cp .env.local.template .env.local
8760
2. Edit `.env.local` and populate the relevant values:
8861
- Set `VITE_SUPABASE_URL` to your Supabase project URL. You can find this by going to the main page for the project on the Supabase dashboard and then look for "Project URL" in the "Project API" panel.
8962
- Set `VITE_SUPABASE_ANON_KEY` to your Supabase API key. This can be found right below the Project URL on the Supabase dashboard.
90-
- Set `VITE_POWERSYNC_URL` to your PowerSync instance URL (this is the same URL from step 5)
63+
- Set `VITE_POWERSYNC_URL` to your PowerSync instance URL (this is the same URL from step 3)
9164

92-
### 8. Run the demo app
65+
### 6. Run the demo app
9366

9467
In this directory, run the following to start the development server:
9568

demos/yjs-react-supabase-text-collab/package.json

-17
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,9 @@
99
"start": "pnpm build && pnpm preview"
1010
},
1111
"dependencies": {
12-
"@emotion/react": "^11.11.4",
13-
"@emotion/styled": "^11.11.0",
14-
"@fontsource/roboto": "^5.0.12",
1512
"@powersync/react": "workspace:*",
1613
"@powersync/web": "workspace:*",
1714
"@journeyapps/wa-sqlite": "^1.2.0",
18-
"@lexical/react": "^0.11.3",
19-
"@mui/icons-material": "^5.15.12",
2015
"@mui/material": "^5.15.12",
2116
"@mui/x-data-grid": "^6.19.6",
2217
"@supabase/supabase-js": "^2.39.8",
@@ -27,31 +22,19 @@
2722
"@tiptap/extension-task-list": "2.2.2",
2823
"@tiptap/react": "2.2.2",
2924
"@tiptap/starter-kit": "2.2.2",
30-
"d3": "^7.8.5",
31-
"fast-glob": "^3.3.2",
32-
"formik": "^2.4.5",
33-
"highlight.js": "^11.9.0",
3425
"js-logger": "^1.6.1",
3526
"lato-font": "^3.0.0",
36-
"lexical": "^0.11.3",
3727
"lib0": "^0.2.91",
38-
"lodash": "^4.17.21",
39-
"lowlight": "^2.9.0",
4028
"react": "^18.2.0",
4129
"react-dom": "^18.2.0",
4230
"react-router": "^6.22.3",
4331
"react-router-dom": "^6.22.3",
4432
"remixicon": "^2.5.0",
45-
"shiki": "^0.10.1",
46-
"simplify-js": "^1.2.4",
4733
"uuid": "^9.0.1",
48-
"y-prosemirror": "1.0.20",
49-
"y-protocols": "1.0.6",
5034
"yjs": "^13.6.14"
5135
},
5236
"devDependencies": {
5337
"@swc/core": "~1.6.0",
54-
"@types/lodash": "^4.17.0",
5538
"@types/node": "^20.11.26",
5639
"@types/react": "^18.2.65",
5740
"@types/react-dom": "^18.2.21",

demos/yjs-react-supabase-text-collab/src/app/editor/page.tsx

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { usePowerSync, useQuery, useStatus } from '@powersync/react';
2-
import { Box, Container, Typography } from '@mui/material';
2+
import { Box, Container, FormControlLabel, Switch, Typography } from '@mui/material';
33
import { useEffect, useMemo, useState } from 'react';
44
import MenuBar from '@/components/widgets/MenuBar';
55
import { PowerSyncYjsProvider } from '@/library/powersync/PowerSyncYjsProvider';
@@ -12,6 +12,7 @@ import StarterKit from '@tiptap/starter-kit';
1212
import * as Y from 'yjs';
1313
import './tiptap-styles.scss';
1414
import { useParams } from 'react-router-dom';
15+
import { connector } from '@/components/providers/SystemProvider';
1516

1617
export default function EditorPage() {
1718
const powerSync = usePowerSync();
@@ -24,6 +25,7 @@ export default function EditorPage() {
2425
}
2526

2627
const [totalDocUpdates, setTotalDocUpdates] = useState(0);
28+
const [allowUploads, setAllowUploads] = useState(connector.enableUploads);
2729

2830
const ydoc = useMemo(() => {
2931
return new Y.Doc();
@@ -36,6 +38,10 @@ export default function EditorPage() {
3638
};
3739
}, [ydoc, powerSync]);
3840

41+
useEffect(() => {
42+
connector.enableUploads = allowUploads;
43+
}, [allowUploads]);
44+
3945
// watch for total number of document updates changing to update the counter
4046
const { data: docUpdatesCount } = useQuery(
4147
'SELECT COUNT(*) as total_updates FROM document_updates WHERE document_id=?',
@@ -89,6 +95,10 @@ export default function EditorPage() {
8995
<Typography variant="caption" display="block" gutterBottom>
9096
{totalDocUpdates} total edit(s) in this document.
9197
</Typography>
98+
<FormControlLabel
99+
control={<Switch checked={allowUploads} onChange={(e) => setAllowUploads(e.target.checked)} />}
100+
label="Enable uploads"
101+
/>
92102
</Box>
93103
</>
94104
)}

demos/yjs-react-supabase-text-collab/src/app/page.tsx

+8-9
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import React from 'react';
22
import { CircularProgress, Grid, styled } from '@mui/material';
33
import { useSupabase } from '@/components/providers/SystemProvider';
44
import { useNavigate } from 'react-router-dom';
5+
import { usePowerSync } from '@powersync/react';
56

67
export default function EntryPage() {
78
const navigate = useNavigate();
89
const connector = useSupabase();
10+
const powerSync = usePowerSync();
911

1012
React.useEffect(() => {
1113
if (!connector) {
@@ -21,17 +23,14 @@ export default function EntryPage() {
2123
return;
2224
}
2325
// otherwise, create a new document
24-
const { data } = await connector.client
25-
.from('documents')
26-
.insert({
27-
title: 'Test Document ' + (1000 + Math.floor(Math.random() * 8999))
28-
})
29-
.select()
30-
.single();
26+
const results = await powerSync.execute('INSERT INTO documents(id, title) VALUES(uuid(), ?) RETURNING id', [
27+
'Test Document ' + (1000 + Math.floor(Math.random() * 8999))
28+
]);
29+
const documentId = results.rows!.item(0).id;
3130

3231
// redirect user to the document
33-
lastDocumentId = data.id;
34-
window.localStorage.setItem('lastDocumentId', lastDocumentId || '');
32+
lastDocumentId = documentId;
33+
window.localStorage.setItem('lastDocumentId', documentId);
3534
navigate('/editor/' + lastDocumentId);
3635
};
3736

demos/yjs-react-supabase-text-collab/src/library/powersync/SupabaseConnector.ts

+21-23
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,7 @@ import {
66
UpdateType
77
} from '@powersync/web';
88

9-
import {
10-
SupabaseClient,
11-
createClient,
12-
FunctionsHttpError,
13-
FunctionsRelayError,
14-
FunctionsFetchError
15-
} from '@supabase/supabase-js';
9+
import { createClient, SupabaseClient } from '@supabase/supabase-js';
1610

1711
export type SupabaseConfig = {
1812
supabaseUrl: string;
@@ -40,6 +34,8 @@ export class SupabaseConnector extends BaseObserver<SupabaseConnectorListener> i
4034
readonly client: SupabaseClient;
4135
readonly config: SupabaseConfig;
4236

37+
enableUploads: boolean = true;
38+
4339
constructor() {
4440
super();
4541
this.config = {
@@ -51,29 +47,31 @@ export class SupabaseConnector extends BaseObserver<SupabaseConnectorListener> i
5147
}
5248

5349
async fetchCredentials() {
54-
const { data, error } = await this.client.functions.invoke('powersync-auth-anonymous', {
55-
method: 'GET'
56-
});
57-
58-
if (error instanceof FunctionsHttpError) {
59-
const errorMessage = await error.context.json();
60-
console.log('Supabase edge function returned an error', errorMessage);
61-
} else if (error instanceof FunctionsRelayError) {
62-
console.log('Supabase edge function: Relay error:', error.message);
63-
} else if (error instanceof FunctionsFetchError) {
64-
console.log('Supabase edge function: Fetch error:', error.message);
50+
let {
51+
data: { session }
52+
} = await this.client.auth.getSession();
53+
if (session == null) {
54+
const { data, error } = await this.client.auth.signInAnonymously();
55+
if (error) {
56+
throw error;
57+
}
58+
session = data.session;
6559
}
66-
if (error) {
67-
throw error;
60+
if (session == null) {
61+
throw new Error(`Failed to get Supabase session`);
6862
}
69-
7063
return {
71-
endpoint: data.powersync_url,
72-
token: data.token
64+
endpoint: this.config.powersyncUrl,
65+
token: session.access_token
7366
};
7467
}
7568

7669
async uploadData(database: AbstractPowerSyncDatabase): Promise<void> {
70+
if (!this.enableUploads) {
71+
console.log('Skipping uploadData because uploads were disabled manually');
72+
return;
73+
}
74+
7775
const batch = await database.getCrudBatch(200);
7876

7977
if (!batch) {

demos/yjs-react-supabase-text-collab/supabase/functions/powersync-auth-anonymous/index.ts

-48
This file was deleted.

demos/yjs-react-supabase-text-collab/supabase/functions/powersync-jwks/index.ts

-23
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Sync-rule docs: https://docs.powersync.com/usage/sync-rules
22
bucket_definitions:
3-
global:
3+
documents:
44
data:
55
- SELECT * FROM documents
6+
updates:
7+
# Allow remote changes to be synchronized even while there are local changes
8+
priority: 0
9+
data:
610
- SELECT id, document_id, base64(update_data) as update_b64 FROM document_updates

0 commit comments

Comments
 (0)