├── .dockerignore ├── .eslintrc.cjs ├── .gitignore ├── .prettierrc ├── .yarnrc.yml ├── Dockerfile ├── README.md ├── dbs └── .gitkeep ├── etc └── litefs.yml ├── fly.toml ├── index.html ├── notes.md ├── package.json ├── pnpm-lock.yaml ├── public └── vite.svg ├── server.js ├── src ├── App.css ├── App.tsx ├── Root.tsx ├── assets │ ├── react.svg │ ├── vlcn-hand.png │ └── vlcn.png ├── index.css ├── main.tsx ├── schemas │ └── main2.sql ├── support │ └── randomWords.ts ├── sync-worker.ts └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.dockerignore: -------------------------------------------------------------------------------- 1 | fly.toml 2 | Dockerfile 3 | .dockerignore 4 | node_modules 5 | .git 6 | dbs -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { browser: true, es2020: true }, 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:react-hooks/recommended', 7 | ], 8 | parser: '@typescript-eslint/parser', 9 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 10 | plugins: ['react-refresh'], 11 | rules: { 12 | 'react-refresh/only-export-components': 'warn', 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | dbs/ 26 | 27 | public/assets/crsqlite-*.wasm 28 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "singleQuote": false 5 | } 6 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bullseye as builder 2 | 3 | ENV PATH=/usr/local/node/bin:$PATH 4 | ARG NODE_VERSION=19.0.1 5 | 6 | RUN apt-get update; apt install -y curl python-is-python3 pkg-config build-essential && \ 7 | curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \ 8 | /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \ 9 | rm -rf /tmp/node-build-master 10 | 11 | RUN mkdir /app 12 | WORKDIR /app 13 | 14 | COPY . . 15 | 16 | RUN npm install -g pnpm 17 | RUN pnpm install 18 | RUN pnpm run build 19 | 20 | 21 | FROM debian:bullseye-slim 22 | 23 | LABEL fly_launch_runtime="nodejs" 24 | 25 | COPY --from=builder /usr/local/node /usr/local/node 26 | COPY --from=builder /app /app 27 | 28 | WORKDIR /app 29 | ENV NODE_ENV production 30 | ENV PATH /usr/local/node/bin:$PATH 31 | 32 | CMD [ "pnpm", "run", "start" ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vite-starter 2 | 3 | Getting started with pnpm/npm/yarn create: 4 | 5 | ``` 6 | # npm 7 | npm create @vlcn.io@latest your-app-name 8 | # pnpm 9 | pnpm create @vlcn.io your-app-name 10 | # yarn 11 | yarn create @vlcn.io your-app-name 12 | ``` 13 | 14 | Or, just cloning directly: 15 | 16 | ``` 17 | git clone git@github.com:vlcn-io/vite-starter.git 18 | npm install 19 | npm dev 20 | ``` 21 | 22 | What you get: 23 | - A client ([App.tsx](https://github.com/vlcn-io/vite-starter/blob/main/src/App.tsx)) that runs a SQLite DB 24 | - A server ([server.js](https://github.com/vlcn-io/vite-starter/blob/main/server.js)) that the client (or many clients) can sync to when online 25 | - A database schema file ([schemas/main.mjs](https://github.com/vlcn-io/vite-starter/blob/main/src/schemas/main.mjs) that is automatically migrated to (auto migration is still in beta! You may find yourself needing to wipe the DB (clear indexeddb or change dbid) when using auto-migrate) on server and client restart. 26 | 27 | 28 | Demo Video: [![example scaffolding result](https://img.youtube.com/vi/QJBQLYmXReI/0.jpg)](https://www.youtube.com/watch?v=QJBQLYmXReI) 29 | 30 | Deployed Scaffolding: https://vite-starter2.fly.dev/ Try it out! Collaborate between all your devices. 31 | -------------------------------------------------------------------------------- /dbs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vlcn-io/vite-starter/72d9f0496e2e3ff78bedc5f81e79f145a1e96a34/dbs/.gitkeep -------------------------------------------------------------------------------- /etc/litefs.yml: -------------------------------------------------------------------------------- 1 | # This directory is where your application will access the database. 2 | fuse: 3 | dir: "/app/dbs" 4 | 5 | # This directory is where LiteFS will store internal data. 6 | # You must place this directory on a persistent volume. 7 | data: 8 | dir: "/var/lib/litefs" 9 | 10 | exec: 11 | - cmd: "pnpm run start" 12 | 13 | # The lease section specifies how the cluster will be managed. We're using the 14 | # "consul" lease type so that our application can dynamically change the primary. 15 | # 16 | # These environment variables will be available in your Fly.io application. 17 | lease: 18 | type: "consul" 19 | advertise-url: "http://${HOSTNAME}.vm.${FLY_APP_NAME}.internal:20202" 20 | candidate: ${FLY_REGION == PRIMARY_REGION} 21 | promote: true 22 | 23 | consul: 24 | url: "${FLY_CONSUL_URL}" 25 | key: "litefs/${FLY_APP_NAME}" 26 | -------------------------------------------------------------------------------- /fly.toml: -------------------------------------------------------------------------------- 1 | # fly.toml app configuration file generated for vite-starter on 2023-06-01T10:35:47-04:00 2 | # 3 | # See https://fly.io/docs/reference/configuration/ for information about how to use this file. 4 | # 5 | 6 | app = "vite-starter2" 7 | primary_region = "iad" 8 | kill_signal = "SIGINT" 9 | kill_timeout = "5s" 10 | 11 | [experimental] 12 | auto_rollback = true 13 | 14 | [env] 15 | PORT = "8080" 16 | PRIMARY_REGION = "iad" 17 | 18 | [[mounts]] 19 | source = "dbs_machines" 20 | destination = "/app/dbs" 21 | processes = ["app"] 22 | 23 | [[services]] 24 | protocol = "tcp" 25 | internal_port = 8080 26 | processes = ["app"] 27 | 28 | [[services.ports]] 29 | port = 80 30 | handlers = ["http"] 31 | force_https = true 32 | 33 | [[services.ports]] 34 | port = 443 35 | handlers = ["tls", "http"] 36 | [services.concurrency] 37 | type = "connections" 38 | hard_limit = 250 39 | soft_limit = 200 40 | 41 | [[services.tcp_checks]] 42 | interval = "15s" 43 | timeout = "2s" 44 | grace_period = "1s" 45 | restart_limit = 0 -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /notes.md: -------------------------------------------------------------------------------- 1 | more regions: 2 | https://fly.io/docs/litefs/speedrun/#5-add-some-replicas-in-other-regions 3 | 4 | ``` 5 | fly m clone --select --region lhr 6 | ``` 7 | 8 | consul: 9 | 10 | ``` 11 | fly consul attach 12 | ``` 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vlcn-vite-starter", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "node ./server.js", 8 | "start": "NODE_ENV=production node ./server.js", 9 | "build": "tsc && vite build", 10 | "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0" 11 | }, 12 | "dependencies": { 13 | "@vlcn.io/crsqlite-wasm": "0.16.0", 14 | "@vlcn.io/react": "3.1.0", 15 | "@vlcn.io/ws-browserdb": "0.2.0", 16 | "@vlcn.io/ws-client": "0.2.0", 17 | "@vlcn.io/ws-server": "0.2.3", 18 | "cors": "^2.8.5", 19 | "express": "^4.18.2", 20 | "react": "^18.2.0", 21 | "react-dom": "^18.2.0", 22 | "vite-express": "^0.9.2" 23 | }, 24 | "devDependencies": { 25 | "@types/react": "^18.0.28", 26 | "@types/react-dom": "^18.0.11", 27 | "@typescript-eslint/eslint-plugin": "^5.57.1", 28 | "@typescript-eslint/parser": "^5.57.1", 29 | "@vitejs/plugin-react": "^4.0.0", 30 | "eslint": "^8.38.0", 31 | "eslint-plugin-react-hooks": "^4.6.0", 32 | "eslint-plugin-react-refresh": "^0.3.4", 33 | "typescript": "^5.0.2", 34 | "vite": "^4.3.9", 35 | "vite-plugin-pwa": "^0.16.4" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import ViteExpress from "vite-express"; 3 | import { attachWebsocketServer } from "@vlcn.io/ws-server"; 4 | import * as http from "http"; 5 | 6 | const PORT = parseInt(process.env.PORT || "8080"); 7 | 8 | const app = express(); 9 | const server = http.createServer(app); 10 | 11 | const wsConfig = { 12 | dbFolder: "./dbs", 13 | schemaFolder: "./src/schemas", 14 | pathPattern: /\/sync/, 15 | }; 16 | 17 | attachWebsocketServer(server, wsConfig); 18 | 19 | server.listen(PORT, () => 20 | console.log("info", `listening on http://localhost:${PORT}!`) 21 | ); 22 | 23 | ViteExpress.bind(app, server); 24 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 2em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | 15 | .logo:hover { 16 | filter: drop-shadow(0 0 2em #646cffaa); 17 | } 18 | .logo.react:hover { 19 | filter: drop-shadow(0 0 2em #61dafbaa); 20 | } 21 | .logo.vlcn:hover { 22 | filter: drop-shadow(0 0 2em #e3eaef); 23 | } 24 | 25 | .card { 26 | padding: 2em; 27 | } 28 | 29 | .read-the-docs { 30 | color: #888; 31 | } 32 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { CtxAsync, useCachedState, useQuery, useSync } from "@vlcn.io/react"; 2 | import reactLogo from "./assets/react.svg"; 3 | import viteLogo from "/vite.svg"; 4 | import vlcnLogo from "./assets/vlcn.png"; 5 | import "./App.css"; 6 | import randomWords from "./support/randomWords.js"; 7 | import { useDB } from "@vlcn.io/react"; 8 | import SyncWorker from "./sync-worker.js?worker"; 9 | 10 | type TestRecord = { id: string; name: string }; 11 | const wordOptions = { exactly: 3, join: " " }; 12 | 13 | function getEndpoint() { 14 | let proto = "ws:"; 15 | const host = window.location.host; 16 | if (window.location.protocol === "https:") { 17 | proto = "wss:"; 18 | } 19 | 20 | return `${proto}//${host}/sync`; 21 | } 22 | 23 | const worker = new SyncWorker(); 24 | function App({ dbname }: { dbname: string }) { 25 | const ctx = useDB(dbname); 26 | useSync({ 27 | dbname, 28 | endpoint: getEndpoint(), 29 | room: dbname, 30 | worker, 31 | }); 32 | const data = useQuery( 33 | ctx, 34 | "SELECT * FROM test ORDER BY id DESC" 35 | ).data; 36 | 37 | const addData = () => { 38 | ctx.db.exec("INSERT INTO test (id, name) VALUES (?, ?);", [ 39 | nanoid(10), 40 | randomWords(wordOptions) as string, 41 | ]); 42 | }; 43 | 44 | const dropData = () => { 45 | ctx.db.exec("DELETE FROM test;"); 46 | }; 47 | 48 | return ( 49 | <> 50 |
51 | 52 | Vite logo 53 | 54 | 55 | React logo 56 | 57 | 58 | Vulcan logo 59 | 60 |
61 |

Vite + React + Vulcan

62 |
63 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | {data.map((row) => ( 76 | 77 | 78 | 81 | 82 | ))} 83 | 84 |
IDName
{row.id} 79 | 80 |
85 |

86 | Edit src/App.tsx and save to test HMR 87 |

88 |

89 | Open another browser and navigate to{" "} 90 | 91 | this window's url 92 | {" "} 93 | to test sync. 94 |

95 |
96 |

97 | Click on the Vite, React and Vulcan logos to learn more 98 |

99 | 100 | ); 101 | } 102 | 103 | function EditableItem({ 104 | ctx, 105 | id, 106 | value, 107 | }: { 108 | ctx: CtxAsync; 109 | id: string; 110 | value: string; 111 | }) { 112 | // Generally you will not need to use `useCachedState`. It is only required for highly interactive components 113 | // that write to the database on every interaction (e.g., keystroke or drag) or in cases where you want 114 | // to de-bounce your writes to the DB. 115 | // 116 | // `useCachedState` will never be required once when one of the following is true: 117 | // a. We complete the synchronous Reactive SQL layer (SQLiteRX) 118 | // b. We figure out how to get SQLite-WASM to do a write + read round-trip in a single event loop tick 119 | const [cachedValue, setCachedValue] = useCachedState(value); 120 | const onChange = async (e: React.ChangeEvent) => { 121 | setCachedValue(e.target.value); 122 | // You could de-bounce your write to the DB here if so desired. 123 | return ctx.db.exec("UPDATE test SET name = ? WHERE id = ?;", [ 124 | e.target.value, 125 | id, 126 | ]); 127 | }; 128 | 129 | return ; 130 | } 131 | 132 | export default App; 133 | 134 | const nanoid = (t = 21) => 135 | crypto 136 | .getRandomValues(new Uint8Array(t)) 137 | .reduce( 138 | (t, e) => 139 | (t += 140 | (e &= 63) < 36 141 | ? e.toString(36) 142 | : e < 62 143 | ? (e - 26).toString(36).toUpperCase() 144 | : e > 62 145 | ? "-" 146 | : "_"), 147 | "" 148 | ); 149 | -------------------------------------------------------------------------------- /src/Root.tsx: -------------------------------------------------------------------------------- 1 | import App from "./App.tsx"; 2 | import schemaContent from "./schemas/main2.sql?raw"; 3 | import { DBProvider } from "@vlcn.io/react"; 4 | import { useEffect, useState } from "react"; 5 | 6 | /** 7 | * Generates a random room name to sync with or pulls one from local storage. 8 | */ 9 | function getRoom(hash: HashBag): string { 10 | return hash.room || localStorage.getItem("room") || newRoom(); 11 | } 12 | 13 | function hashChanged() { 14 | const hash = parseHash(); 15 | const room = getRoom(hash); 16 | if (room != hash.room) { 17 | hash.room = room; 18 | window.location.hash = writeHash(hash); 19 | } 20 | localStorage.setItem("room", room); 21 | return room; 22 | } 23 | const room = hashChanged(); 24 | 25 | export default function Root() { 26 | const [theRoom, setTheRoom] = useState(room); 27 | useEffect(() => { 28 | const cb = () => { 29 | const room = hashChanged(); 30 | if (room != theRoom) { 31 | setTheRoom(room); 32 | } 33 | }; 34 | addEventListener("hashchange", cb); 35 | return () => { 36 | removeEventListener("hashchange", cb); 37 | }; 38 | }, []); // ignore -- theRoom is managed by the effect 39 | 40 | return ( 41 | } 48 | > 49 | ); 50 | } 51 | 52 | type HashBag = { [key: string]: string }; 53 | function parseHash(): HashBag { 54 | const hash = window.location.hash; 55 | const ret: { [key: string]: string } = {}; 56 | if (hash.length > 1) { 57 | const substr = hash.substring(1); 58 | const parts = substr.split(","); 59 | for (const part of parts) { 60 | const [key, value] = part.split("="); 61 | ret[key] = value; 62 | } 63 | } 64 | 65 | return ret; 66 | } 67 | 68 | function writeHash(hash: HashBag) { 69 | const parts = []; 70 | for (const key in hash) { 71 | parts.push(`${key}=${hash[key]}`); 72 | } 73 | return parts.join(","); 74 | } 75 | 76 | function newRoom() { 77 | return crypto.randomUUID().replaceAll("-", ""); 78 | } 79 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/vlcn-hand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vlcn-io/vite-starter/72d9f0496e2e3ff78bedc5f81e79f145a1e96a34/src/assets/vlcn-hand.png -------------------------------------------------------------------------------- /src/assets/vlcn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vlcn-io/vite-starter/72d9f0496e2e3ff78bedc5f81e79f145a1e96a34/src/assets/vlcn.png -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | -webkit-text-size-adjust: 100%; 15 | } 16 | 17 | a { 18 | font-weight: 500; 19 | color: #646cff; 20 | text-decoration: inherit; 21 | } 22 | a:hover { 23 | color: #535bf2; 24 | } 25 | 26 | body { 27 | margin: 0; 28 | display: flex; 29 | place-items: center; 30 | min-width: 320px; 31 | min-height: 100vh; 32 | } 33 | 34 | h1 { 35 | font-size: 3.2em; 36 | line-height: 1.1; 37 | } 38 | 39 | button { 40 | border-radius: 8px; 41 | border: 1px solid transparent; 42 | padding: 0.6em 1.2em; 43 | font-size: 1em; 44 | font-weight: 500; 45 | font-family: inherit; 46 | background-color: #1a1a1a; 47 | cursor: pointer; 48 | transition: border-color 0.25s; 49 | } 50 | button:hover { 51 | border-color: #646cff; 52 | } 53 | button:focus, 54 | button:focus-visible { 55 | outline: 4px auto -webkit-focus-ring-color; 56 | } 57 | 58 | table { 59 | border-collapse: collapse; 60 | border-spacing: 0; 61 | width: 100%; 62 | text-align: center; 63 | margin-top: 20px; 64 | margin-bottom: 80px; 65 | } 66 | 67 | table input { 68 | background: none; 69 | border: none; 70 | outline: none; 71 | width: 100%; 72 | height: 100%; 73 | text-align: center; 74 | padding: 8px; 75 | font-size: 1em; 76 | } 77 | 78 | table input:focus { 79 | background: grey; 80 | } 81 | 82 | thead { 83 | font-size: 24px; 84 | } 85 | 86 | @media (prefers-color-scheme: light) { 87 | :root { 88 | color: #213547; 89 | background-color: #ffffff; 90 | } 91 | a:hover { 92 | color: #747bff; 93 | } 94 | button { 95 | background-color: #f9f9f9; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from "react-dom/client"; 2 | 3 | import "./index.css"; 4 | 5 | import React from "react"; 6 | import Root from "./Root.tsx"; 7 | 8 | // Launch our app. 9 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /src/schemas/main2.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS test (id PRIMARY KEY NOT NULL, name TEXT); 2 | SELECT crsql_as_crr('test'); 3 | -------------------------------------------------------------------------------- /src/support/randomWords.ts: -------------------------------------------------------------------------------- 1 | var wordList = [ 2 | // Borrowed from xkcd password generator which borrowed it from wherever 3 | "ability","able","aboard","about","above","accept","accident","according", 4 | "account","accurate","acres","across","act","action","active","activity", 5 | "actual","actually","add","addition","additional","adjective","adult","adventure", 6 | "advice","affect","afraid","after","afternoon","again","against","age", 7 | "ago","agree","ahead","aid","air","airplane","alike","alive", 8 | "all","allow","almost","alone","along","aloud","alphabet","already", 9 | "also","although","am","among","amount","ancient","angle","angry", 10 | "animal","announced","another","answer","ants","any","anybody","anyone", 11 | "anything","anyway","anywhere","apart","apartment","appearance","apple","applied", 12 | "appropriate","are","area","arm","army","around","arrange","arrangement", 13 | "arrive","arrow","art","article","as","aside","ask","asleep", 14 | "at","ate","atmosphere","atom","atomic","attached","attack","attempt", 15 | "attention","audience","author","automobile","available","average","avoid","aware", 16 | "away","baby","back","bad","badly","bag","balance","ball", 17 | "balloon","band","bank","bar","bare","bark","barn","base", 18 | "baseball","basic","basis","basket","bat","battle","be","bean", 19 | "bear","beat","beautiful","beauty","became","because","become","becoming", 20 | "bee","been","before","began","beginning","begun","behavior","behind", 21 | "being","believed","bell","belong","below","belt","bend","beneath", 22 | "bent","beside","best","bet","better","between","beyond","bicycle", 23 | "bigger","biggest","bill","birds","birth","birthday","bit","bite", 24 | "black","blank","blanket","blew","blind","block","blood","blow", 25 | "blue","board","boat","body","bone","book","border","born", 26 | "both","bottle","bottom","bound","bow","bowl","box","boy", 27 | "brain","branch","brass","brave","bread","break","breakfast","breath", 28 | "breathe","breathing","breeze","brick","bridge","brief","bright","bring", 29 | "broad","broke","broken","brother","brought","brown","brush","buffalo", 30 | "build","building","built","buried","burn","burst","bus","bush", 31 | "business","busy","but","butter","buy","by","cabin","cage", 32 | "cake","call","calm","came","camera","camp","can","canal", 33 | "cannot","cap","capital","captain","captured","car","carbon","card", 34 | "care","careful","carefully","carried","carry","case","cast","castle", 35 | "cat","catch","cattle","caught","cause","cave","cell","cent", 36 | "center","central","century","certain","certainly","chain","chair","chamber", 37 | "chance","change","changing","chapter","character","characteristic","charge","chart", 38 | "check","cheese","chemical","chest","chicken","chief","child","children", 39 | "choice","choose","chose","chosen","church","circle","circus","citizen", 40 | "city","class","classroom","claws","clay","clean","clear","clearly", 41 | "climate","climb","clock","close","closely","closer","cloth","clothes", 42 | "clothing","cloud","club","coach","coal","coast","coat","coffee", 43 | "cold","collect","college","colony","color","column","combination","combine", 44 | "come","comfortable","coming","command","common","community","company","compare", 45 | "compass","complete","completely","complex","composed","composition","compound","concerned", 46 | "condition","congress","connected","consider","consist","consonant","constantly","construction", 47 | "contain","continent","continued","contrast","control","conversation","cook","cookies", 48 | "cool","copper","copy","corn","corner","correct","correctly","cost", 49 | "cotton","could","count","country","couple","courage","course","court", 50 | "cover","cow","cowboy","crack","cream","create","creature","crew", 51 | "crop","cross","crowd","cry","cup","curious","current","curve", 52 | "customs","cut","cutting","daily","damage","dance","danger","dangerous", 53 | "dark","darkness","date","daughter","dawn","day","dead","deal", 54 | "dear","death","decide","declared","deep","deeply","deer","definition", 55 | "degree","depend","depth","describe","desert","design","desk","detail", 56 | "determine","develop","development","diagram","diameter","did","die","differ", 57 | "difference","different","difficult","difficulty","dig","dinner","direct","direction", 58 | "directly","dirt","dirty","disappear","discover","discovery","discuss","discussion", 59 | "disease","dish","distance","distant","divide","division","do","doctor", 60 | "does","dog","doing","doll","dollar","done","donkey","door", 61 | "dot","double","doubt","down","dozen","draw","drawn","dream", 62 | "dress","drew","dried","drink","drive","driven","driver","driving", 63 | "drop","dropped","drove","dry","duck","due","dug","dull", 64 | "during","dust","duty","each","eager","ear","earlier","early", 65 | "earn","earth","easier","easily","east","easy","eat","eaten", 66 | "edge","education","effect","effort","egg","eight","either","electric", 67 | "electricity","element","elephant","eleven","else","empty","end","enemy", 68 | "energy","engine","engineer","enjoy","enough","enter","entire","entirely", 69 | "environment","equal","equally","equator","equipment","escape","especially","essential", 70 | "establish","even","evening","event","eventually","ever","every","everybody", 71 | "everyone","everything","everywhere","evidence","exact","exactly","examine","example", 72 | "excellent","except","exchange","excited","excitement","exciting","exclaimed","exercise", 73 | "exist","expect","experience","experiment","explain","explanation","explore","express", 74 | "expression","extra","eye","face","facing","fact","factor","factory", 75 | "failed","fair","fairly","fall","fallen","familiar","family","famous", 76 | "far","farm","farmer","farther","fast","fastened","faster","fat", 77 | "father","favorite","fear","feathers","feature","fed","feed","feel", 78 | "feet","fell","fellow","felt","fence","few","fewer","field", 79 | "fierce","fifteen","fifth","fifty","fight","fighting","figure","fill", 80 | "film","final","finally","find","fine","finest","finger","finish", 81 | "fire","fireplace","firm","first","fish","five","fix","flag", 82 | "flame","flat","flew","flies","flight","floating","floor","flow", 83 | "flower","fly","fog","folks","follow","food","foot","football", 84 | "for","force","foreign","forest","forget","forgot","forgotten","form", 85 | "former","fort","forth","forty","forward","fought","found","four", 86 | "fourth","fox","frame","free","freedom","frequently","fresh","friend", 87 | "friendly","frighten","frog","from","front","frozen","fruit","fuel", 88 | "full","fully","fun","function","funny","fur","furniture","further", 89 | "future","gain","game","garage","garden","gas","gasoline","gate", 90 | "gather","gave","general","generally","gentle","gently","get","getting", 91 | "giant","gift","girl","give","given","giving","glad","glass", 92 | "globe","go","goes","gold","golden","gone","good","goose", 93 | "got","government","grabbed","grade","gradually","grain","grandfather","grandmother", 94 | "graph","grass","gravity","gray","great","greater","greatest","greatly", 95 | "green","grew","ground","group","grow","grown","growth","guard", 96 | "guess","guide","gulf","gun","habit","had","hair","half", 97 | "halfway","hall","hand","handle","handsome","hang","happen","happened", 98 | "happily","happy","harbor","hard","harder","hardly","has","hat", 99 | "have","having","hay","he","headed","heading","health","heard", 100 | "hearing","heart","heat","heavy","height","held","hello","help", 101 | "helpful","her","herd","here","herself","hidden","hide","high", 102 | "higher","highest","highway","hill","him","himself","his","history", 103 | "hit","hold","hole","hollow","home","honor","hope","horn", 104 | "horse","hospital","hot","hour","house","how","however","huge", 105 | "human","hundred","hung","hungry","hunt","hunter","hurried","hurry", 106 | "hurt","husband","ice","idea","identity","if","ill","image", 107 | "imagine","immediately","importance","important","impossible","improve","in","inch", 108 | "include","including","income","increase","indeed","independent","indicate","individual", 109 | "industrial","industry","influence","information","inside","instance","instant","instead", 110 | "instrument","interest","interior","into","introduced","invented","involved","iron", 111 | "is","island","it","its","itself","jack","jar","jet", 112 | "job","join","joined","journey","joy","judge","jump","jungle", 113 | "just","keep","kept","key","kids","kill","kind","kitchen", 114 | "knew","knife","know","knowledge","known","label","labor","lack", 115 | "lady","laid","lake","lamp","land","language","large","larger", 116 | "largest","last","late","later","laugh","law","lay","layers", 117 | "lead","leader","leaf","learn","least","leather","leave","leaving", 118 | "led","left","leg","length","lesson","let","letter","level", 119 | "library","lie","life","lift","light","like","likely","limited", 120 | "line","lion","lips","liquid","list","listen","little","live", 121 | "living","load","local","locate","location","log","lonely","long", 122 | "longer","look","loose","lose","loss","lost","lot","loud", 123 | "love","lovely","low","lower","luck","lucky","lunch","lungs", 124 | "lying","machine","machinery","mad","made","magic","magnet","mail", 125 | "main","mainly","major","make","making","man","managed","manner", 126 | "manufacturing","many","map","mark","market","married","mass","massage", 127 | "master","material","mathematics","matter","may","maybe","me","meal", 128 | "mean","means","meant","measure","meat","medicine","meet","melted", 129 | "member","memory","men","mental","merely","met","metal","method", 130 | "mice","middle","might","mighty","mile","military","milk","mill", 131 | "mind","mine","minerals","minute","mirror","missing","mission","mistake", 132 | "mix","mixture","model","modern","molecular","moment","money","monkey", 133 | "month","mood","moon","more","morning","most","mostly","mother", 134 | "motion","motor","mountain","mouse","mouth","move","movement","movie", 135 | "moving","mud","muscle","music","musical","must","my","myself", 136 | "mysterious","nails","name","nation","national","native","natural","naturally", 137 | "nature","near","nearby","nearer","nearest","nearly","necessary","neck", 138 | "needed","needle","needs","negative","neighbor","neighborhood","nervous","nest", 139 | "never","new","news","newspaper","next","nice","night","nine", 140 | "no","nobody","nodded","noise","none","noon","nor","north", 141 | "nose","not","note","noted","nothing","notice","noun","now", 142 | "number","numeral","nuts","object","observe","obtain","occasionally","occur", 143 | "ocean","of","off","offer","office","officer","official","oil", 144 | "old","older","oldest","on","once","one","only","onto", 145 | "open","operation","opinion","opportunity","opposite","or","orange","orbit", 146 | "order","ordinary","organization","organized","origin","original","other","ought", 147 | "our","ourselves","out","outer","outline","outside","over","own", 148 | "owner","oxygen","pack","package","page","paid","pain","paint", 149 | "pair","palace","pale","pan","paper","paragraph","parallel","parent", 150 | "park","part","particles","particular","particularly","partly","parts","party", 151 | "pass","passage","past","path","pattern","pay","peace","pen", 152 | "pencil","people","per","percent","perfect","perfectly","perhaps","period", 153 | "person","personal","pet","phrase","physical","piano","pick","picture", 154 | "pictured","pie","piece","pig","pile","pilot","pine","pink", 155 | "pipe","pitch","place","plain","plan","plane","planet","planned", 156 | "planning","plant","plastic","plate","plates","play","pleasant","please", 157 | "pleasure","plenty","plural","plus","pocket","poem","poet","poetry", 158 | "point","pole","police","policeman","political","pond","pony","pool", 159 | "poor","popular","population","porch","port","position","positive","possible", 160 | "possibly","post","pot","potatoes","pound","pour","powder","power", 161 | "powerful","practical","practice","prepare","present","president","press","pressure", 162 | "pretty","prevent","previous","price","pride","primitive","principal","principle", 163 | "printed","private","prize","probably","problem","process","produce","product", 164 | "production","program","progress","promised","proper","properly","property","protection", 165 | "proud","prove","provide","public","pull","pupil","pure","purple", 166 | "purpose","push","put","putting","quarter","queen","question","quick", 167 | "quickly","quiet","quietly","quite","rabbit","race","radio","railroad", 168 | "rain","raise","ran","ranch","range","rapidly","rate","rather", 169 | "raw","rays","reach","read","reader","ready","real","realize", 170 | "rear","reason","recall","receive","recent","recently","recognize","record", 171 | "red","refer","refused","region","regular","related","relationship","religious", 172 | "remain","remarkable","remember","remove","repeat","replace","replied","report", 173 | "represent","require","research","respect","rest","result","return","review", 174 | "rhyme","rhythm","rice","rich","ride","riding","right","ring", 175 | "rise","rising","river","road","roar","rock","rocket","rocky", 176 | "rod","roll","roof","room","root","rope","rose","rough", 177 | "round","route","row","rubbed","rubber","rule","ruler","run", 178 | "running","rush","sad","saddle","safe","safety","said","sail", 179 | "sale","salmon","salt","same","sand","sang","sat","satellites", 180 | "satisfied","save","saved","saw","say","scale","scared","scene", 181 | "school","science","scientific","scientist","score","screen","sea","search", 182 | "season","seat","second","secret","section","see","seed","seeing", 183 | "seems","seen","seldom","select","selection","sell","send","sense", 184 | "sent","sentence","separate","series","serious","serve","service","sets", 185 | "setting","settle","settlers","seven","several","shade","shadow","shake", 186 | "shaking","shall","shallow","shape","share","sharp","she","sheep", 187 | "sheet","shelf","shells","shelter","shine","shinning","ship","shirt", 188 | "shoe","shoot","shop","shore","short","shorter","shot","should", 189 | "shoulder","shout","show","shown","shut","sick","sides","sight", 190 | "sign","signal","silence","silent","silk","silly","silver","similar", 191 | "simple","simplest","simply","since","sing","single","sink","sister", 192 | "sit","sitting","situation","six","size","skill","skin","sky", 193 | "slabs","slave","sleep","slept","slide","slight","slightly","slip", 194 | "slipped","slope","slow","slowly","small","smaller","smallest","smell", 195 | "smile","smoke","smooth","snake","snow","so","soap","social", 196 | "society","soft","softly","soil","solar","sold","soldier","solid", 197 | "solution","solve","some","somebody","somehow","someone","something","sometime", 198 | "somewhere","son","song","soon","sort","sound","source","south", 199 | "southern","space","speak","special","species","specific","speech","speed", 200 | "spell","spend","spent","spider","spin","spirit","spite","split", 201 | "spoken","sport","spread","spring","square","stage","stairs","stand", 202 | "standard","star","stared","start","state","statement","station","stay", 203 | "steady","steam","steel","steep","stems","step","stepped","stick", 204 | "stiff","still","stock","stomach","stone","stood","stop","stopped", 205 | "store","storm","story","stove","straight","strange","stranger","straw", 206 | "stream","street","strength","stretch","strike","string","strip","strong", 207 | "stronger","struck","structure","struggle","stuck","student","studied","studying", 208 | "subject","substance","success","successful","such","sudden","suddenly","sugar", 209 | "suggest","suit","sum","summer","sun","sunlight","supper","supply", 210 | "support","suppose","sure","surface","surprise","surrounded","swam","sweet", 211 | "swept","swim","swimming","swing","swung","syllable","symbol","system", 212 | "table","tail","take","taken","tales","talk","tall","tank", 213 | "tape","task","taste","taught","tax","tea","teach","teacher", 214 | "team","tears","teeth","telephone","television","tell","temperature","ten", 215 | "tent","term","terrible","test","than","thank","that","thee", 216 | "them","themselves","then","theory","there","therefore","these","they", 217 | "thick","thin","thing","think","third","thirty","this","those", 218 | "thou","though","thought","thousand","thread","three","threw","throat", 219 | "through","throughout","throw","thrown","thumb","thus","thy","tide", 220 | "tie","tight","tightly","till","time","tin","tiny","tip", 221 | "tired","title","to","tobacco","today","together","told","tomorrow", 222 | "tone","tongue","tonight","too","took","tool","top","topic", 223 | "torn","total","touch","toward","tower","town","toy","trace", 224 | "track","trade","traffic","trail","train","transportation","trap","travel", 225 | "treated","tree","triangle","tribe","trick","tried","trip","troops", 226 | "tropical","trouble","truck","trunk","truth","try","tube","tune", 227 | "turn","twelve","twenty","twice","two","type","typical","uncle", 228 | "under","underline","understanding","unhappy","union","unit","universe","unknown", 229 | "unless","until","unusual","up","upon","upper","upward","us", 230 | "use","useful","using","usual","usually","valley","valuable","value", 231 | "vapor","variety","various","vast","vegetable","verb","vertical","very", 232 | "vessels","victory","view","village","visit","visitor","voice","volume", 233 | "vote","vowel","voyage","wagon","wait","walk","wall","want", 234 | "war","warm","warn","was","wash","waste","watch","water", 235 | "wave","way","we","weak","wealth","wear","weather","week", 236 | "weigh","weight","welcome","well","went","were","west","western", 237 | "wet","whale","what","whatever","wheat","wheel","when","whenever", 238 | "where","wherever","whether","which","while","whispered","whistle","white", 239 | "who","whole","whom","whose","why","wide","widely","wife", 240 | "wild","will","willing","win","wind","window","wing","winter", 241 | "wire","wise","wish","with","within","without","wolf","women", 242 | "won","wonder","wonderful","wood","wooden","wool","word","wore", 243 | "work","worker","world","worried","worry","worse","worth","would", 244 | "wrapped","write","writer","writing","written","wrong","wrote","yard", 245 | "year","yellow","yes","yesterday","yet","you","young","younger", 246 | "your","yourself","youth","zero","zebra","zipper","zoo","zulu" 247 | ]; 248 | 249 | export default function words( 250 | options: any 251 | ) { 252 | 253 | function word() { 254 | if (options && options.maxLength > 1) { 255 | return generateWordWithMaxLength(); 256 | } else { 257 | return generateRandomWord(); 258 | } 259 | } 260 | 261 | function generateWordWithMaxLength() { 262 | var rightSize = false; 263 | var wordUsed; 264 | while (!rightSize) { 265 | wordUsed = generateRandomWord(); 266 | if(wordUsed.length <= (options.maxLength)) { 267 | rightSize = true; 268 | } 269 | 270 | } 271 | return wordUsed; 272 | } 273 | 274 | function generateRandomWord() { 275 | return wordList[randInt(wordList.length)]; 276 | } 277 | 278 | function randInt(lessThan: number) { 279 | return Math.floor(Math.random() * lessThan); 280 | } 281 | 282 | // No arguments = generate one word 283 | if (typeof(options) === 'undefined') { 284 | return word(); 285 | } 286 | 287 | // Just a number = return that many words 288 | if (typeof(options) === 'number') { 289 | options = { exactly: options }; 290 | } 291 | 292 | // options supported: exactly, min, max, join 293 | if (options.exactly) { 294 | options.min = options.exactly; 295 | options.max = options.exactly; 296 | } 297 | 298 | // not a number = one word par string 299 | if (typeof(options.wordsPerString) !== 'number') { 300 | options.wordsPerString = 1; 301 | } 302 | 303 | //not a function = returns the raw word 304 | if (typeof(options.formatter) !== 'function') { 305 | options.formatter = (word: string) => word; 306 | } 307 | 308 | //not a string = separator is a space 309 | if (typeof(options.separator) !== 'string') { 310 | options.separator = ' '; 311 | } 312 | 313 | var total = options.min + randInt(options.max + 1 - options.min); 314 | var results: any = []; 315 | var token = ''; 316 | var relativeIndex = 0; 317 | 318 | for (var i = 0; (i < total * options.wordsPerString); i++) { 319 | if (relativeIndex === options.wordsPerString - 1) { 320 | token += options.formatter(word(), relativeIndex); 321 | } 322 | else { 323 | token += options.formatter(word(), relativeIndex) + options.separator; 324 | } 325 | relativeIndex++; 326 | if ((i + 1) % options.wordsPerString === 0) { 327 | results.push(token); 328 | token = ''; 329 | relativeIndex = 0; 330 | } 331 | 332 | } 333 | if (typeof options.join === 'string') { 334 | results = results.join(options.join); 335 | } 336 | 337 | return results; 338 | } 339 | -------------------------------------------------------------------------------- /src/sync-worker.ts: -------------------------------------------------------------------------------- 1 | import { Config, defaultConfig } from "@vlcn.io/ws-client"; 2 | import { start } from "@vlcn.io/ws-client/worker.js"; 3 | import { createDbProvider } from "@vlcn.io/ws-browserdb"; 4 | 5 | export const config: Config = { 6 | dbProvider: createDbProvider(), 7 | transportProvider: defaultConfig.transportProvider, 8 | }; 9 | 10 | start(config); 11 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 5 | "module": "ESNext", 6 | "skipLibCheck": true, 7 | "allowJs": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 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 | } 11 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | import { VitePWA } from "vite-plugin-pwa"; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | build: { 8 | target: "esnext", 9 | }, 10 | optimizeDeps: { 11 | exclude: ["@vite/client", "@vite/env", "@vlcn.io/crsqlite-wasm"], 12 | esbuildOptions: { 13 | target: "esnext", 14 | }, 15 | }, 16 | plugins: [ 17 | react(), 18 | // VitePWA({ 19 | // workbox: { 20 | // globPatterns: [ 21 | // "**/*.js", 22 | // "**/*.css", 23 | // "**/*.svg", 24 | // "**/*.html", 25 | // "**/*.png", 26 | // "**/*.wasm", 27 | // ], 28 | // }, 29 | // }), 30 | ], 31 | server: { 32 | fs: { 33 | strict: false, 34 | }, 35 | }, 36 | }); 37 | --------------------------------------------------------------------------------