Skip to content

Commit 518bae5

Browse files
committed
Merge branch 'pr/matthewgapp/37'
2 parents 4178a18 + 65c103d commit 518bae5

File tree

18 files changed

+1180
-27
lines changed

18 files changed

+1180
-27
lines changed

examples/guestbook-solid-js/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Solid JS + TypeScript + Vite + SQLSync
2+
3+
This template provides a minimal setup to get SQLSync working with Solid JS and Vite.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!doctype html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>SQLSync + Vite + Solid JS + TS</title>
8+
</head>
9+
10+
<body>
11+
<div id="root">if nothing renders, open the console</div>
12+
<script type="module" src="/src/main.tsx"></script>
13+
</body>
14+
15+
</html>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "guestbook-react",
3+
"private": true,
4+
"version": "0.0.0",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite",
8+
"build": "tsc && vite build",
9+
"preview": "vite preview"
10+
},
11+
"dependencies": {
12+
"@orbitinghail/sqlsync-solid-js": "workspace:*",
13+
"@orbitinghail/sqlsync-worker": "workspace:*",
14+
"uuid": "^9.0.1",
15+
"solid-js": "^1.8.7"
16+
},
17+
"devDependencies": {
18+
"@types/uuid": "^9.0.6",
19+
"vite-plugin-solid": "^2.7.0",
20+
"typescript": "^5.2.2",
21+
"vite": "^4.5.0",
22+
"vitest": "^0.34.6"
23+
}
24+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { createDocHooks } from "@orbitinghail/sqlsync-solid-js";
2+
import { DocType, serializeMutationAsJSON } from "@orbitinghail/sqlsync-worker";
3+
4+
const REDUCER_URL = new URL(
5+
"../../../target/wasm32-unknown-unknown/release/reducer_guestbook.wasm",
6+
import.meta.url,
7+
);
8+
9+
// Must match the Mutation type in the Rust Reducer code
10+
export type Mutation =
11+
| {
12+
tag: "InitSchema";
13+
}
14+
| {
15+
tag: "AddMessage";
16+
id: string;
17+
msg: string;
18+
}
19+
| {
20+
tag: "DeleteMessage";
21+
id: string;
22+
};
23+
24+
export const TaskDocType: DocType<Mutation> = {
25+
reducerUrl: REDUCER_URL,
26+
serializeMutation: serializeMutationAsJSON,
27+
};
28+
29+
export const { useMutate, useQuery, useSetConnectionEnabled } = createDocHooks(() => TaskDocType);
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { SQLSyncProvider } from "@orbitinghail/sqlsync-solid-js";
2+
import { journalIdFromString, sql } from "@orbitinghail/sqlsync-worker";
3+
4+
// this example uses the uuid library (`npm install uuid`)
5+
import { JSX } from "solid-js/jsx-runtime";
6+
import { v4 as uuidv4 } from "uuid";
7+
8+
// You'll need to configure your build system to make these entrypoints
9+
// available as urls. Vite does this automatically via the `?url` and `?worker&url` suffix.
10+
import sqlSyncWasmUrl from "@orbitinghail/sqlsync-worker/sqlsync.wasm?url";
11+
import workerUrl from "@orbitinghail/sqlsync-worker/worker.ts?worker&url";
12+
13+
import { createEffect, createSignal } from "solid-js";
14+
import { For, render } from "solid-js/web";
15+
import { useMutate, useQuery } from "./doctype";
16+
17+
// Create a DOC_ID to use, each DOC_ID will correspond to a different SQLite
18+
// database. We use a static doc id so we can play with cross-tab sync.
19+
const DOC_ID = journalIdFromString("VM7fC4gKxa52pbdtrgd9G9");
20+
21+
// Use SQLSync hooks in your app
22+
export function App() {
23+
// we will use the standard useState hook to handle the message input box
24+
const [msg, setMsg] = createSignal("");
25+
26+
// create a mutate function for our document
27+
const mutate = useMutate(DOC_ID);
28+
29+
// initialize the schema; eventually this will be handled by SQLSync automatically
30+
createEffect(() => {
31+
mutate({ tag: "InitSchema" }).catch((err) => {
32+
console.error("Failed to init schema", err);
33+
});
34+
});
35+
36+
// create a callback which knows how to trigger the add message mutation
37+
const handleSubmit: JSX.EventHandler<HTMLFormElement, SubmitEvent> = (e) => {
38+
// Prevent the browser from reloading the page
39+
e.preventDefault();
40+
41+
// create a unique message id
42+
const id = crypto.randomUUID ? crypto.randomUUID() : uuidv4();
43+
44+
// don't add empty messages
45+
if (msg().trim() !== "") {
46+
mutate({ tag: "AddMessage", id, msg: msg() }).catch((err) => {
47+
console.error("Failed to add message", err);
48+
});
49+
// clear the message
50+
setMsg("");
51+
}
52+
};
53+
54+
// create a callback to delete a message
55+
const handleDelete = (id: string) => {
56+
mutate({ tag: "DeleteMessage", id }).catch((err) => {
57+
console.error("Failed to delete message", err);
58+
});
59+
};
60+
61+
// finally, query SQLSync for all the messages, sorted by created_at
62+
const queryState = useQuery<{ id: string; msg: string }>(
63+
() => DOC_ID,
64+
() => sql`
65+
select id, msg from messages
66+
order by created_at
67+
`,
68+
);
69+
70+
const rows = () => queryState().rows ?? [];
71+
72+
return (
73+
<div>
74+
<h1>Guestbook:</h1>
75+
<ul>
76+
<For each={rows()}>
77+
{(row) => {
78+
return (
79+
<li>
80+
{row.msg}
81+
<button
82+
type="button"
83+
onClick={() => handleDelete(row.id)}
84+
style={{ "margin-left": "40px" }}
85+
>
86+
remove msg
87+
</button>
88+
</li>
89+
);
90+
}}
91+
</For>
92+
</ul>
93+
<h3>Leave a message:</h3>
94+
<form onSubmit={handleSubmit}>
95+
<label>
96+
Msg:
97+
<input type="text" name="msg" value={msg()} onChange={(e) => setMsg(e.target.value)} />
98+
</label>
99+
<input type="submit" value="Submit" />
100+
</form>
101+
</div>
102+
);
103+
}
104+
105+
// Configure the SQLSync provider near the top of the React tree
106+
render(
107+
() => (
108+
<SQLSyncProvider wasmUrl={sqlSyncWasmUrl} workerUrl={workerUrl}>
109+
<App />
110+
</SQLSyncProvider>
111+
),
112+
// biome-ignore lint/style/noNonNullAssertion: we know this element exists
113+
document.getElementById("root")!,
114+
);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2020",
4+
"useDefineForClassFields": true,
5+
"lib": ["ES2020", "ES2021.WeakRef", "DOM", "DOM.Iterable"],
6+
"module": "ESNext",
7+
"skipLibCheck": true,
8+
"types": ["vite/client"],
9+
10+
/* Bundler mode */
11+
"moduleResolution": "bundler",
12+
"allowImportingTsExtensions": true,
13+
"resolveJsonModule": true,
14+
"isolatedModules": true,
15+
"noEmit": true,
16+
"jsx": "preserve",
17+
"jsxImportSource": "solid-js",
18+
19+
/* Linting */
20+
"strict": true,
21+
"noUnusedLocals": true,
22+
"noUnusedParameters": true,
23+
"noFallthroughCasesInSwitch": true
24+
},
25+
"include": ["src"],
26+
"references": [{ "path": "./tsconfig.node.json" }]
27+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"compilerOptions": {
3+
"composite": true,
4+
"skipLibCheck": true,
5+
"module": "ESNext",
6+
"moduleResolution": "bundler",
7+
"allowSyntheticDefaultImports": true
8+
},
9+
"include": ["vite.config.ts"]
10+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { defineConfig } from "vite";
2+
import solid from "vite-plugin-solid";
3+
4+
// https://vitejs.dev/config/
5+
export default defineConfig({
6+
plugins: [solid()],
7+
});

justfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ wasm-examples-reducer-guestbook:
4949
example-guestbook-react: wasm-examples-reducer-guestbook
5050
cd examples/guestbook-react && pnpm dev
5151

52+
example-guestbook-solid-js: wasm-examples-reducer-guestbook
53+
cd examples/guestbook-solid-js && pnpm dev
54+
5255
test-end-to-end-local rng_seed="": wasm-task-reducer
5356
RUST_BACKTRACE=1 cargo run --example end-to-end-local {{rng_seed}}
5457

@@ -64,6 +67,9 @@ node_modules:
6467
package-sqlsync-react:
6568
cd lib/sqlsync-react && pnpm build
6669

70+
package-sqlsync-solid-js:
71+
cd lib/sqlsync-solid-js && pnpm build
72+
6773
package-sqlsync-worker target='release':
6874
#!/usr/bin/env bash
6975
if [[ '{{target}}' = 'release' ]]; then

lib/sqlsync-react/tsconfig.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
"target": "esnext",
44
"module": "esnext",
55
"moduleResolution": "Bundler",
6-
"baseUrl": ".",
7-
"lib": ["ES6", "DOM", "ES2021.WeakRef"],
6+
"lib": ["ES6", "DOM", "DOM.Iterable", "ES2021.WeakRef"],
87

98
/* Compiling */
109
"allowJs": true,

lib/sqlsync-solid-js/package.json

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"name": "@orbitinghail/sqlsync-solid-js",
3+
"version": "0.2.0",
4+
"description": "SQLSync is a collaborative offline-first wrapper around SQLite. It is designed to synchronize web application state between users, devices, and the edge.",
5+
"homepage": "https://sqlsync.dev",
6+
"license": "Apache-2.0",
7+
"keywords": [
8+
"sqlsync",
9+
"sql",
10+
"database",
11+
"sqlite",
12+
"offline-first",
13+
"local-first",
14+
"solid-js"
15+
],
16+
"repository": {
17+
"type": "git",
18+
"url": "https://github.com/orbitinghail/sqlsync"
19+
},
20+
"files": [
21+
"dist",
22+
"src"
23+
],
24+
"type": "module",
25+
"types": "./src/index.ts",
26+
"main": "./src/index.ts",
27+
"exports": {
28+
".": {
29+
"import": "./src/index.ts",
30+
"default": "./src/index.ts",
31+
"types": "./src/index.ts"
32+
}
33+
},
34+
"publishConfig": {
35+
"main": "./dist/index.js",
36+
"types": "./dist/index.d.ts",
37+
"exports": {
38+
".": {
39+
"import": "./dist/index.js",
40+
"default": "./dist/index.js",
41+
"types": "./dist/index.d.ts"
42+
}
43+
}
44+
},
45+
"scripts": {
46+
"build": "rollup --config"
47+
},
48+
"devDependencies": {
49+
"@rollup/plugin-babel": "^6.0.4",
50+
"@rollup/plugin-node-resolve": "^15.2.3",
51+
"@rollup/plugin-typescript": "^11.1.5",
52+
"@types/node": "^20.8.8",
53+
"babel-preset-solid": "^1.8.8",
54+
"@orbitinghail/sqlsync-worker": "workspace:^",
55+
"rollup": "^3.29.4",
56+
"typescript": "^5.2.2"
57+
},
58+
"dependencies": {
59+
"fast-equals": "^5.0.1"
60+
},
61+
"peerDependencies": {
62+
"solid-js": "^1.8.7",
63+
"@orbitinghail/sqlsync-worker": "workspace:^"
64+
}
65+
}

lib/sqlsync-solid-js/rollup.config.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import babel from "@rollup/plugin-babel";
2+
import { nodeResolve } from "@rollup/plugin-node-resolve";
3+
import typescript from "@rollup/plugin-typescript";
4+
5+
export default {
6+
input: "src/index.ts",
7+
output: {
8+
dir: "dist",
9+
format: "es",
10+
sourcemap: true,
11+
},
12+
external: ["solid-js", "@orbitinghail/sqlsync-worker"],
13+
plugins: [
14+
typescript(),
15+
nodeResolve(),
16+
babel({
17+
extensions: [".ts", ".tsx"],
18+
babelHelpers: "bundled",
19+
presets: ["solid", "@babel/preset-typescript"],
20+
exclude: [/node_modules\//],
21+
}),
22+
],
23+
};

lib/sqlsync-solid-js/src/context.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { SQLSync } from "@orbitinghail/sqlsync-worker";
2+
import { ParentComponent, createContext, createSignal, onCleanup } from "solid-js";
3+
4+
export const SQLSyncContext = createContext<[() => SQLSync | null, (sqlSync: SQLSync) => void]>([
5+
() => null,
6+
() => {},
7+
]);
8+
9+
interface Props {
10+
workerUrl: string | URL;
11+
wasmUrl: string | URL;
12+
coordinatorUrl?: string | URL;
13+
}
14+
15+
export const createSqlSync = (props: Props): SQLSync => {
16+
return new SQLSync(props.workerUrl, props.wasmUrl, props.coordinatorUrl);
17+
};
18+
19+
export const SQLSyncProvider: ParentComponent<Props> = (props) => {
20+
const [sqlSync, setSQLSync] = createSignal<SQLSync>(createSqlSync(props));
21+
22+
onCleanup(() => {
23+
const s = sqlSync();
24+
if (s) {
25+
s.close();
26+
}
27+
});
28+
29+
return (
30+
<SQLSyncContext.Provider value={[sqlSync, setSQLSync]}>
31+
{props.children}
32+
</SQLSyncContext.Provider>
33+
);
34+
};

0 commit comments

Comments
 (0)