Skip to content

Commit 4cc242e

Browse files
committed
First pass on SQLSync docs a'la a guide/walkthrough
1 parent 0b00082 commit 4cc242e

File tree

7 files changed

+372
-79
lines changed

7 files changed

+372
-79
lines changed

.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ indent_size = 2
1414
[*.md]
1515
indent_style = tab
1616
indent_size = 2
17-
max_line_length = 100
17+
max_line_length = 80

CONTRIBUTING.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Contributing to SQLSync
2+
3+
This document attempts to explain how to work on SQLSync itself. Buckle up, it's
4+
pretty rough and is changing fast.
5+
6+
### Dependencies
7+
8+
- [Just](https://github.com/casey/just)
9+
- [Rust](https://www.rust-lang.org/)
10+
- [wasm-pack](https://rustwasm.github.io/wasm-pack/)
11+
- [node.js](https://nodejs.org/en)
12+
- [pnpm](https://pnpm.io/)
13+
14+
### Build Wasm artifacts
15+
16+
```bash
17+
just run-with-prefix 'wasm-'
18+
just wasm-demo-reducer --release
19+
just package-sqlsync-worker dev
20+
```
21+
22+
### Local Coordinator
23+
24+
> [!WARNING] Currently this seems to require modifying the wrangler.toml config
25+
> file to point at your own Cloudflare buckets (even though they aren't being
26+
> used). Work is underway to replace the local coordinator with a wrangler
27+
> agnostic alternative optimized for local development.
28+
29+
```bash
30+
cd demo/cloudflare-backend
31+
pnpm i
32+
pnpm dev
33+
34+
# then in another shell
35+
just upload-demo-reducer release local
36+
```
37+
38+
### Local Todo Demo
39+
40+
```bash
41+
cd demo/frontend
42+
pnpm i
43+
pnpm dev
44+
```
45+
46+
Then go to http://localhost:5173
47+
48+
### Run some tests
49+
50+
These tests are useful for learning more about how SQLSync works.
51+
52+
```bash
53+
just unit-test
54+
just test-end-to-end-local
55+
just test-end-to-end-local-net
56+
```
57+
58+
## Community & Contributing
59+
60+
If you are interested in contributing to SQLSync, please [join the Discord
61+
community][discord] and let us know what you want to build. All contributions
62+
will be held to a high standard, and are more likely to be accepted if they are
63+
tied to an existing task and agreed upon specification.
64+
65+
[![Join the SQLSync Community](https://discordapp.com/api/guilds/1149205110262595634/widget.png?style=banner2)][discord]
66+
67+
[discord]: https://discord.gg/etFk2N9nzC

Cargo.lock

Lines changed: 5 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ serde_bytes = "0.11"
4848
worker = "0.0.18"
4949
event-listener = "3.0"
5050
sha2 = "0.10.8"
51+
serde-wasm-bindgen = "0.6"
5152

5253
# specific revision of gloo needed for:
5354
# - access `TryFrom<web_sys::Websocket> for WebSocket`
@@ -59,9 +60,6 @@ gloo-net = { git = "https://github.com/carlsverre/gloo", rev = "90f88e31daf1a959
5960
# specific revision of tsify needed for serde updates
6061
tsify = { git = "https://github.com/carlsverre/tsify", rev = "c05a60b6ae15b2869bf63f618940dc56ef516d1d", default-features = false }
6162

62-
# required to use internally tagged enums with serde_bytes
63-
serde-wasm-bindgen = { git = "https://github.com/carlsverre/serde-wasm-bindgen", rev = "638e36ea6c6334a709c582961cf457ed712d84b3" }
64-
6563
[workspace.dependencies.libsqlite3-sys]
6664
git = "https://github.com/trevyn/rusqlite"
6765
branch = "wasm32-unknown-unknown"

GUIDE.md

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
# The SQLSync Guide
2+
3+
> [!IMPORTANT] SQLSync is in active development and thus is changing quickly.
4+
> Currently, do not use it in a production application as there is no backwards
5+
> compatibility or stability promise.
6+
7+
SQLSync is distributed as a Javascript package as well as a Rust Crate.
8+
Currently both are required to use SQLSync. Also, React is the only supported
9+
framework at the moment.
10+
11+
If you want to jump ahead to a working demo, check out the finished product at:
12+
https://github.com/orbitinghail/sqlsync-demo-guestbook
13+
14+
## Step 1: Creating the Reducer
15+
16+
SQLSync requires that all mutations are handled by a piece of code called "The
17+
Reducer". Currently this code has to be written in Rust, however we have plans
18+
to make it possible to write Reducers using JS or other languages. The fastest
19+
way to create a reducer is to initialize a new Rust project like so:
20+
21+
1. Make sure you have Rust installed; if not install using [rustup].
22+
2. Install support for the `wasm32-unknown-unknown` target:
23+
24+
```bash
25+
rustup target add wasm32-unknown-unknown
26+
```
27+
28+
3. Initialize the reducer: (feel free to rename)
29+
30+
```bash
31+
cargo init --lib reducer
32+
cd reducer
33+
```
34+
35+
4. Update `Cargo.toml` to look something like this
36+
37+
```toml
38+
[package]
39+
name = "reducer"
40+
version = "0.1.0"
41+
edition = "2021"
42+
43+
[lib]
44+
crate-type = ["cdylib"]
45+
46+
[profile.release]
47+
lto = true
48+
strip = "debuginfo"
49+
codegen-units = 1
50+
51+
[dependencies]
52+
sqlsync-reducer = "0.1"
53+
serde = { version = "1.0", features = ["derive"] }
54+
serde_json = "1.0"
55+
log = "0.4"
56+
```
57+
58+
5. Update `src/lib.rs` to look something like this:
59+
60+
```rust
61+
use serde::Deserialize;
62+
use sqlsync_reducer::{execute, init_reducer, types::ReducerError};
63+
64+
#[derive(Deserialize, Debug)]
65+
#[serde(tag = "tag")]
66+
enum Mutation {
67+
InitSchema,
68+
AddMessage { id: String, msg: String },
69+
}
70+
71+
init_reducer!(reducer);
72+
async fn reducer(mutation: Vec<u8>) -> Result<(), ReducerError> {
73+
let mutation: Mutation = serde_json::from_slice(&mutation[..])?;
74+
75+
match mutation {
76+
Mutation::InitSchema => {
77+
execute!(
78+
"CREATE TABLE IF NOT EXISTS messages (
79+
id TEXT PRIMARY KEY,
80+
msg TEXT NOT NULL,
81+
created_at TEXT NOT NULL
82+
)"
83+
).await;
84+
}
85+
86+
Mutation::AddMessage { id, msg } => {
87+
log::info!("appending message({}): {}", id, msg);
88+
execute!(
89+
"insert into messages (id, msg, created_at)
90+
values (?, ?, datetime('now'))",
91+
id, msg
92+
).await;
93+
}
94+
}
95+
96+
Ok(())
97+
}
98+
```
99+
100+
6. Compile your reducer to Wasm
101+
102+
```bash
103+
cargo build --target wasm32-unknown-unknown --release
104+
```
105+
106+
## Step 2: Install and configure the React library
107+
108+
```bash
109+
npm install @orbitinghail/sqlsync-react @orbitinghail/sqlsync-worker
110+
```
111+
112+
The following examples will be using Typescript to make everything a bit more
113+
precise. If you are not using Typescript you can still use SQLSync, just skip
114+
the type descriptions and annotations.
115+
116+
Also, make sure your JS bundling tool supports importing assets from the
117+
filesystem, as will need that to easily get access to the Reducer we compiled
118+
earlier in this guide. If in doubt, [Vite] is highly recommended.
119+
120+
Create a file which will contain type information for your Mutations, the
121+
reducer URL, and export some useful React hooks for your app to consume. It
122+
should look something like this:
123+
124+
```typescript
125+
import {
126+
DocType,
127+
createDocHooks,
128+
serializeMutationAsJSON,
129+
} from "@orbitinghail/sqlsync-react";
130+
131+
// Path to your compiled reducer artifact, your js bundler should handle making
132+
// this a working URL that resolves during development and in production.
133+
const REDUCER_URL = new URL(
134+
"../reducer/target/wasm32-unknown-unknown/release/reducer.wasm",
135+
import.meta.url
136+
);
137+
138+
// Must match the Mutation type in the Rust Reducer code
139+
export type Mutation =
140+
| {
141+
tag: "InitSchema";
142+
}
143+
| {
144+
tag: "AddMessage";
145+
id: string;
146+
msg: string;
147+
};
148+
149+
export const TaskDocType: DocType<Mutation> = {
150+
reducerUrl: REDUCER_URL,
151+
serializeMutation: serializeMutationAsJSON,
152+
};
153+
154+
export const { useMutate, useQuery, useSetConnectionEnabled } =
155+
createDocHooks(TaskDocType);
156+
```
157+
158+
## Step 3: Hooking it up to your app
159+
160+
Using the hooks exported from the file in
161+
[Step 2](#step-2-install-and-configure-the-react-library) we can easily hook
162+
SQLSync up to our application.
163+
164+
Here is a complete example of a very trivial guestbook application which uses
165+
the reducer we created above. If
166+
167+
```tsx
168+
import React, { FormEvent, useEffect } from "react";
169+
import ReactDOM from "react-dom/client";
170+
171+
// this example uses the uuid library (`npm install uuid`)
172+
import { v4 as uuidv4 } from "uuid";
173+
174+
// You'll need to configure your build system to make these entrypoints
175+
// available as urls. Vite does this automatically via the `?url` suffix.
176+
import sqlSyncWasmUrl from "@orbitinghail/sqlsync-worker/sqlsync.wasm?url";
177+
import workerUrl from "@orbitinghail/sqlsync-worker/worker.js?url";
178+
179+
// import the SQLSync provider and hooks
180+
import { SQLSyncProvider, sql } from "@orbitinghail/sqlsync-react";
181+
import { useMutate, useQuery } from "./doctype";
182+
183+
// Create a DOC_ID to use, each DOC_ID will correspond to a different SQLite
184+
// database. We use a static doc id so we can play with cross-tab sync.
185+
import { journalIdFromString } from "@orbitinghail/sqlsync-worker";
186+
const DOC_ID = journalIdFromString("VM7fC4gKxa52pbdtrgd9G9");
187+
188+
// Configure the SQLSync provider near the top of the React tree
189+
ReactDOM.createRoot(document.getElementById("root")!).render(
190+
<SQLSyncProvider wasmUrl={sqlSyncWasmUrl} workerUrl={workerUrl}>
191+
<App />
192+
</SQLSyncProvider>
193+
);
194+
195+
// Use SQLSync hooks in your app
196+
export function App() {
197+
// we will use the standard useState hook to handle the message input box
198+
const [msg, setMsg] = React.useState("");
199+
200+
// create a mutate function for our document
201+
const mutate = useMutate(DOC_ID);
202+
203+
// initialize the schema; eventually this will be handled by SQLSync automatically
204+
useEffect(() => {
205+
mutate({ tag: "InitSchema" }).catch((err) => {
206+
console.error("Failed to init schema", err);
207+
});
208+
}, [mutate]);
209+
210+
// create a callback which knows how to trigger the add message mutation
211+
const handleSubmit = React.useCallback(
212+
(e: FormEvent<HTMLFormElement>) => {
213+
// Prevent the browser from reloading the page
214+
e.preventDefault();
215+
216+
// create a unique message id
217+
const id = crypto.randomUUID ? crypto.randomUUID() : uuidv4();
218+
219+
// don't add empty messages
220+
if (msg.trim() !== "") {
221+
mutate({ tag: "AddMessage", id, msg }).catch((err) => {
222+
console.error("Failed to add message", err);
223+
});
224+
// clear the message
225+
setMsg("");
226+
}
227+
},
228+
[mutate, msg]
229+
);
230+
231+
// finally, query SQLSync for all the messages, sorted by created_at
232+
const { rows } = useQuery<{ id: string; msg: string }>(
233+
DOC_ID,
234+
sql`
235+
select id, msg from messages
236+
order by created_at
237+
`
238+
);
239+
240+
return (
241+
<div>
242+
<h1>Guestbook:</h1>
243+
<ul>
244+
{(rows ?? []).map(({ id, msg }) => (
245+
<li key={id}>{msg}</li>
246+
))}
247+
</ul>
248+
<h3>Leave a message:</h3>
249+
<form onSubmit={handleSubmit}>
250+
<label>
251+
Msg:
252+
<input
253+
type="text"
254+
name="msg"
255+
value={msg}
256+
onChange={(e) => setMsg(e.target.value)}
257+
/>
258+
</label>
259+
<input type="submit" value="Submit" />
260+
</form>
261+
</div>
262+
);
263+
}
264+
```
265+
266+
## Step 4: Connect to the coordinator (COMING SOON)
267+
268+
This step still requires using SQLSync from source. For now you'll have to
269+
follow the directions in the [Contribution Guide] to setup a Local Coordinator.
270+
271+
[rustup]: https://rustup.rs/
272+
[Vite]: https://vitejs.dev/
273+
[Contribution Guide]: ./CONTRIBUTING.md

0 commit comments

Comments
 (0)