@korajs/cli 1.0.0-beta.5 → 1.0.0-beta.7

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 (36) hide show
  1. package/dist/bin.cjs +49 -10
  2. package/dist/bin.cjs.map +1 -1
  3. package/dist/bin.js +41 -2
  4. package/dist/bin.js.map +1 -1
  5. package/package.json +8 -8
  6. package/templates/react-basic/README.md.hbs +1 -1
  7. package/templates/react-basic/vite.config.ts +7 -4
  8. package/templates/react-sync/README.md.hbs +6 -5
  9. package/templates/react-sync/server.ts +42 -2
  10. package/templates/react-sync/vite.config.ts +14 -4
  11. package/templates/react-tailwind/README.md.hbs +1 -1
  12. package/templates/react-tailwind/vite.config.ts +7 -4
  13. package/templates/react-tailwind-sync/README.md.hbs +6 -5
  14. package/templates/react-tailwind-sync/server.ts +42 -2
  15. package/templates/react-tailwind-sync/vite.config.ts +14 -4
  16. package/templates/svelte-basic/README.md.hbs +1 -1
  17. package/templates/svelte-basic/vite.config.ts +4 -2
  18. package/templates/svelte-sync/README.md.hbs +6 -5
  19. package/templates/svelte-sync/server.ts +42 -2
  20. package/templates/svelte-sync/vite.config.ts +11 -2
  21. package/templates/svelte-tailwind/README.md.hbs +1 -1
  22. package/templates/svelte-tailwind/vite.config.ts +4 -2
  23. package/templates/svelte-tailwind-sync/README.md.hbs +6 -5
  24. package/templates/svelte-tailwind-sync/server.ts +42 -2
  25. package/templates/svelte-tailwind-sync/vite.config.ts +11 -2
  26. package/templates/tauri-react/server.ts +40 -0
  27. package/templates/vue-basic/README.md.hbs +1 -1
  28. package/templates/vue-basic/vite.config.ts +4 -2
  29. package/templates/vue-sync/README.md.hbs +6 -5
  30. package/templates/vue-sync/server.ts +42 -2
  31. package/templates/vue-sync/vite.config.ts +11 -2
  32. package/templates/vue-tailwind/README.md.hbs +1 -1
  33. package/templates/vue-tailwind/vite.config.ts +4 -2
  34. package/templates/vue-tailwind-sync/README.md.hbs +6 -5
  35. package/templates/vue-tailwind-sync/server.ts +42 -2
  36. package/templates/vue-tailwind-sync/vite.config.ts +11 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@korajs/cli",
3
- "version": "1.0.0-beta.5",
3
+ "version": "1.0.0-beta.7",
4
4
  "description": "Kora.js CLI tooling and project scaffolding",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -37,7 +37,7 @@
37
37
  "conf": "^15.1.0",
38
38
  "esbuild": "^0.28.0",
39
39
  "validate-npm-package-name": "^7.0.2",
40
- "@korajs/core": "1.0.0-beta.5"
40
+ "@korajs/core": "1.0.0-beta.7"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/better-sqlite3": "^7.6.13",
@@ -47,12 +47,12 @@
47
47
  "tsup": "^8.3.6",
48
48
  "typescript": "^5.7.3",
49
49
  "vitest": "^3.0.4",
50
- "@korajs/server": "1.0.0-beta.5",
51
- "@korajs/sync": "1.0.0-beta.5",
52
- "@korajs/store": "1.0.0-beta.5",
53
- "@korajs/merge": "1.0.0-beta.5",
54
- "@korajs/test": "1.0.0-beta.5",
55
- "korajs": "1.0.0-beta.5"
50
+ "@korajs/server": "1.0.0-beta.7",
51
+ "@korajs/merge": "1.0.0-beta.7",
52
+ "@korajs/store": "1.0.0-beta.8",
53
+ "@korajs/sync": "1.0.0-beta.7",
54
+ "korajs": "1.0.0-beta.8",
55
+ "@korajs/test": "1.0.0-beta.8"
56
56
  },
57
57
  "license": "MIT",
58
58
  "scripts": {
@@ -47,7 +47,7 @@ To enable real-time sync across devices, update `src/main.tsx`:
47
47
  const app = createApp({
48
48
  schema,
49
49
  sync: {
50
- url: import.meta.env.VITE_SYNC_URL || 'ws://localhost:3001',
50
+ url: import.meta.env.VITE_SYNC_URL || 'ws://localhost:3001/kora-sync',
51
51
  },
52
52
  // ...
53
53
  })
@@ -6,23 +6,26 @@ import { defineConfig } from 'vite'
6
6
 
7
7
  /**
8
8
  * Adds Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers
9
- * required for SharedArrayBuffer. Without these headers, OPFS SAH Pool VFS
10
- * cannot be used and SQLite WASM falls back to in-memory storage (no persistence).
9
+ * enabling SharedArrayBuffer where browsers support it while keeping common
10
+ * third-party embeds such as YouTube and Vimeo usable. Kora falls back to
11
+ * durable IndexedDB storage when SharedArrayBuffer is unavailable.
11
12
  */
12
13
  function crossOriginIsolation(): Plugin {
14
+ const embedderPolicy = process.env.KORA_COEP_POLICY || 'credentialless'
15
+
13
16
  return {
14
17
  name: 'cross-origin-isolation',
15
18
  configureServer(server) {
16
19
  server.middlewares.use((_req, res, next) => {
17
20
  res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
18
- res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
21
+ res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
19
22
  next()
20
23
  })
21
24
  },
22
25
  configurePreviewServer(server) {
23
26
  server.middlewares.use((_req, res, next) => {
24
27
  res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
25
- res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
28
+ res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
26
29
  next()
27
30
  })
28
31
  },
@@ -11,7 +11,7 @@ npm run dev
11
11
  This starts both the Vite dev server and the Kora sync server.
12
12
 
13
13
  - **App**: http://localhost:5173
14
- - **Sync server**: ws://localhost:3001
14
+ - **Sync server**: ws://localhost:3001/kora-sync
15
15
 
16
16
  ## Project Structure
17
17
 
@@ -40,11 +40,12 @@ writes, and `useTodos.ts` is the React binding that exposes todo data and action
40
40
 
41
41
  | Variable | Default | Description |
42
42
  |----------|---------|-------------|
43
- | `VITE_SYNC_URL` | `ws://localhost:3001` | WebSocket URL for the sync server |
43
+ | `VITE_SYNC_URL` | `ws://localhost:3001/kora-sync` | WebSocket URL for the sync endpoint |
44
44
  | `VITE_AUTH_URL` | current origin | HTTP URL for `/auth/*` routes |
45
45
  | `PORT` | `3001` | Sync server port |
46
- | `KORA_SERVER_DB` | `./kora-server.db` | SQLite file used by the sync server when `DATABASE_URL` is empty |
47
- | `KORA_AUTH_DB` | `./kora-auth.db` | SQLite file used by OAuth state and linked identities when `DATABASE_URL` is empty |
46
+ | `KORA_SERVER_DB` | `./.kora/kora-server.db` | SQLite file used by the sync server when `DATABASE_URL` is empty |
47
+ | `KORA_AUTH_DB` | `./.kora/kora-auth.db` | SQLite file used by OAuth state and linked identities when `DATABASE_URL` is empty |
48
+ | `KORA_COEP_POLICY` | `credentialless` | Dev/preview Cross-Origin-Embedder-Policy. Use `require-corp` only when every embedded resource opts in. |
48
49
  | `DATABASE_URL` | (none) | PostgreSQL connection string for production sync server deployments |
49
50
  | `KORA_SYNC_PATH` | `/kora-sync` | WebSocket path exposed by the sync server |
50
51
  | `KORA_AUTH_SECRET` | (none) | Enables built-in `/auth/*` routes and authenticated sync when set |
@@ -94,7 +95,7 @@ The client and sync server must be deployed separately:
94
95
  Set `VITE_SYNC_URL` to your deployed sync server URL when building:
95
96
 
96
97
  ```bash
97
- VITE_SYNC_URL=wss://your-server.example.com npm run build
98
+ VITE_SYNC_URL=wss://your-server.example.com/kora-sync npm run build
98
99
  ```
99
100
 
100
101
  ## Learn More
@@ -1,3 +1,4 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
1
2
  import {
2
3
  type CreateKoraAuthServerOptions,
3
4
  createKoraAuthServer,
@@ -12,6 +13,45 @@ import {
12
13
  } from '@korajs/server'
13
14
  import schema from './src/schema'
14
15
 
16
+ loadLocalEnv()
17
+
18
+ function loadLocalEnv(mode = process.env.NODE_ENV || 'development') {
19
+ const existingKeys = new Set(Object.keys(process.env))
20
+ for (const file of ['.env', '.env.local', `.env.${mode}`, `.env.${mode}.local`]) {
21
+ if (!existsSync(file)) continue
22
+ for (const [key, value] of Object.entries(parseEnvFile(readFileSync(file, 'utf8')))) {
23
+ if (!existingKeys.has(key)) {
24
+ process.env[key] = value
25
+ }
26
+ }
27
+ }
28
+ }
29
+
30
+ function parseEnvFile(contents: string): Record<string, string> {
31
+ const env: Record<string, string> = {}
32
+ for (const rawLine of contents.split(/\r?\n/)) {
33
+ const line = rawLine.trim()
34
+ if (!line || line.startsWith('#')) continue
35
+ const normalized = line.startsWith('export ') ? line.slice('export '.length).trim() : line
36
+ const equalsIndex = normalized.indexOf('=')
37
+ if (equalsIndex <= 0) continue
38
+ const key = normalized.slice(0, equalsIndex).trim()
39
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue
40
+ env[key] = parseEnvValue(normalized.slice(equalsIndex + 1).trim())
41
+ }
42
+ return env
43
+ }
44
+
45
+ function parseEnvValue(value: string): string {
46
+ const quote = value[0]
47
+ if ((quote === '"' || quote === "'") && value[value.length - 1] === quote) {
48
+ const inner = value.slice(1, -1)
49
+ return quote === '"' ? inner.replace(/\\n/g, '\n').replace(/\\r/g, '\r') : inner
50
+ }
51
+ const commentIndex = value.search(/\s#/)
52
+ return (commentIndex >= 0 ? value.slice(0, commentIndex) : value).trim()
53
+ }
54
+
15
55
  async function createStore() {
16
56
  if (process.env.DATABASE_URL) {
17
57
  return createPostgresServerStore({
@@ -20,7 +60,7 @@ async function createStore() {
20
60
  }
21
61
 
22
62
  return createSqliteServerStore({
23
- filename: process.env.KORA_SERVER_DB || './kora-server.db',
63
+ filename: process.env.KORA_SERVER_DB || './.kora/kora-server.db',
24
64
  })
25
65
  }
26
66
 
@@ -72,7 +112,7 @@ async function createOAuthConfig(): Promise<CreateKoraAuthServerOptions['oauth']
72
112
  const oauthStores = process.env.DATABASE_URL
73
113
  ? await createPostgresOAuthStores({ connectionString: process.env.DATABASE_URL })
74
114
  : await createSqliteOAuthStores({
75
- filename: process.env.KORA_AUTH_DB || './kora-auth.db',
115
+ filename: process.env.KORA_AUTH_DB || './.kora/kora-auth.db',
76
116
  })
77
117
 
78
118
  return {
@@ -6,23 +6,26 @@ import { defineConfig } from 'vite'
6
6
 
7
7
  /**
8
8
  * Adds Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers
9
- * required for SharedArrayBuffer. Without these headers, OPFS SAH Pool VFS
10
- * cannot be used and SQLite WASM falls back to in-memory storage (no persistence).
9
+ * enabling SharedArrayBuffer where browsers support it while keeping common
10
+ * third-party embeds such as YouTube and Vimeo usable. Kora falls back to
11
+ * durable IndexedDB storage when SharedArrayBuffer is unavailable.
11
12
  */
12
13
  function crossOriginIsolation(): Plugin {
14
+ const embedderPolicy = process.env.KORA_COEP_POLICY || 'credentialless'
15
+
13
16
  return {
14
17
  name: 'cross-origin-isolation',
15
18
  configureServer(server) {
16
19
  server.middlewares.use((_req, res, next) => {
17
20
  res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
18
- res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
21
+ res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
19
22
  next()
20
23
  })
21
24
  },
22
25
  configurePreviewServer(server) {
23
26
  server.middlewares.use((_req, res, next) => {
24
27
  res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
25
- res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
28
+ res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
26
29
  next()
27
30
  })
28
31
  },
@@ -81,12 +84,19 @@ export default defineConfig({
81
84
  },
82
85
  server: {
83
86
  allowedHosts: true,
87
+ watch: {
88
+ ignored: ['**/.kora/**', '**/*.db', '**/*.db-shm', '**/*.db-wal'],
89
+ },
84
90
  proxy: {
85
91
  '/kora-sync': {
86
92
  target: 'ws://localhost:3001',
87
93
  ws: true,
88
94
  rewriteWsOrigin: true,
89
95
  },
96
+ '/auth': {
97
+ target: 'http://localhost:3001',
98
+ changeOrigin: true,
99
+ },
90
100
  },
91
101
  },
92
102
  })
@@ -47,7 +47,7 @@ To enable real-time sync across devices, update `src/main.tsx`:
47
47
  const app = createApp({
48
48
  schema,
49
49
  sync: {
50
- url: import.meta.env.VITE_SYNC_URL || 'ws://localhost:3001',
50
+ url: import.meta.env.VITE_SYNC_URL || 'ws://localhost:3001/kora-sync',
51
51
  },
52
52
  // ...
53
53
  })
@@ -7,23 +7,26 @@ import { defineConfig } from 'vite'
7
7
 
8
8
  /**
9
9
  * Adds Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers
10
- * required for SharedArrayBuffer. Without these headers, OPFS SAH Pool VFS
11
- * cannot be used and SQLite WASM falls back to in-memory storage (no persistence).
10
+ * enabling SharedArrayBuffer where browsers support it while keeping common
11
+ * third-party embeds such as YouTube and Vimeo usable. Kora falls back to
12
+ * durable IndexedDB storage when SharedArrayBuffer is unavailable.
12
13
  */
13
14
  function crossOriginIsolation(): Plugin {
15
+ const embedderPolicy = process.env.KORA_COEP_POLICY || 'credentialless'
16
+
14
17
  return {
15
18
  name: 'cross-origin-isolation',
16
19
  configureServer(server) {
17
20
  server.middlewares.use((_req, res, next) => {
18
21
  res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
19
- res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
22
+ res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
20
23
  next()
21
24
  })
22
25
  },
23
26
  configurePreviewServer(server) {
24
27
  server.middlewares.use((_req, res, next) => {
25
28
  res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
26
- res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
29
+ res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
27
30
  next()
28
31
  })
29
32
  },
@@ -11,7 +11,7 @@ npm run dev
11
11
  This starts both the Vite dev server and the Kora sync server.
12
12
 
13
13
  - **App**: http://localhost:5173
14
- - **Sync server**: ws://localhost:3001
14
+ - **Sync server**: ws://localhost:3001/kora-sync
15
15
 
16
16
  ## Project Structure
17
17
 
@@ -40,11 +40,12 @@ writes, and `useTodos.ts` is the React binding that exposes todo data and action
40
40
 
41
41
  | Variable | Default | Description |
42
42
  |----------|---------|-------------|
43
- | `VITE_SYNC_URL` | `ws://localhost:3001` | WebSocket URL for the sync server |
43
+ | `VITE_SYNC_URL` | `ws://localhost:3001/kora-sync` | WebSocket URL for the sync endpoint |
44
44
  | `VITE_AUTH_URL` | current origin | HTTP URL for `/auth/*` routes |
45
45
  | `PORT` | `3001` | Sync server port |
46
- | `KORA_SERVER_DB` | `./kora-server.db` | SQLite file used by the sync server when `DATABASE_URL` is empty |
47
- | `KORA_AUTH_DB` | `./kora-auth.db` | SQLite file used by OAuth state and linked identities when `DATABASE_URL` is empty |
46
+ | `KORA_SERVER_DB` | `./.kora/kora-server.db` | SQLite file used by the sync server when `DATABASE_URL` is empty |
47
+ | `KORA_AUTH_DB` | `./.kora/kora-auth.db` | SQLite file used by OAuth state and linked identities when `DATABASE_URL` is empty |
48
+ | `KORA_COEP_POLICY` | `credentialless` | Dev/preview Cross-Origin-Embedder-Policy. Use `require-corp` only when every embedded resource opts in. |
48
49
  | `DATABASE_URL` | (none) | PostgreSQL connection string for production sync server deployments |
49
50
  | `KORA_SYNC_PATH` | `/kora-sync` | WebSocket path exposed by the sync server |
50
51
  | `KORA_AUTH_SECRET` | (none) | Enables built-in `/auth/*` routes and authenticated sync when set |
@@ -94,7 +95,7 @@ The client and sync server must be deployed separately:
94
95
  Set `VITE_SYNC_URL` to your deployed sync server URL when building:
95
96
 
96
97
  ```bash
97
- VITE_SYNC_URL=wss://your-server.example.com npm run build
98
+ VITE_SYNC_URL=wss://your-server.example.com/kora-sync npm run build
98
99
  ```
99
100
 
100
101
  ## Learn More
@@ -1,3 +1,4 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
1
2
  import {
2
3
  type CreateKoraAuthServerOptions,
3
4
  createKoraAuthServer,
@@ -12,6 +13,45 @@ import {
12
13
  } from '@korajs/server'
13
14
  import schema from './src/schema'
14
15
 
16
+ loadLocalEnv()
17
+
18
+ function loadLocalEnv(mode = process.env.NODE_ENV || 'development') {
19
+ const existingKeys = new Set(Object.keys(process.env))
20
+ for (const file of ['.env', '.env.local', `.env.${mode}`, `.env.${mode}.local`]) {
21
+ if (!existsSync(file)) continue
22
+ for (const [key, value] of Object.entries(parseEnvFile(readFileSync(file, 'utf8')))) {
23
+ if (!existingKeys.has(key)) {
24
+ process.env[key] = value
25
+ }
26
+ }
27
+ }
28
+ }
29
+
30
+ function parseEnvFile(contents: string): Record<string, string> {
31
+ const env: Record<string, string> = {}
32
+ for (const rawLine of contents.split(/\r?\n/)) {
33
+ const line = rawLine.trim()
34
+ if (!line || line.startsWith('#')) continue
35
+ const normalized = line.startsWith('export ') ? line.slice('export '.length).trim() : line
36
+ const equalsIndex = normalized.indexOf('=')
37
+ if (equalsIndex <= 0) continue
38
+ const key = normalized.slice(0, equalsIndex).trim()
39
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue
40
+ env[key] = parseEnvValue(normalized.slice(equalsIndex + 1).trim())
41
+ }
42
+ return env
43
+ }
44
+
45
+ function parseEnvValue(value: string): string {
46
+ const quote = value[0]
47
+ if ((quote === '"' || quote === "'") && value[value.length - 1] === quote) {
48
+ const inner = value.slice(1, -1)
49
+ return quote === '"' ? inner.replace(/\\n/g, '\n').replace(/\\r/g, '\r') : inner
50
+ }
51
+ const commentIndex = value.search(/\s#/)
52
+ return (commentIndex >= 0 ? value.slice(0, commentIndex) : value).trim()
53
+ }
54
+
15
55
  async function createStore() {
16
56
  if (process.env.DATABASE_URL) {
17
57
  return createPostgresServerStore({
@@ -20,7 +60,7 @@ async function createStore() {
20
60
  }
21
61
 
22
62
  return createSqliteServerStore({
23
- filename: process.env.KORA_SERVER_DB || './kora-server.db',
63
+ filename: process.env.KORA_SERVER_DB || './.kora/kora-server.db',
24
64
  })
25
65
  }
26
66
 
@@ -72,7 +112,7 @@ async function createOAuthConfig(): Promise<CreateKoraAuthServerOptions['oauth']
72
112
  const oauthStores = process.env.DATABASE_URL
73
113
  ? await createPostgresOAuthStores({ connectionString: process.env.DATABASE_URL })
74
114
  : await createSqliteOAuthStores({
75
- filename: process.env.KORA_AUTH_DB || './kora-auth.db',
115
+ filename: process.env.KORA_AUTH_DB || './.kora/kora-auth.db',
76
116
  })
77
117
 
78
118
  return {
@@ -7,23 +7,26 @@ import { defineConfig } from 'vite'
7
7
 
8
8
  /**
9
9
  * Adds Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers
10
- * required for SharedArrayBuffer. Without these headers, OPFS SAH Pool VFS
11
- * cannot be used and SQLite WASM falls back to in-memory storage (no persistence).
10
+ * enabling SharedArrayBuffer where browsers support it while keeping common
11
+ * third-party embeds such as YouTube and Vimeo usable. Kora falls back to
12
+ * durable IndexedDB storage when SharedArrayBuffer is unavailable.
12
13
  */
13
14
  function crossOriginIsolation(): Plugin {
15
+ const embedderPolicy = process.env.KORA_COEP_POLICY || 'credentialless'
16
+
14
17
  return {
15
18
  name: 'cross-origin-isolation',
16
19
  configureServer(server) {
17
20
  server.middlewares.use((_req, res, next) => {
18
21
  res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
19
- res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
22
+ res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
20
23
  next()
21
24
  })
22
25
  },
23
26
  configurePreviewServer(server) {
24
27
  server.middlewares.use((_req, res, next) => {
25
28
  res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
26
- res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
29
+ res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
27
30
  next()
28
31
  })
29
32
  },
@@ -82,12 +85,19 @@ export default defineConfig({
82
85
  },
83
86
  server: {
84
87
  allowedHosts: true,
88
+ watch: {
89
+ ignored: ['**/.kora/**', '**/*.db', '**/*.db-shm', '**/*.db-wal'],
90
+ },
85
91
  proxy: {
86
92
  '/kora-sync': {
87
93
  target: 'ws://localhost:3001',
88
94
  ws: true,
89
95
  rewriteWsOrigin: true,
90
96
  },
97
+ '/auth': {
98
+ target: 'http://localhost:3001',
99
+ changeOrigin: true,
100
+ },
91
101
  },
92
102
  },
93
103
  })
@@ -47,7 +47,7 @@ To enable real-time sync across devices, update `src/main.tsx`:
47
47
  const app = createApp({
48
48
  schema,
49
49
  sync: {
50
- url: import.meta.env.VITE_SYNC_URL || 'ws://localhost:3001',
50
+ url: import.meta.env.VITE_SYNC_URL || 'ws://localhost:3001/kora-sync',
51
51
  },
52
52
  // ...
53
53
  })
@@ -5,19 +5,21 @@ import type { Plugin } from 'vite'
5
5
  import { defineConfig } from 'vite'
6
6
 
7
7
  function crossOriginIsolation(): Plugin {
8
+ const embedderPolicy = process.env.KORA_COEP_POLICY || 'credentialless'
9
+
8
10
  return {
9
11
  name: 'cross-origin-isolation',
10
12
  configureServer(server) {
11
13
  server.middlewares.use((_req, res, next) => {
12
14
  res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
13
- res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
15
+ res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
14
16
  next()
15
17
  })
16
18
  },
17
19
  configurePreviewServer(server) {
18
20
  server.middlewares.use((_req, res, next) => {
19
21
  res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
20
- res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
22
+ res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
21
23
  next()
22
24
  })
23
25
  },
@@ -11,7 +11,7 @@ npm run dev
11
11
  This starts both the Vite dev server and the Kora sync server.
12
12
 
13
13
  - **App**: http://localhost:5173
14
- - **Sync server**: ws://localhost:3001
14
+ - **Sync server**: ws://localhost:3001/kora-sync
15
15
 
16
16
  ## Project Structure
17
17
 
@@ -40,11 +40,12 @@ writes, and `useTodos.ts` is the React binding that exposes todo data and action
40
40
 
41
41
  | Variable | Default | Description |
42
42
  |----------|---------|-------------|
43
- | `VITE_SYNC_URL` | `ws://localhost:3001` | WebSocket URL for the sync server |
43
+ | `VITE_SYNC_URL` | `ws://localhost:3001/kora-sync` | WebSocket URL for the sync endpoint |
44
44
  | `VITE_AUTH_URL` | current origin | HTTP URL for `/auth/*` routes |
45
45
  | `PORT` | `3001` | Sync server port |
46
- | `KORA_SERVER_DB` | `./kora-server.db` | SQLite file used by the sync server when `DATABASE_URL` is empty |
47
- | `KORA_AUTH_DB` | `./kora-auth.db` | SQLite file used by OAuth state and linked identities when `DATABASE_URL` is empty |
46
+ | `KORA_SERVER_DB` | `./.kora/kora-server.db` | SQLite file used by the sync server when `DATABASE_URL` is empty |
47
+ | `KORA_AUTH_DB` | `./.kora/kora-auth.db` | SQLite file used by OAuth state and linked identities when `DATABASE_URL` is empty |
48
+ | `KORA_COEP_POLICY` | `credentialless` | Dev/preview Cross-Origin-Embedder-Policy. Use `require-corp` only when every embedded resource opts in. |
48
49
  | `DATABASE_URL` | (none) | PostgreSQL connection string for production sync server deployments |
49
50
  | `KORA_SYNC_PATH` | `/kora-sync` | WebSocket path exposed by the sync server |
50
51
  | `KORA_AUTH_SECRET` | (none) | Enables built-in `/auth/*` routes and authenticated sync when set |
@@ -94,7 +95,7 @@ The client and sync server must be deployed separately:
94
95
  Set `VITE_SYNC_URL` to your deployed sync server URL when building:
95
96
 
96
97
  ```bash
97
- VITE_SYNC_URL=wss://your-server.example.com npm run build
98
+ VITE_SYNC_URL=wss://your-server.example.com/kora-sync npm run build
98
99
  ```
99
100
 
100
101
  ## Learn More
@@ -1,3 +1,4 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
1
2
  import {
2
3
  type CreateKoraAuthServerOptions,
3
4
  createKoraAuthServer,
@@ -12,6 +13,45 @@ import {
12
13
  } from '@korajs/server'
13
14
  import schema from './src/schema'
14
15
 
16
+ loadLocalEnv()
17
+
18
+ function loadLocalEnv(mode = process.env.NODE_ENV || 'development') {
19
+ const existingKeys = new Set(Object.keys(process.env))
20
+ for (const file of ['.env', '.env.local', `.env.${mode}`, `.env.${mode}.local`]) {
21
+ if (!existsSync(file)) continue
22
+ for (const [key, value] of Object.entries(parseEnvFile(readFileSync(file, 'utf8')))) {
23
+ if (!existingKeys.has(key)) {
24
+ process.env[key] = value
25
+ }
26
+ }
27
+ }
28
+ }
29
+
30
+ function parseEnvFile(contents: string): Record<string, string> {
31
+ const env: Record<string, string> = {}
32
+ for (const rawLine of contents.split(/\r?\n/)) {
33
+ const line = rawLine.trim()
34
+ if (!line || line.startsWith('#')) continue
35
+ const normalized = line.startsWith('export ') ? line.slice('export '.length).trim() : line
36
+ const equalsIndex = normalized.indexOf('=')
37
+ if (equalsIndex <= 0) continue
38
+ const key = normalized.slice(0, equalsIndex).trim()
39
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue
40
+ env[key] = parseEnvValue(normalized.slice(equalsIndex + 1).trim())
41
+ }
42
+ return env
43
+ }
44
+
45
+ function parseEnvValue(value: string): string {
46
+ const quote = value[0]
47
+ if ((quote === '"' || quote === "'") && value[value.length - 1] === quote) {
48
+ const inner = value.slice(1, -1)
49
+ return quote === '"' ? inner.replace(/\\n/g, '\n').replace(/\\r/g, '\r') : inner
50
+ }
51
+ const commentIndex = value.search(/\s#/)
52
+ return (commentIndex >= 0 ? value.slice(0, commentIndex) : value).trim()
53
+ }
54
+
15
55
  async function createStore() {
16
56
  if (process.env.DATABASE_URL) {
17
57
  return createPostgresServerStore({
@@ -20,7 +60,7 @@ async function createStore() {
20
60
  }
21
61
 
22
62
  return createSqliteServerStore({
23
- filename: process.env.KORA_SERVER_DB || './kora-server.db',
63
+ filename: process.env.KORA_SERVER_DB || './.kora/kora-server.db',
24
64
  })
25
65
  }
26
66
 
@@ -72,7 +112,7 @@ async function createOAuthConfig(): Promise<CreateKoraAuthServerOptions['oauth']
72
112
  const oauthStores = process.env.DATABASE_URL
73
113
  ? await createPostgresOAuthStores({ connectionString: process.env.DATABASE_URL })
74
114
  : await createSqliteOAuthStores({
75
- filename: process.env.KORA_AUTH_DB || './kora-auth.db',
115
+ filename: process.env.KORA_AUTH_DB || './.kora/kora-auth.db',
76
116
  })
77
117
 
78
118
  return {
@@ -5,19 +5,21 @@ import type { Plugin } from 'vite'
5
5
  import { defineConfig } from 'vite'
6
6
 
7
7
  function crossOriginIsolation(): Plugin {
8
+ const embedderPolicy = process.env.KORA_COEP_POLICY || 'credentialless'
9
+
8
10
  return {
9
11
  name: 'cross-origin-isolation',
10
12
  configureServer(server) {
11
13
  server.middlewares.use((_req, res, next) => {
12
14
  res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
13
- res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
15
+ res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
14
16
  next()
15
17
  })
16
18
  },
17
19
  configurePreviewServer(server) {
18
20
  server.middlewares.use((_req, res, next) => {
19
21
  res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
20
- res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
22
+ res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
21
23
  next()
22
24
  })
23
25
  },
@@ -68,12 +70,19 @@ export default defineConfig({
68
70
  },
69
71
  server: {
70
72
  allowedHosts: true,
73
+ watch: {
74
+ ignored: ['**/.kora/**', '**/*.db', '**/*.db-shm', '**/*.db-wal'],
75
+ },
71
76
  proxy: {
72
77
  '/kora-sync': {
73
78
  target: 'ws://localhost:3001',
74
79
  ws: true,
75
80
  rewriteWsOrigin: true,
76
81
  },
82
+ '/auth': {
83
+ target: 'http://localhost:3001',
84
+ changeOrigin: true,
85
+ },
77
86
  },
78
87
  },
79
88
  })
@@ -47,7 +47,7 @@ To enable real-time sync across devices, update `src/main.tsx`:
47
47
  const app = createApp({
48
48
  schema,
49
49
  sync: {
50
- url: import.meta.env.VITE_SYNC_URL || 'ws://localhost:3001',
50
+ url: import.meta.env.VITE_SYNC_URL || 'ws://localhost:3001/kora-sync',
51
51
  },
52
52
  // ...
53
53
  })
@@ -6,19 +6,21 @@ import type { Plugin } from 'vite'
6
6
  import { defineConfig } from 'vite'
7
7
 
8
8
  function crossOriginIsolation(): Plugin {
9
+ const embedderPolicy = process.env.KORA_COEP_POLICY || 'credentialless'
10
+
9
11
  return {
10
12
  name: 'cross-origin-isolation',
11
13
  configureServer(server) {
12
14
  server.middlewares.use((_req, res, next) => {
13
15
  res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
14
- res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
16
+ res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
15
17
  next()
16
18
  })
17
19
  },
18
20
  configurePreviewServer(server) {
19
21
  server.middlewares.use((_req, res, next) => {
20
22
  res.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
21
- res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
23
+ res.setHeader('Cross-Origin-Embedder-Policy', embedderPolicy)
22
24
  next()
23
25
  })
24
26
  },