├── .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: [](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 |
61 | Vite + React + Vulcan
62 |
63 |
66 |
67 |
68 |
69 |
70 | ID |
71 | Name |
72 |
73 |
74 |
75 | {data.map((row) => (
76 |
77 | {row.id} |
78 |
79 |
80 | |
81 |
82 | ))}
83 |
84 |
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 |
--------------------------------------------------------------------------------