@korajs/cli 0.1.13 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/package.json +1 -1
  2. package/templates/react-basic/.env.example +3 -0
  3. package/templates/react-basic/README.md.hbs +53 -0
  4. package/templates/react-basic/package.json.hbs +8 -1
  5. package/templates/react-basic/src/kora-worker.ts +7 -0
  6. package/templates/react-basic/src/main.tsx +2 -1
  7. package/templates/react-basic/tsconfig.json +2 -1
  8. package/templates/react-basic/vite.config.ts +37 -1
  9. package/templates/react-sync/.env.example +6 -0
  10. package/templates/react-sync/README.md.hbs +63 -0
  11. package/templates/react-sync/package.json.hbs +12 -1
  12. package/templates/react-sync/server.ts +9 -5
  13. package/templates/react-sync/src/kora-worker.ts +7 -0
  14. package/templates/react-sync/src/main.tsx +9 -2
  15. package/templates/react-sync/tsconfig.json +2 -1
  16. package/templates/react-sync/vite.config.ts +47 -1
  17. package/templates/react-tailwind/.env.example +3 -0
  18. package/templates/react-tailwind/README.md.hbs +53 -0
  19. package/templates/react-tailwind/package.json.hbs +8 -1
  20. package/templates/react-tailwind/src/kora-worker.ts +7 -0
  21. package/templates/react-tailwind/src/main.tsx +2 -1
  22. package/templates/react-tailwind/tsconfig.json +2 -1
  23. package/templates/react-tailwind/vite.config.ts +37 -1
  24. package/templates/react-tailwind-sync/.env.example +6 -0
  25. package/templates/react-tailwind-sync/README.md.hbs +63 -0
  26. package/templates/react-tailwind-sync/package.json.hbs +12 -1
  27. package/templates/react-tailwind-sync/server.ts +9 -5
  28. package/templates/react-tailwind-sync/src/kora-worker.ts +7 -0
  29. package/templates/react-tailwind-sync/src/main.tsx +9 -2
  30. package/templates/react-tailwind-sync/tsconfig.json +2 -1
  31. package/templates/react-tailwind-sync/vite.config.ts +47 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@korajs/cli",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Kora.js CLI tooling and project scaffolding",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -0,0 +1,3 @@
1
+ # No environment variables required for local-only mode.
2
+ # To enable sync, see the README for instructions.
3
+ # VITE_SYNC_URL=ws://localhost:3001
@@ -0,0 +1,53 @@
1
+ # {{projectName}}
2
+
3
+ An offline-first application built with [Kora.js](https://ehoneahobed.github.io/kora/).
4
+
5
+ ## Getting Started
6
+
7
+ ```bash
8
+ npm run dev
9
+ ```
10
+
11
+ - **App**: http://localhost:5173
12
+
13
+ ## Project Structure
14
+
15
+ ```
16
+ src/
17
+ schema.ts # Data schema definition
18
+ main.tsx # App initialization and Kora setup
19
+ App.tsx # Main application component
20
+ kora-worker.ts # SQLite WASM web worker
21
+ index.css # Styles
22
+ kora.config.ts # Kora configuration
23
+ ```
24
+
25
+ ## Scripts
26
+
27
+ | Script | Description |
28
+ |--------|-------------|
29
+ | `npm run dev` | Start dev server |
30
+ | `npm run build` | Build for production |
31
+ | `npm run preview` | Preview production build |
32
+
33
+ ## Adding Sync
34
+
35
+ To enable real-time sync across devices, update `src/main.tsx`:
36
+
37
+ ```typescript
38
+ const app = createApp({
39
+ schema,
40
+ sync: {
41
+ url: import.meta.env.VITE_SYNC_URL || 'ws://localhost:3001',
42
+ },
43
+ // ...
44
+ })
45
+ ```
46
+
47
+ Then create a `server.ts` and install `@korajs/server`. See the [Sync Configuration guide](https://ehoneahobed.github.io/kora/guide/sync-configuration).
48
+
49
+ ## Learn More
50
+
51
+ - [Documentation](https://ehoneahobed.github.io/kora/)
52
+ - [Storage Configuration](https://ehoneahobed.github.io/kora/guide/storage-configuration)
53
+ - [Schema Design](https://ehoneahobed.github.io/kora/guide/schema-design)
@@ -14,7 +14,14 @@
14
14
  "@korajs/store": "{{koraVersion}}",
15
15
  "@sqlite.org/sqlite-wasm": ">=3.51.0-build1",
16
16
  "react": "^19.0.0",
17
- "react-dom": "^19.0.0"
17
+ "react-dom": "^19.0.0",
18
+ "yjs": "^13.6.30"
19
+ },
20
+ "pnpm": {
21
+ "onlyBuiltDependencies": [
22
+ "esbuild",
23
+ "protobufjs"
24
+ ]
18
25
  },
19
26
  "devDependencies": {
20
27
  "@korajs/cli": "{{koraVersion}}",
@@ -1,3 +1,10 @@
1
1
  // Web Worker entry point for SQLite WASM.
2
2
  // Loaded automatically by the Kora store to run SQLite in a background thread.
3
+
4
+ // Import the WASM binary URL so Vite resolves it with the correct content hash.
5
+ // Without this, production builds fail because sqlite3 looks for the unhashed filename.
6
+ import sqliteWasmUrl from '@sqlite.org/sqlite-wasm/sqlite3.wasm?url'
7
+
8
+ ;(globalThis as Record<string, unknown>).__KORA_SQLITE_WASM_URL = sqliteWasmUrl
9
+
3
10
  import '@korajs/store/sqlite-wasm/worker'
@@ -5,11 +5,12 @@ import { createRoot } from 'react-dom/client'
5
5
  import schema from './schema'
6
6
  import { App } from './App'
7
7
  import './index.css'
8
+ import koraWorkerUrl from './kora-worker.ts?worker&url'
8
9
 
9
10
  const app = createApp({
10
11
  schema,
11
12
  store: {
12
- workerUrl: new URL('./kora-worker.ts', import.meta.url),
13
+ workerUrl: koraWorkerUrl,
13
14
  },
14
15
  devtools: true,
15
16
  })
@@ -8,7 +8,8 @@
8
8
  "noUncheckedIndexedAccess": true,
9
9
  "esModuleInterop": true,
10
10
  "skipLibCheck": true,
11
- "outDir": "dist"
11
+ "outDir": "dist",
12
+ "types": ["vite/client"]
12
13
  },
13
14
  "include": ["src"]
14
15
  }
@@ -1,3 +1,5 @@
1
+ import { existsSync, readdirSync, copyFileSync } from 'node:fs'
2
+ import { resolve, join } from 'node:path'
1
3
  import react from '@vitejs/plugin-react'
2
4
  import type { Plugin } from 'vite'
3
5
  import { defineConfig } from 'vite'
@@ -27,8 +29,42 @@ function crossOriginIsolation(): Plugin {
27
29
  }
28
30
  }
29
31
 
32
+ /**
33
+ * Fixes sqlite3 WASM assets in production builds:
34
+ * 1. Copies the hashed sqlite3.wasm to an unhashed name so Emscripten's locateFile works
35
+ * 2. Copies sqlite3-opfs-async-proxy.js from node_modules (not bundled by Vite since
36
+ * sqlite3 loads it dynamically) so the full OPFS VFS can initialize without errors
37
+ */
38
+ function sqliteWasmHotfix(): Plugin {
39
+ return {
40
+ name: 'sqlite-wasm-hotfix',
41
+ apply: 'build',
42
+ closeBundle() {
43
+ const assetsDir = resolve('dist', 'assets')
44
+ if (!existsSync(assetsDir)) return
45
+
46
+ // Copy hashed sqlite3.wasm to unhashed name
47
+ for (const file of readdirSync(assetsDir)) {
48
+ if (/^sqlite3-.+\.wasm$/.test(file)) {
49
+ copyFileSync(join(assetsDir, file), join(assetsDir, 'sqlite3.wasm'))
50
+ break
51
+ }
52
+ }
53
+
54
+ // Copy OPFS async proxy worker (dynamically loaded by sqlite3, not detected by Vite)
55
+ const proxyFile = resolve('node_modules', '@sqlite.org', 'sqlite-wasm', 'sqlite-wasm', 'jswasm', 'sqlite3-opfs-async-proxy.js')
56
+ if (existsSync(proxyFile)) {
57
+ copyFileSync(proxyFile, join(assetsDir, 'sqlite3-opfs-async-proxy.js'))
58
+ }
59
+ },
60
+ }
61
+ }
62
+
30
63
  export default defineConfig({
31
- plugins: [react(), crossOriginIsolation()],
64
+ plugins: [react(), crossOriginIsolation(), sqliteWasmHotfix()],
65
+ worker: {
66
+ format: 'es',
67
+ },
32
68
  optimizeDeps: {
33
69
  exclude: ['@sqlite.org/sqlite-wasm', '@korajs/store'],
34
70
  include: ['yjs'],
@@ -0,0 +1,6 @@
1
+ # Kora Sync Server
2
+ # WebSocket URL for the sync server (used by the client)
3
+ VITE_SYNC_URL=ws://localhost:3001
4
+
5
+ # Sync server port
6
+ PORT=3001
@@ -0,0 +1,63 @@
1
+ # {{projectName}}
2
+
3
+ An offline-first application built with [Kora.js](https://ehoneahobed.github.io/kora/).
4
+
5
+ ## Getting Started
6
+
7
+ ```bash
8
+ npm run dev
9
+ ```
10
+
11
+ This starts both the Vite dev server and the Kora sync server.
12
+
13
+ - **App**: http://localhost:5173
14
+ - **Sync server**: ws://localhost:3001
15
+
16
+ ## Project Structure
17
+
18
+ ```
19
+ src/
20
+ schema.ts # Data schema definition
21
+ main.tsx # App initialization and Kora setup
22
+ App.tsx # Main application component
23
+ kora-worker.ts # SQLite WASM web worker
24
+ index.css # Styles
25
+ server.ts # Kora sync server
26
+ kora.config.ts # Kora configuration
27
+ ```
28
+
29
+ ## Environment Variables
30
+
31
+ | Variable | Default | Description |
32
+ |----------|---------|-------------|
33
+ | `VITE_SYNC_URL` | `ws://localhost:3001` | WebSocket URL for the sync server |
34
+ | `PORT` | `3001` | Sync server port |
35
+
36
+ ## Scripts
37
+
38
+ | Script | Description |
39
+ |--------|-------------|
40
+ | `npm run dev` | Start dev server + sync server |
41
+ | `npm run dev:server` | Start sync server only |
42
+ | `npm run build` | Build for production |
43
+ | `npm run preview` | Preview production build |
44
+
45
+ ## Deployment
46
+
47
+ The client and sync server must be deployed separately:
48
+
49
+ - **Client**: Any static host (Vercel, Netlify, Cloudflare Pages)
50
+ - **Sync server**: Any Node.js host with WebSocket support (Railway, Render, Fly.io)
51
+
52
+ Set `VITE_SYNC_URL` to your deployed sync server URL when building:
53
+
54
+ ```bash
55
+ VITE_SYNC_URL=wss://your-server.example.com npm run build
56
+ ```
57
+
58
+ ## Learn More
59
+
60
+ - [Documentation](https://ehoneahobed.github.io/kora/)
61
+ - [Storage Configuration](https://ehoneahobed.github.io/kora/guide/storage-configuration)
62
+ - [Sync Configuration](https://ehoneahobed.github.io/kora/guide/sync-configuration)
63
+ - [Deployment Guide](https://ehoneahobed.github.io/kora/guide/deployment)
@@ -7,6 +7,7 @@
7
7
  "dev": "kora dev",
8
8
  "dev:server": "tsx server.ts",
9
9
  "build": "tsc && vite build",
10
+ "start": "node --import tsx server.ts",
10
11
  "preview": "vite preview"
11
12
  },
12
13
  "dependencies": {
@@ -15,13 +16,23 @@
15
16
  "@korajs/store": "{{koraVersion}}",
16
17
  "@sqlite.org/sqlite-wasm": ">=3.51.0-build1",
17
18
  "react": "^19.0.0",
18
- "react-dom": "^19.0.0"
19
+ "react-dom": "^19.0.0",
20
+ "ws": "^8.18.0",
21
+ "yjs": "^13.6.30"
22
+ },
23
+ "pnpm": {
24
+ "onlyBuiltDependencies": [
25
+ "better-sqlite3",
26
+ "esbuild",
27
+ "protobufjs"
28
+ ]
19
29
  },
20
30
  "devDependencies": {
21
31
  "@korajs/cli": "{{koraVersion}}",
22
32
  "@korajs/server": "{{koraVersion}}",
23
33
  "@types/react": "^19.0.0",
24
34
  "@types/react-dom": "^19.0.0",
35
+ "@types/ws": "^8.5.0",
25
36
  "@vitejs/plugin-react": "^4.3.0",
26
37
  "tsx": "^4.19.0",
27
38
  "typescript": "^5.7.0",
@@ -1,4 +1,4 @@
1
- import { createKoraServer, createSqliteServerStore } from '@korajs/server'
1
+ import { createProductionServer, createSqliteServerStore } from '@korajs/server'
2
2
 
3
3
  // SQLite persists data to disk — survives server restarts
4
4
  const store = createSqliteServerStore({ filename: './kora-server.db' })
@@ -12,11 +12,15 @@ const store = createSqliteServerStore({ filename: './kora-server.db' })
12
12
  // connectionString: 'postgresql://user:password@localhost:5432/mydb',
13
13
  // })
14
14
 
15
- const server = createKoraServer({
15
+ // Production server: serves static files + WebSocket sync on a single port.
16
+ // One port means one tunnel (ngrok, cloudflared) handles everything.
17
+ const server = createProductionServer({
16
18
  store,
17
- port: 3001,
19
+ port: Number(process.env.PORT) || 3001,
20
+ staticDir: './dist',
21
+ syncPath: '/kora-sync',
18
22
  })
19
23
 
20
- server.start().then(() => {
21
- console.log('Kora sync server running on ws://localhost:3001')
24
+ server.start().then((url) => {
25
+ console.log(`Kora app running at ${url}`)
22
26
  })
@@ -1,3 +1,10 @@
1
1
  // Web Worker entry point for SQLite WASM.
2
2
  // Loaded automatically by the Kora store to run SQLite in a background thread.
3
+
4
+ // Import the WASM binary URL so Vite resolves it with the correct content hash.
5
+ // Without this, production builds fail because sqlite3 looks for the unhashed filename.
6
+ import sqliteWasmUrl from '@sqlite.org/sqlite-wasm/sqlite3.wasm?url'
7
+
8
+ ;(globalThis as Record<string, unknown>).__KORA_SQLITE_WASM_URL = sqliteWasmUrl
9
+
3
10
  import '@korajs/store/sqlite-wasm/worker'
@@ -5,14 +5,21 @@ import { createRoot } from 'react-dom/client'
5
5
  import schema from './schema'
6
6
  import { App } from './App'
7
7
  import './index.css'
8
+ import koraWorkerUrl from './kora-worker.ts?worker&url'
9
+
10
+ // Build sync URL: use env var if set, otherwise derive from current page host.
11
+ // This allows the Vite proxy (/kora-sync → ws://localhost:3001) to work in dev,
12
+ // and also works through any tunnel (ngrok, cloudflared) without extra configuration.
13
+ const syncUrl = import.meta.env.VITE_SYNC_URL
14
+ || `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/kora-sync`
8
15
 
9
16
  const app = createApp({
10
17
  schema,
11
18
  sync: {
12
- url: 'ws://localhost:3001',
19
+ url: syncUrl,
13
20
  },
14
21
  store: {
15
- workerUrl: new URL('./kora-worker.ts', import.meta.url),
22
+ workerUrl: koraWorkerUrl,
16
23
  },
17
24
  devtools: true,
18
25
  })
@@ -8,7 +8,8 @@
8
8
  "noUncheckedIndexedAccess": true,
9
9
  "esModuleInterop": true,
10
10
  "skipLibCheck": true,
11
- "outDir": "dist"
11
+ "outDir": "dist",
12
+ "types": ["vite/client"]
12
13
  },
13
14
  "include": ["src"]
14
15
  }
@@ -1,3 +1,5 @@
1
+ import { existsSync, readdirSync, copyFileSync } from 'node:fs'
2
+ import { resolve, join } from 'node:path'
1
3
  import react from '@vitejs/plugin-react'
2
4
  import type { Plugin } from 'vite'
3
5
  import { defineConfig } from 'vite'
@@ -27,8 +29,42 @@ function crossOriginIsolation(): Plugin {
27
29
  }
28
30
  }
29
31
 
32
+ /**
33
+ * Fixes sqlite3 WASM assets in production builds:
34
+ * 1. Copies the hashed sqlite3.wasm to an unhashed name so Emscripten's locateFile works
35
+ * 2. Copies sqlite3-opfs-async-proxy.js from node_modules (not bundled by Vite since
36
+ * sqlite3 loads it dynamically) so the full OPFS VFS can initialize without errors
37
+ */
38
+ function sqliteWasmHotfix(): Plugin {
39
+ return {
40
+ name: 'sqlite-wasm-hotfix',
41
+ apply: 'build',
42
+ closeBundle() {
43
+ const assetsDir = resolve('dist', 'assets')
44
+ if (!existsSync(assetsDir)) return
45
+
46
+ // Copy hashed sqlite3.wasm to unhashed name
47
+ for (const file of readdirSync(assetsDir)) {
48
+ if (/^sqlite3-.+\.wasm$/.test(file)) {
49
+ copyFileSync(join(assetsDir, file), join(assetsDir, 'sqlite3.wasm'))
50
+ break
51
+ }
52
+ }
53
+
54
+ // Copy OPFS async proxy worker (dynamically loaded by sqlite3, not detected by Vite)
55
+ const proxyFile = resolve('node_modules', '@sqlite.org', 'sqlite-wasm', 'sqlite-wasm', 'jswasm', 'sqlite3-opfs-async-proxy.js')
56
+ if (existsSync(proxyFile)) {
57
+ copyFileSync(proxyFile, join(assetsDir, 'sqlite3-opfs-async-proxy.js'))
58
+ }
59
+ },
60
+ }
61
+ }
62
+
30
63
  export default defineConfig({
31
- plugins: [react(), crossOriginIsolation()],
64
+ plugins: [react(), crossOriginIsolation(), sqliteWasmHotfix()],
65
+ worker: {
66
+ format: 'es',
67
+ },
32
68
  optimizeDeps: {
33
69
  exclude: ['@sqlite.org/sqlite-wasm', '@korajs/store'],
34
70
  include: ['yjs'],
@@ -36,4 +72,14 @@ export default defineConfig({
36
72
  resolve: {
37
73
  dedupe: ['yjs'],
38
74
  },
75
+ server: {
76
+ allowedHosts: true,
77
+ proxy: {
78
+ '/kora-sync': {
79
+ target: 'ws://localhost:3001',
80
+ ws: true,
81
+ rewriteWsOrigin: true,
82
+ },
83
+ },
84
+ },
39
85
  })
@@ -0,0 +1,3 @@
1
+ # No environment variables required for local-only mode.
2
+ # To enable sync, see the README for instructions.
3
+ # VITE_SYNC_URL=ws://localhost:3001
@@ -0,0 +1,53 @@
1
+ # {{projectName}}
2
+
3
+ An offline-first application built with [Kora.js](https://ehoneahobed.github.io/kora/).
4
+
5
+ ## Getting Started
6
+
7
+ ```bash
8
+ npm run dev
9
+ ```
10
+
11
+ - **App**: http://localhost:5173
12
+
13
+ ## Project Structure
14
+
15
+ ```
16
+ src/
17
+ schema.ts # Data schema definition
18
+ main.tsx # App initialization and Kora setup
19
+ App.tsx # Main application component
20
+ kora-worker.ts # SQLite WASM web worker
21
+ index.css # Styles
22
+ kora.config.ts # Kora configuration
23
+ ```
24
+
25
+ ## Scripts
26
+
27
+ | Script | Description |
28
+ |--------|-------------|
29
+ | `npm run dev` | Start dev server |
30
+ | `npm run build` | Build for production |
31
+ | `npm run preview` | Preview production build |
32
+
33
+ ## Adding Sync
34
+
35
+ To enable real-time sync across devices, update `src/main.tsx`:
36
+
37
+ ```typescript
38
+ const app = createApp({
39
+ schema,
40
+ sync: {
41
+ url: import.meta.env.VITE_SYNC_URL || 'ws://localhost:3001',
42
+ },
43
+ // ...
44
+ })
45
+ ```
46
+
47
+ Then create a `server.ts` and install `@korajs/server`. See the [Sync Configuration guide](https://ehoneahobed.github.io/kora/guide/sync-configuration).
48
+
49
+ ## Learn More
50
+
51
+ - [Documentation](https://ehoneahobed.github.io/kora/)
52
+ - [Storage Configuration](https://ehoneahobed.github.io/kora/guide/storage-configuration)
53
+ - [Schema Design](https://ehoneahobed.github.io/kora/guide/schema-design)
@@ -15,7 +15,14 @@
15
15
  "@sqlite.org/sqlite-wasm": ">=3.51.0-build1",
16
16
  "lucide-react": "^0.468.0",
17
17
  "react": "^19.0.0",
18
- "react-dom": "^19.0.0"
18
+ "react-dom": "^19.0.0",
19
+ "yjs": "^13.6.30"
20
+ },
21
+ "pnpm": {
22
+ "onlyBuiltDependencies": [
23
+ "esbuild",
24
+ "protobufjs"
25
+ ]
19
26
  },
20
27
  "devDependencies": {
21
28
  "@korajs/cli": "{{koraVersion}}",
@@ -1,3 +1,10 @@
1
1
  // Web Worker entry point for SQLite WASM.
2
2
  // Loaded automatically by the Kora store to run SQLite in a background thread.
3
+
4
+ // Import the WASM binary URL so Vite resolves it with the correct content hash.
5
+ // Without this, production builds fail because sqlite3 looks for the unhashed filename.
6
+ import sqliteWasmUrl from '@sqlite.org/sqlite-wasm/sqlite3.wasm?url'
7
+
8
+ ;(globalThis as Record<string, unknown>).__KORA_SQLITE_WASM_URL = sqliteWasmUrl
9
+
3
10
  import '@korajs/store/sqlite-wasm/worker'
@@ -5,11 +5,12 @@ import { createRoot } from 'react-dom/client'
5
5
  import schema from './schema'
6
6
  import { App } from './App'
7
7
  import './index.css'
8
+ import koraWorkerUrl from './kora-worker.ts?worker&url'
8
9
 
9
10
  const app = createApp({
10
11
  schema,
11
12
  store: {
12
- workerUrl: new URL('./kora-worker.ts', import.meta.url),
13
+ workerUrl: koraWorkerUrl,
13
14
  },
14
15
  devtools: true,
15
16
  })
@@ -8,7 +8,8 @@
8
8
  "noUncheckedIndexedAccess": true,
9
9
  "esModuleInterop": true,
10
10
  "skipLibCheck": true,
11
- "outDir": "dist"
11
+ "outDir": "dist",
12
+ "types": ["vite/client"]
12
13
  },
13
14
  "include": ["src"]
14
15
  }
@@ -1,3 +1,5 @@
1
+ import { existsSync, readdirSync, copyFileSync } from 'node:fs'
2
+ import { resolve, join } from 'node:path'
1
3
  import tailwindcss from '@tailwindcss/vite'
2
4
  import react from '@vitejs/plugin-react'
3
5
  import type { Plugin } from 'vite'
@@ -28,8 +30,42 @@ function crossOriginIsolation(): Plugin {
28
30
  }
29
31
  }
30
32
 
33
+ /**
34
+ * Fixes sqlite3 WASM assets in production builds:
35
+ * 1. Copies the hashed sqlite3.wasm to an unhashed name so Emscripten's locateFile works
36
+ * 2. Copies sqlite3-opfs-async-proxy.js from node_modules (not bundled by Vite since
37
+ * sqlite3 loads it dynamically) so the full OPFS VFS can initialize without errors
38
+ */
39
+ function sqliteWasmHotfix(): Plugin {
40
+ return {
41
+ name: 'sqlite-wasm-hotfix',
42
+ apply: 'build',
43
+ closeBundle() {
44
+ const assetsDir = resolve('dist', 'assets')
45
+ if (!existsSync(assetsDir)) return
46
+
47
+ // Copy hashed sqlite3.wasm to unhashed name
48
+ for (const file of readdirSync(assetsDir)) {
49
+ if (/^sqlite3-.+\.wasm$/.test(file)) {
50
+ copyFileSync(join(assetsDir, file), join(assetsDir, 'sqlite3.wasm'))
51
+ break
52
+ }
53
+ }
54
+
55
+ // Copy OPFS async proxy worker (dynamically loaded by sqlite3, not detected by Vite)
56
+ const proxyFile = resolve('node_modules', '@sqlite.org', 'sqlite-wasm', 'sqlite-wasm', 'jswasm', 'sqlite3-opfs-async-proxy.js')
57
+ if (existsSync(proxyFile)) {
58
+ copyFileSync(proxyFile, join(assetsDir, 'sqlite3-opfs-async-proxy.js'))
59
+ }
60
+ },
61
+ }
62
+ }
63
+
31
64
  export default defineConfig({
32
- plugins: [react(), tailwindcss(), crossOriginIsolation()],
65
+ plugins: [react(), tailwindcss(), crossOriginIsolation(), sqliteWasmHotfix()],
66
+ worker: {
67
+ format: 'es',
68
+ },
33
69
  optimizeDeps: {
34
70
  exclude: ['@sqlite.org/sqlite-wasm', '@korajs/store'],
35
71
  include: ['yjs'],
@@ -0,0 +1,6 @@
1
+ # Kora Sync Server
2
+ # WebSocket URL for the sync server (used by the client)
3
+ VITE_SYNC_URL=ws://localhost:3001
4
+
5
+ # Sync server port
6
+ PORT=3001
@@ -0,0 +1,63 @@
1
+ # {{projectName}}
2
+
3
+ An offline-first application built with [Kora.js](https://ehoneahobed.github.io/kora/).
4
+
5
+ ## Getting Started
6
+
7
+ ```bash
8
+ npm run dev
9
+ ```
10
+
11
+ This starts both the Vite dev server and the Kora sync server.
12
+
13
+ - **App**: http://localhost:5173
14
+ - **Sync server**: ws://localhost:3001
15
+
16
+ ## Project Structure
17
+
18
+ ```
19
+ src/
20
+ schema.ts # Data schema definition
21
+ main.tsx # App initialization and Kora setup
22
+ App.tsx # Main application component
23
+ kora-worker.ts # SQLite WASM web worker
24
+ index.css # Styles
25
+ server.ts # Kora sync server
26
+ kora.config.ts # Kora configuration
27
+ ```
28
+
29
+ ## Environment Variables
30
+
31
+ | Variable | Default | Description |
32
+ |----------|---------|-------------|
33
+ | `VITE_SYNC_URL` | `ws://localhost:3001` | WebSocket URL for the sync server |
34
+ | `PORT` | `3001` | Sync server port |
35
+
36
+ ## Scripts
37
+
38
+ | Script | Description |
39
+ |--------|-------------|
40
+ | `npm run dev` | Start dev server + sync server |
41
+ | `npm run dev:server` | Start sync server only |
42
+ | `npm run build` | Build for production |
43
+ | `npm run preview` | Preview production build |
44
+
45
+ ## Deployment
46
+
47
+ The client and sync server must be deployed separately:
48
+
49
+ - **Client**: Any static host (Vercel, Netlify, Cloudflare Pages)
50
+ - **Sync server**: Any Node.js host with WebSocket support (Railway, Render, Fly.io)
51
+
52
+ Set `VITE_SYNC_URL` to your deployed sync server URL when building:
53
+
54
+ ```bash
55
+ VITE_SYNC_URL=wss://your-server.example.com npm run build
56
+ ```
57
+
58
+ ## Learn More
59
+
60
+ - [Documentation](https://ehoneahobed.github.io/kora/)
61
+ - [Storage Configuration](https://ehoneahobed.github.io/kora/guide/storage-configuration)
62
+ - [Sync Configuration](https://ehoneahobed.github.io/kora/guide/sync-configuration)
63
+ - [Deployment Guide](https://ehoneahobed.github.io/kora/guide/deployment)
@@ -7,6 +7,7 @@
7
7
  "dev": "kora dev",
8
8
  "dev:server": "tsx server.ts",
9
9
  "build": "tsc && vite build",
10
+ "start": "node --import tsx server.ts",
10
11
  "preview": "vite preview"
11
12
  },
12
13
  "dependencies": {
@@ -16,7 +17,16 @@
16
17
  "@sqlite.org/sqlite-wasm": ">=3.51.0-build1",
17
18
  "lucide-react": "^0.468.0",
18
19
  "react": "^19.0.0",
19
- "react-dom": "^19.0.0"
20
+ "react-dom": "^19.0.0",
21
+ "ws": "^8.18.0",
22
+ "yjs": "^13.6.30"
23
+ },
24
+ "pnpm": {
25
+ "onlyBuiltDependencies": [
26
+ "better-sqlite3",
27
+ "esbuild",
28
+ "protobufjs"
29
+ ]
20
30
  },
21
31
  "devDependencies": {
22
32
  "@korajs/cli": "{{koraVersion}}",
@@ -24,6 +34,7 @@
24
34
  "@tailwindcss/vite": "^4.0.0",
25
35
  "@types/react": "^19.0.0",
26
36
  "@types/react-dom": "^19.0.0",
37
+ "@types/ws": "^8.5.0",
27
38
  "@vitejs/plugin-react": "^4.3.0",
28
39
  "tailwindcss": "^4.0.0",
29
40
  "tsx": "^4.19.0",
@@ -1,4 +1,4 @@
1
- import { createKoraServer, createSqliteServerStore } from '@korajs/server'
1
+ import { createProductionServer, createSqliteServerStore } from '@korajs/server'
2
2
 
3
3
  // SQLite persists data to disk — survives server restarts
4
4
  const store = createSqliteServerStore({ filename: './kora-server.db' })
@@ -12,11 +12,15 @@ const store = createSqliteServerStore({ filename: './kora-server.db' })
12
12
  // connectionString: 'postgresql://user:password@localhost:5432/mydb',
13
13
  // })
14
14
 
15
- const server = createKoraServer({
15
+ // Production server: serves static files + WebSocket sync on a single port.
16
+ // One port means one tunnel (ngrok, cloudflared) handles everything.
17
+ const server = createProductionServer({
16
18
  store,
17
- port: 3001,
19
+ port: Number(process.env.PORT) || 3001,
20
+ staticDir: './dist',
21
+ syncPath: '/kora-sync',
18
22
  })
19
23
 
20
- server.start().then(() => {
21
- console.log('Kora sync server running on ws://localhost:3001')
24
+ server.start().then((url) => {
25
+ console.log(`Kora app running at ${url}`)
22
26
  })
@@ -1,3 +1,10 @@
1
1
  // Web Worker entry point for SQLite WASM.
2
2
  // Loaded automatically by the Kora store to run SQLite in a background thread.
3
+
4
+ // Import the WASM binary URL so Vite resolves it with the correct content hash.
5
+ // Without this, production builds fail because sqlite3 looks for the unhashed filename.
6
+ import sqliteWasmUrl from '@sqlite.org/sqlite-wasm/sqlite3.wasm?url'
7
+
8
+ ;(globalThis as Record<string, unknown>).__KORA_SQLITE_WASM_URL = sqliteWasmUrl
9
+
3
10
  import '@korajs/store/sqlite-wasm/worker'
@@ -5,14 +5,21 @@ import { createRoot } from 'react-dom/client'
5
5
  import schema from './schema'
6
6
  import { App } from './App'
7
7
  import './index.css'
8
+ import koraWorkerUrl from './kora-worker.ts?worker&url'
9
+
10
+ // Build sync URL: use env var if set, otherwise derive from current page host.
11
+ // This allows the Vite proxy (/kora-sync → ws://localhost:3001) to work in dev,
12
+ // and also works through any tunnel (ngrok, cloudflared) without extra configuration.
13
+ const syncUrl = import.meta.env.VITE_SYNC_URL
14
+ || `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/kora-sync`
8
15
 
9
16
  const app = createApp({
10
17
  schema,
11
18
  sync: {
12
- url: 'ws://localhost:3001',
19
+ url: syncUrl,
13
20
  },
14
21
  store: {
15
- workerUrl: new URL('./kora-worker.ts', import.meta.url),
22
+ workerUrl: koraWorkerUrl,
16
23
  },
17
24
  devtools: true,
18
25
  })
@@ -8,7 +8,8 @@
8
8
  "noUncheckedIndexedAccess": true,
9
9
  "esModuleInterop": true,
10
10
  "skipLibCheck": true,
11
- "outDir": "dist"
11
+ "outDir": "dist",
12
+ "types": ["vite/client"]
12
13
  },
13
14
  "include": ["src"]
14
15
  }
@@ -1,3 +1,5 @@
1
+ import { existsSync, readdirSync, copyFileSync } from 'node:fs'
2
+ import { resolve, join } from 'node:path'
1
3
  import tailwindcss from '@tailwindcss/vite'
2
4
  import react from '@vitejs/plugin-react'
3
5
  import type { Plugin } from 'vite'
@@ -28,8 +30,42 @@ function crossOriginIsolation(): Plugin {
28
30
  }
29
31
  }
30
32
 
33
+ /**
34
+ * Fixes sqlite3 WASM assets in production builds:
35
+ * 1. Copies the hashed sqlite3.wasm to an unhashed name so Emscripten's locateFile works
36
+ * 2. Copies sqlite3-opfs-async-proxy.js from node_modules (not bundled by Vite since
37
+ * sqlite3 loads it dynamically) so the full OPFS VFS can initialize without errors
38
+ */
39
+ function sqliteWasmHotfix(): Plugin {
40
+ return {
41
+ name: 'sqlite-wasm-hotfix',
42
+ apply: 'build',
43
+ closeBundle() {
44
+ const assetsDir = resolve('dist', 'assets')
45
+ if (!existsSync(assetsDir)) return
46
+
47
+ // Copy hashed sqlite3.wasm to unhashed name
48
+ for (const file of readdirSync(assetsDir)) {
49
+ if (/^sqlite3-.+\.wasm$/.test(file)) {
50
+ copyFileSync(join(assetsDir, file), join(assetsDir, 'sqlite3.wasm'))
51
+ break
52
+ }
53
+ }
54
+
55
+ // Copy OPFS async proxy worker (dynamically loaded by sqlite3, not detected by Vite)
56
+ const proxyFile = resolve('node_modules', '@sqlite.org', 'sqlite-wasm', 'sqlite-wasm', 'jswasm', 'sqlite3-opfs-async-proxy.js')
57
+ if (existsSync(proxyFile)) {
58
+ copyFileSync(proxyFile, join(assetsDir, 'sqlite3-opfs-async-proxy.js'))
59
+ }
60
+ },
61
+ }
62
+ }
63
+
31
64
  export default defineConfig({
32
- plugins: [react(), tailwindcss(), crossOriginIsolation()],
65
+ plugins: [react(), tailwindcss(), crossOriginIsolation(), sqliteWasmHotfix()],
66
+ worker: {
67
+ format: 'es',
68
+ },
33
69
  optimizeDeps: {
34
70
  exclude: ['@sqlite.org/sqlite-wasm', '@korajs/store'],
35
71
  include: ['yjs'],
@@ -37,4 +73,14 @@ export default defineConfig({
37
73
  resolve: {
38
74
  dedupe: ['yjs'],
39
75
  },
76
+ server: {
77
+ allowedHosts: true,
78
+ proxy: {
79
+ '/kora-sync': {
80
+ target: 'ws://localhost:3001',
81
+ ws: true,
82
+ rewriteWsOrigin: true,
83
+ },
84
+ },
85
+ },
40
86
  })