Skip to content

Commit 0273d2a

Browse files
authored
Merge pull request #38 from orbitinghail:refactor-sqlsync-react
Refactor sqlsync react
2 parents c34cb11 + b868102 commit 0273d2a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1403
-892
lines changed

.github/workflows/actions.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,7 @@ jobs:
4545
run: just test-sqlsync-reducer
4646
- name: build sqlsync react and worker packages
4747
run: just package-sqlsync-react package-sqlsync-worker
48+
- name: build frontend
49+
run: cd demo/frontend && pnpm build
50+
- name: build examples
51+
run: cd examples/guestbook-react && pnpm build

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
This changelog documents changes across multiple projects contained in this monorepo. Each project is released for every SQLSync version, even if the project has not changed. The reason for this decision is to simplify testing and debugging. Lockstep versioning will be relaxed as SQLSync matures.
22

3+
# Pending Changes
4+
5+
- Moved the majority of functionality from `sqlsync-react` to `sqlsync-worker` to make it easier to add additional JS framework support. ([#38])
6+
37
# 0.2.0 - Dec 1 2023
48

5-
- Reducer can now handle query errors (#29)
9+
- Reducer can now handle query errors ([#29])
610

711
# 0.1.0 - Oct 23 2023
812

913
- Initial release
14+
15+
[#38]: https://github.com/orbitinghail/sqlsync/pull/38
16+
[#29]: https://github.com/orbitinghail/sqlsync/pull/29

Cargo.lock

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

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ members = [
77
"lib/sqlsync-reducer",
88
"lib/sqlite-vfs",
99
"lib/testutil",
10-
"lib/sqlsync-react/sqlsync-react-test-reducer",
10+
11+
"examples/reducer-guestbook",
1112

1213
"demo/demo-reducer",
1314
"demo/cloudflare-backend",

GUIDE.md

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
66
SQLSync is distributed as a JavaScript package as well as a Rust Crate. Currently, both are required to use SQLSync. Also, React is the only supported framework at the moment.
77

8-
If you want to jump ahead to a working demo, check out the finished product at: https://github.com/orbitinghail/sqlsync-demo-guestbook
8+
If you want to jump ahead to a working demo, [check out the finished product here][guestbook-src]. Note that it has a couple more features than what you get with this guide.
9+
10+
[guestbook-src]: examples/guestbook-react
11+
912

1013
## Step 1: Creating the Reducer
1114

@@ -48,7 +51,7 @@ strip = "debuginfo"
4851
codegen-units = 1
4952

5053
[dependencies]
51-
sqlsync-reducer = "0.1"
54+
sqlsync-reducer = "0.2"
5255
serde = { version = "1.0", features = ["derive"] }
5356
serde_json = "1.0"
5457
log = "0.4"
@@ -118,17 +121,12 @@ Also, make sure your JS bundling tool supports importing assets from the file sy
118121
Create a file which will contain type information for your Mutations, the reducer URL, and export some useful React hooks for your app to consume. It should look something like this:
119122

120123
```typescript
121-
import {
122-
DocType,
123-
createDocHooks,
124-
serializeMutationAsJSON,
125-
} from "@orbitinghail/sqlsync-react";
126-
127-
// Path to your compiled reducer artifact, your js bundler should handle making
128-
// this a working URL that resolves during development and in production.
124+
import { createDocHooks } from "@orbitinghail/sqlsync-react";
125+
import { DocType, serializeMutationAsJSON } from "@orbitinghail/sqlsync-worker";
126+
129127
const REDUCER_URL = new URL(
130-
"../reducer/target/wasm32-unknown-unknown/release/reducer.wasm",
131-
import.meta.url
128+
"../../../target/wasm32-unknown-unknown/release/reducer_guestbook.wasm",
129+
import.meta.url,
132130
);
133131

134132
// Must match the Mutation type in the Rust Reducer code
@@ -147,8 +145,7 @@ export const TaskDocType: DocType<Mutation> = {
147145
serializeMutation: serializeMutationAsJSON,
148146
};
149147

150-
export const { useMutate, useQuery, useSetConnectionEnabled } =
151-
createDocHooks(TaskDocType);
148+
export const { useMutate, useQuery, useSetConnectionEnabled } = createDocHooks(TaskDocType);
152149
```
153150

154151
## Step 3: Hooking it up to your app
@@ -158,31 +155,32 @@ Using the hooks exported from the file in [Step 2](#step-2-install-and-configure
158155
Here is a complete example of a very trivial guestbook application which uses the reducer we created above.
159156

160157
```tsx
161-
import React, { FormEvent, useEffect } from "react";
162-
import ReactDOM from "react-dom/client";
158+
import { SQLSyncProvider } from "@orbitinghail/sqlsync-react";
159+
import { journalIdFromString, sql } from "@orbitinghail/sqlsync-worker";
163160

164161
// this example uses the uuid library (`npm install uuid`)
165162
import { v4 as uuidv4 } from "uuid";
166163

164+
import React, { FormEvent, useCallback, useEffect } from "react";
165+
import ReactDOM from "react-dom/client";
166+
167167
// You'll need to configure your build system to make these entrypoints
168-
// available as urls. Vite does this automatically via the `?url` suffix.
168+
// available as urls. Vite does this automatically via the `?url` and `?worker&url` suffix.
169169
import sqlSyncWasmUrl from "@orbitinghail/sqlsync-worker/sqlsync.wasm?url";
170-
import workerUrl from "@orbitinghail/sqlsync-worker/worker.js?url";
170+
import workerUrl from "@orbitinghail/sqlsync-worker/worker.js?worker&url";
171171

172-
// import the SQLSync provider and hooks
173-
import { SQLSyncProvider, sql } from "@orbitinghail/sqlsync-react";
174172
import { useMutate, useQuery } from "./doctype";
175173

176174
// Create a DOC_ID to use, each DOC_ID will correspond to a different SQLite
177175
// database. We use a static doc id so we can play with cross-tab sync.
178-
import { journalIdFromString } from "@orbitinghail/sqlsync-worker";
179176
const DOC_ID = journalIdFromString("VM7fC4gKxa52pbdtrgd9G9");
180177

181178
// Configure the SQLSync provider near the top of the React tree
179+
// biome-ignore lint/style/noNonNullAssertion: we know this element exists
182180
ReactDOM.createRoot(document.getElementById("root")!).render(
183181
<SQLSyncProvider wasmUrl={sqlSyncWasmUrl} workerUrl={workerUrl}>
184182
<App />
185-
</SQLSyncProvider>
183+
</SQLSyncProvider>,
186184
);
187185

188186
// Use SQLSync hooks in your app
@@ -201,7 +199,7 @@ export function App() {
201199
}, [mutate]);
202200

203201
// create a callback which knows how to trigger the add message mutation
204-
const handleSubmit = React.useCallback(
202+
const handleSubmit = useCallback(
205203
(e: FormEvent<HTMLFormElement>) => {
206204
// Prevent the browser from reloading the page
207205
e.preventDefault();
@@ -218,7 +216,7 @@ export function App() {
218216
setMsg("");
219217
}
220218
},
221-
[mutate, msg]
219+
[mutate, msg, setMsg],
222220
);
223221

224222
// finally, query SQLSync for all the messages, sorted by created_at
@@ -227,7 +225,7 @@ export function App() {
227225
sql`
228226
select id, msg from messages
229227
order by created_at
230-
`
228+
`,
231229
);
232230

233231
return (
@@ -242,12 +240,7 @@ export function App() {
242240
<form onSubmit={handleSubmit}>
243241
<label>
244242
Msg:
245-
<input
246-
type="text"
247-
name="msg"
248-
value={msg}
249-
onChange={(e) => setMsg(e.target.value)}
250-
/>
243+
<input type="text" name="msg" value={msg} onChange={(e) => setMsg(e.target.value)} />
251244
</label>
252245
<input type="submit" value="Submit" />
253246
</form>

demo/frontend/src/components/TaskList.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Center, Flex, Paper, Stack, Title } from "@mantine/core";
2-
import { sql } from "@orbitinghail/sqlsync-react";
3-
import { JournalId } from "@orbitinghail/sqlsync-worker";
2+
import { JournalId, sql } from "@orbitinghail/sqlsync-worker";
43
import { useMutate, useQuery } from "../doctype";
54
import { ConnectionStatus } from "./ConnectionStatus";
65
import { TaskForm } from "./TaskForm";

demo/frontend/src/doctype.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { DocType, createDocHooks, serializeMutationAsJSON } from "@orbitinghail/sqlsync-react";
1+
import { createDocHooks } from "@orbitinghail/sqlsync-react";
2+
import { DocType, serializeMutationAsJSON } from "@orbitinghail/sqlsync-worker";
23

34
const REDUCER_URL = new URL(
45
"../../../target/wasm32-unknown-unknown/release/demo_reducer.wasm",

demo/frontend/src/main.tsx

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
11
import React from "react";
22
import ReactDOM from "react-dom/client";
33

4-
import { RouterProvider, createBrowserRouter, redirect, useParams } from "react-router-dom";
4+
import {
5+
RouterProvider,
6+
createBrowserRouter,
7+
redirect,
8+
useParams,
9+
useRouteError,
10+
} from "react-router-dom";
511

6-
// HACK: switch to the .ts version for nicer local dev
7-
// import workerUrl from "@orbitinghail/sqlsync-worker/worker.ts?url";
8-
import workerUrl from "@orbitinghail/sqlsync-worker/worker.js?url";
12+
import sqlSyncWasmUrl from "@orbitinghail/sqlsync-worker/sqlsync.wasm?url";
13+
import workerUrl from "@orbitinghail/sqlsync-worker/worker.ts?worker&url";
914

10-
import { MantineProvider } from "@mantine/core";
15+
import { Alert, Container, MantineProvider, Stack } from "@mantine/core";
1116
import { SQLSyncProvider } from "@orbitinghail/sqlsync-react";
1217
import { journalIdFromString, journalIdToString } from "@orbitinghail/sqlsync-worker";
13-
import sqlSyncWasmUrl from "@orbitinghail/sqlsync-worker/sqlsync.wasm?url";
1418
import { App } from "./App";
1519

1620
import "@mantine/code-highlight/styles.css";
1721
// import stylesheets
1822
import "@mantine/core/styles.css";
23+
import { IconInfoCircle } from "@tabler/icons-react";
24+
import { Header } from "./components/Header";
1925
import { MANTINE_THEME } from "./theme";
2026

2127
const isLocalhost = location.hostname === "localhost" || location.hostname.startsWith("192.168");
@@ -33,6 +39,9 @@ const newDocumentId = async (name = "") => {
3339
const response = await fetch(url, {
3440
method: "POST",
3541
});
42+
if (!response.ok) {
43+
throw new Error(`Failed to create new document: ${response.status}`);
44+
}
3645
return journalIdFromString(await response.text());
3746
};
3847

@@ -47,16 +56,35 @@ export const DocRoute = () => {
4756
return <App docId={journalIdFromString(docId)} />;
4857
};
4958

59+
const ErrorBoundary = () => {
60+
// biome-ignore lint/suspicious/noExplicitAny: could be thrown from anywhere
61+
const error = useRouteError() as any;
62+
console.error(error);
63+
return (
64+
<Container size="xs" py="sm">
65+
<Stack>
66+
<Header />
67+
<Alert variant="light" color="red" title="Error" icon={<IconInfoCircle />}>
68+
Failed to load document
69+
{Object.prototype.hasOwnProperty.call(error, "message") ? `: ${error.message}` : ""}
70+
</Alert>
71+
</Stack>
72+
</Container>
73+
);
74+
};
75+
5076
const router = createBrowserRouter([
5177
{
5278
path: "/",
79+
errorElement: <ErrorBoundary />,
5380
loader: async () => {
5481
const docId = await newDocumentId();
5582
return redirect(`/${journalIdToString(docId)}`);
5683
},
5784
},
5885
{
5986
path: "/named/:name",
87+
errorElement: <ErrorBoundary />,
6088
loader: async ({ params }) => {
6189
const docId = await newDocumentId(params.name);
6290
return redirect(`/${journalIdToString(docId)}`);
@@ -65,6 +93,7 @@ const router = createBrowserRouter([
6593
{
6694
path: "/:docId",
6795
element: <DocRoute />,
96+
errorElement: <ErrorBoundary />,
6897
},
6998
]);
7099

demo/frontend/tailwind.config.js

Lines changed: 0 additions & 8 deletions
This file was deleted.

demo/frontend/tsconfig.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
{
22
"compilerOptions": {
3-
"target": "ESNext",
3+
"target": "ES2020",
44
"useDefineForClassFields": true,
5-
"lib": ["ESNext", "DOM", "DOM.Iterable"],
5+
"lib": ["ES2020", "ES2021.WeakRef", "DOM", "DOM.Iterable"],
66
"module": "ESNext",
77
"skipLibCheck": true,
88
"types": ["vite/client"],
9+
910
/* Bundler mode */
1011
"moduleResolution": "bundler",
1112
"allowImportingTsExtensions": true,
1213
"resolveJsonModule": true,
1314
"isolatedModules": true,
1415
"noEmit": true,
1516
"jsx": "react-jsx",
17+
1618
/* Linting */
1719
"strict": true,
1820
"noUnusedLocals": true,

demo/frontend/tsconfig.node.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@
88
"strict": true,
99
"types": ["node"]
1010
},
11-
"include": ["vite.config.ts", "tailwind.config.js", "vite-plugin-wasm.d.ts", "postcss.config.js"]
11+
"include": ["vite.config.ts", "vite-plugin-wasm.d.ts", "postcss.config.js"]
1212
}

0 commit comments

Comments
 (0)