Skip to content

Commit

Permalink
Merge pull request #38 from orbitinghail:refactor-sqlsync-react
Browse files Browse the repository at this point in the history
Refactor sqlsync react
  • Loading branch information
carlsverre authored Jan 5, 2024
2 parents c34cb11 + b868102 commit 0273d2a
Show file tree
Hide file tree
Showing 48 changed files with 1,403 additions and 892 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/actions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,7 @@ jobs:
run: just test-sqlsync-reducer
- name: build sqlsync react and worker packages
run: just package-sqlsync-react package-sqlsync-worker
- name: build frontend
run: cd demo/frontend && pnpm build
- name: build examples
run: cd examples/guestbook-react && pnpm build
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
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.

# Pending Changes

- Moved the majority of functionality from `sqlsync-react` to `sqlsync-worker` to make it easier to add additional JS framework support. ([#38])

# 0.2.0 - Dec 1 2023

- Reducer can now handle query errors (#29)
- Reducer can now handle query errors ([#29])

# 0.1.0 - Oct 23 2023

- Initial release

[#38]: https://github.com/orbitinghail/sqlsync/pull/38
[#29]: https://github.com/orbitinghail/sqlsync/pull/29
30 changes: 21 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ members = [
"lib/sqlsync-reducer",
"lib/sqlite-vfs",
"lib/testutil",
"lib/sqlsync-react/sqlsync-react-test-reducer",

"examples/reducer-guestbook",

"demo/demo-reducer",
"demo/cloudflare-backend",
Expand Down
55 changes: 24 additions & 31 deletions GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
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.

If you want to jump ahead to a working demo, check out the finished product at: https://github.com/orbitinghail/sqlsync-demo-guestbook
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.

[guestbook-src]: examples/guestbook-react


## Step 1: Creating the Reducer

Expand Down Expand Up @@ -48,7 +51,7 @@ strip = "debuginfo"
codegen-units = 1

[dependencies]
sqlsync-reducer = "0.1"
sqlsync-reducer = "0.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
log = "0.4"
Expand Down Expand Up @@ -118,17 +121,12 @@ Also, make sure your JS bundling tool supports importing assets from the file sy
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:

```typescript
import {
DocType,
createDocHooks,
serializeMutationAsJSON,
} from "@orbitinghail/sqlsync-react";

// Path to your compiled reducer artifact, your js bundler should handle making
// this a working URL that resolves during development and in production.
import { createDocHooks } from "@orbitinghail/sqlsync-react";
import { DocType, serializeMutationAsJSON } from "@orbitinghail/sqlsync-worker";

const REDUCER_URL = new URL(
"../reducer/target/wasm32-unknown-unknown/release/reducer.wasm",
import.meta.url
"../../../target/wasm32-unknown-unknown/release/reducer_guestbook.wasm",
import.meta.url,
);

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

export const { useMutate, useQuery, useSetConnectionEnabled } =
createDocHooks(TaskDocType);
export const { useMutate, useQuery, useSetConnectionEnabled } = createDocHooks(TaskDocType);
```

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

```tsx
import React, { FormEvent, useEffect } from "react";
import ReactDOM from "react-dom/client";
import { SQLSyncProvider } from "@orbitinghail/sqlsync-react";
import { journalIdFromString, sql } from "@orbitinghail/sqlsync-worker";

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

import React, { FormEvent, useCallback, useEffect } from "react";
import ReactDOM from "react-dom/client";

// You'll need to configure your build system to make these entrypoints
// available as urls. Vite does this automatically via the `?url` suffix.
// available as urls. Vite does this automatically via the `?url` and `?worker&url` suffix.
import sqlSyncWasmUrl from "@orbitinghail/sqlsync-worker/sqlsync.wasm?url";
import workerUrl from "@orbitinghail/sqlsync-worker/worker.js?url";
import workerUrl from "@orbitinghail/sqlsync-worker/worker.js?worker&url";

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

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

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

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

// create a callback which knows how to trigger the add message mutation
const handleSubmit = React.useCallback(
const handleSubmit = useCallback(
(e: FormEvent<HTMLFormElement>) => {
// Prevent the browser from reloading the page
e.preventDefault();
Expand All @@ -218,7 +216,7 @@ export function App() {
setMsg("");
}
},
[mutate, msg]
[mutate, msg, setMsg],
);

// finally, query SQLSync for all the messages, sorted by created_at
Expand All @@ -227,7 +225,7 @@ export function App() {
sql`
select id, msg from messages
order by created_at
`
`,
);

return (
Expand All @@ -242,12 +240,7 @@ export function App() {
<form onSubmit={handleSubmit}>
<label>
Msg:
<input
type="text"
name="msg"
value={msg}
onChange={(e) => setMsg(e.target.value)}
/>
<input type="text" name="msg" value={msg} onChange={(e) => setMsg(e.target.value)} />
</label>
<input type="submit" value="Submit" />
</form>
Expand Down
3 changes: 1 addition & 2 deletions demo/frontend/src/components/TaskList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Center, Flex, Paper, Stack, Title } from "@mantine/core";
import { sql } from "@orbitinghail/sqlsync-react";
import { JournalId } from "@orbitinghail/sqlsync-worker";
import { JournalId, sql } from "@orbitinghail/sqlsync-worker";
import { useMutate, useQuery } from "../doctype";
import { ConnectionStatus } from "./ConnectionStatus";
import { TaskForm } from "./TaskForm";
Expand Down
3 changes: 2 additions & 1 deletion demo/frontend/src/doctype.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DocType, createDocHooks, serializeMutationAsJSON } from "@orbitinghail/sqlsync-react";
import { createDocHooks } from "@orbitinghail/sqlsync-react";
import { DocType, serializeMutationAsJSON } from "@orbitinghail/sqlsync-worker";

const REDUCER_URL = new URL(
"../../../target/wasm32-unknown-unknown/release/demo_reducer.wasm",
Expand Down
41 changes: 35 additions & 6 deletions demo/frontend/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import React from "react";
import ReactDOM from "react-dom/client";

import { RouterProvider, createBrowserRouter, redirect, useParams } from "react-router-dom";
import {
RouterProvider,
createBrowserRouter,
redirect,
useParams,
useRouteError,
} from "react-router-dom";

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

import { MantineProvider } from "@mantine/core";
import { Alert, Container, MantineProvider, Stack } from "@mantine/core";
import { SQLSyncProvider } from "@orbitinghail/sqlsync-react";
import { journalIdFromString, journalIdToString } from "@orbitinghail/sqlsync-worker";
import sqlSyncWasmUrl from "@orbitinghail/sqlsync-worker/sqlsync.wasm?url";
import { App } from "./App";

import "@mantine/code-highlight/styles.css";
// import stylesheets
import "@mantine/core/styles.css";
import { IconInfoCircle } from "@tabler/icons-react";
import { Header } from "./components/Header";
import { MANTINE_THEME } from "./theme";

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

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

const ErrorBoundary = () => {
// biome-ignore lint/suspicious/noExplicitAny: could be thrown from anywhere
const error = useRouteError() as any;
console.error(error);
return (
<Container size="xs" py="sm">
<Stack>
<Header />
<Alert variant="light" color="red" title="Error" icon={<IconInfoCircle />}>
Failed to load document
{Object.prototype.hasOwnProperty.call(error, "message") ? `: ${error.message}` : ""}
</Alert>
</Stack>
</Container>
);
};

const router = createBrowserRouter([
{
path: "/",
errorElement: <ErrorBoundary />,
loader: async () => {
const docId = await newDocumentId();
return redirect(`/${journalIdToString(docId)}`);
},
},
{
path: "/named/:name",
errorElement: <ErrorBoundary />,
loader: async ({ params }) => {
const docId = await newDocumentId(params.name);
return redirect(`/${journalIdToString(docId)}`);
Expand All @@ -65,6 +93,7 @@ const router = createBrowserRouter([
{
path: "/:docId",
element: <DocRoute />,
errorElement: <ErrorBoundary />,
},
]);

Expand Down
8 changes: 0 additions & 8 deletions demo/frontend/tailwind.config.js

This file was deleted.

6 changes: 4 additions & 2 deletions demo/frontend/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
{
"compilerOptions": {
"target": "ESNext",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"lib": ["ES2020", "ES2021.WeakRef", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"types": ["vite/client"],

/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",

/* Linting */
"strict": true,
"noUnusedLocals": true,
Expand Down
2 changes: 1 addition & 1 deletion demo/frontend/tsconfig.node.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"strict": true,
"types": ["node"]
},
"include": ["vite.config.ts", "tailwind.config.js", "vite-plugin-wasm.d.ts", "postcss.config.js"]
"include": ["vite.config.ts", "vite-plugin-wasm.d.ts", "postcss.config.js"]
}
Loading

0 comments on commit 0273d2a

Please sign in to comment.