@korajs/cli 0.1.13 → 0.1.14
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.
- package/dist/chunk-N36PFOSA.js +0 -0
- package/dist/chunk-SYOFLJLB.js +0 -0
- package/dist/chunk-VGOOQ56H.js +0 -0
- package/dist/index.js +0 -0
- package/package.json +13 -13
- package/templates/react-basic/.env.example +3 -0
- package/templates/react-basic/README.md.hbs +53 -0
- package/templates/react-basic/package.json.hbs +8 -1
- package/templates/react-basic/src/kora-worker.ts +7 -0
- package/templates/react-basic/src/main.tsx +2 -1
- package/templates/react-basic/tsconfig.json +2 -1
- package/templates/react-basic/vite.config.ts +37 -1
- package/templates/react-sync/.env.example +6 -0
- package/templates/react-sync/README.md.hbs +63 -0
- package/templates/react-sync/package.json.hbs +12 -1
- package/templates/react-sync/server.ts +9 -5
- package/templates/react-sync/src/kora-worker.ts +7 -0
- package/templates/react-sync/src/main.tsx +9 -2
- package/templates/react-sync/tsconfig.json +2 -1
- package/templates/react-sync/vite.config.ts +47 -1
- package/templates/react-tailwind/.env.example +3 -0
- package/templates/react-tailwind/README.md.hbs +53 -0
- package/templates/react-tailwind/package.json.hbs +8 -1
- package/templates/react-tailwind/src/kora-worker.ts +7 -0
- package/templates/react-tailwind/src/main.tsx +2 -1
- package/templates/react-tailwind/tsconfig.json +2 -1
- package/templates/react-tailwind/vite.config.ts +37 -1
- package/templates/react-tailwind-sync/.env.example +6 -0
- package/templates/react-tailwind-sync/README.md.hbs +63 -0
- package/templates/react-tailwind-sync/package.json.hbs +12 -1
- package/templates/react-tailwind-sync/server.ts +9 -5
- package/templates/react-tailwind-sync/src/kora-worker.ts +7 -0
- package/templates/react-tailwind-sync/src/main.tsx +9 -2
- package/templates/react-tailwind-sync/tsconfig.json +2 -1
- package/templates/react-tailwind-sync/vite.config.ts +47 -1
package/dist/chunk-N36PFOSA.js
CHANGED
|
File without changes
|
package/dist/chunk-SYOFLJLB.js
CHANGED
|
File without changes
|
package/dist/chunk-VGOOQ56H.js
CHANGED
|
File without changes
|
package/dist/index.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@korajs/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "Kora.js CLI tooling and project scaffolding",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -30,16 +30,6 @@
|
|
|
30
30
|
"dist",
|
|
31
31
|
"templates"
|
|
32
32
|
],
|
|
33
|
-
"dependencies": {
|
|
34
|
-
"citty": "^0.1.6",
|
|
35
|
-
"@korajs/core": "0.1.3"
|
|
36
|
-
},
|
|
37
|
-
"devDependencies": {
|
|
38
|
-
"typescript": "^5.7.3",
|
|
39
|
-
"tsup": "^8.3.6",
|
|
40
|
-
"vitest": "^3.0.4"
|
|
41
|
-
},
|
|
42
|
-
"license": "MIT",
|
|
43
33
|
"scripts": {
|
|
44
34
|
"build": "tsup",
|
|
45
35
|
"dev": "tsup --watch",
|
|
@@ -48,5 +38,15 @@
|
|
|
48
38
|
"typecheck": "tsc --noEmit",
|
|
49
39
|
"lint": "biome check src/ tests/",
|
|
50
40
|
"clean": "rm -rf dist .turbo coverage"
|
|
51
|
-
}
|
|
52
|
-
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@korajs/core": "workspace:*",
|
|
44
|
+
"citty": "^0.1.6"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"typescript": "^5.7.3",
|
|
48
|
+
"tsup": "^8.3.6",
|
|
49
|
+
"vitest": "^3.0.4"
|
|
50
|
+
},
|
|
51
|
+
"license": "MIT"
|
|
52
|
+
}
|
|
@@ -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:
|
|
13
|
+
workerUrl: koraWorkerUrl,
|
|
13
14
|
},
|
|
14
15
|
devtools: true,
|
|
15
16
|
})
|
|
@@ -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,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 {
|
|
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
|
-
|
|
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(
|
|
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:
|
|
19
|
+
url: syncUrl,
|
|
13
20
|
},
|
|
14
21
|
store: {
|
|
15
|
-
workerUrl:
|
|
22
|
+
workerUrl: koraWorkerUrl,
|
|
16
23
|
},
|
|
17
24
|
devtools: true,
|
|
18
25
|
})
|
|
@@ -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,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:
|
|
13
|
+
workerUrl: koraWorkerUrl,
|
|
13
14
|
},
|
|
14
15
|
devtools: true,
|
|
15
16
|
})
|
|
@@ -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,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 {
|
|
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
|
-
|
|
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(
|
|
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:
|
|
19
|
+
url: syncUrl,
|
|
13
20
|
},
|
|
14
21
|
store: {
|
|
15
|
-
workerUrl:
|
|
22
|
+
workerUrl: koraWorkerUrl,
|
|
16
23
|
},
|
|
17
24
|
devtools: true,
|
|
18
25
|
})
|
|
@@ -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
|
})
|