@korajs/cli 1.0.0-beta.12 → 1.0.0-beta.13
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/README.md +25 -107
- package/dist/bin.cjs +808 -340
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +588 -174
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-G2PBYMMA.js → chunk-7Y4J2GXP.js} +168 -153
- package/dist/chunk-7Y4J2GXP.js.map +1 -0
- package/dist/{chunk-Z7GCXMGW.js → chunk-TIKGLNER.js} +44 -9
- package/dist/chunk-TIKGLNER.js.map +1 -0
- package/dist/{chunk-4IKCHRJ6.js → chunk-TVIRMDER.js} +122 -112
- package/dist/chunk-TVIRMDER.js.map +1 -0
- package/dist/create.cjs +63 -28
- package/dist/create.cjs.map +1 -1
- package/dist/create.js +2 -2
- package/dist/index.cjs +41 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +8 -2
- package/dist/{studio-server-NIFKZI4F.js → studio-server-XLGZNGG5.js} +11 -2
- package/dist/studio-server-XLGZNGG5.js.map +1 -0
- package/dist/vite.cjs +351 -0
- package/dist/vite.cjs.map +1 -0
- package/dist/vite.d.cts +161 -0
- package/dist/vite.d.ts +161 -0
- package/dist/vite.js +321 -0
- package/dist/vite.js.map +1 -0
- package/package.json +18 -8
- package/templates/react-basic/AGENTS.md +5 -5
- package/templates/react-basic/vite.config.ts +4 -1
- package/templates/react-sync/AGENTS.md +5 -5
- package/templates/react-sync/server.ts +8 -0
- package/templates/react-sync/src/vite-env.d.ts +1 -0
- package/templates/react-sync/vite.config.ts +4 -1
- package/templates/react-tailwind/AGENTS.md +5 -5
- package/templates/react-tailwind/vite.config.ts +10 -1
- package/templates/react-tailwind-sync/AGENTS.md +9 -7
- package/templates/react-tailwind-sync/server.ts +8 -0
- package/templates/react-tailwind-sync/src/App.tsx +1 -1
- package/templates/react-tailwind-sync/src/kora.ts +32 -0
- package/templates/react-tailwind-sync/src/main.tsx +1 -23
- package/templates/react-tailwind-sync/src/modules/todos/todo.mutations.ts +4 -8
- package/templates/react-tailwind-sync/src/modules/todos/todo.queries.ts +2 -2
- package/templates/react-tailwind-sync/src/modules/todos/useTodos.ts +1 -1
- package/templates/react-tailwind-sync/src/vite-env.d.ts +1 -0
- package/templates/react-tailwind-sync/vite.config.ts +10 -1
- package/templates/svelte-basic/AGENTS.md +4 -4
- package/templates/svelte-basic/package.json.hbs +6 -0
- package/templates/svelte-basic/src/App.svelte +2 -1
- package/templates/svelte-basic/vite.config.ts +4 -1
- package/templates/svelte-sync/AGENTS.md +4 -4
- package/templates/svelte-sync/server.ts +8 -0
- package/templates/svelte-sync/src/App.svelte +2 -1
- package/templates/svelte-sync/vite.config.ts +4 -1
- package/templates/svelte-tailwind/AGENTS.md +4 -4
- package/templates/svelte-tailwind/package.json.hbs +7 -1
- package/templates/svelte-tailwind/src/App.svelte +2 -1
- package/templates/svelte-tailwind/vite.config.ts +10 -1
- package/templates/svelte-tailwind-sync/AGENTS.md +4 -4
- package/templates/svelte-tailwind-sync/package.json.hbs +1 -1
- package/templates/svelte-tailwind-sync/server.ts +8 -0
- package/templates/svelte-tailwind-sync/src/App.svelte +2 -1
- package/templates/svelte-tailwind-sync/vite.config.ts +10 -1
- package/templates/tauri-react/AGENTS.md +5 -5
- package/templates/tauri-react/package.json.hbs +1 -1
- package/templates/tauri-react/server.ts +8 -0
- package/templates/tauri-react/src/vite-env.d.ts +1 -0
- package/templates/vue-basic/AGENTS.md +4 -4
- package/templates/vue-basic/package.json.hbs +6 -0
- package/templates/vue-basic/vite.config.ts +4 -1
- package/templates/vue-sync/AGENTS.md +4 -4
- package/templates/vue-sync/server.ts +8 -0
- package/templates/vue-sync/vite.config.ts +4 -1
- package/templates/vue-tailwind/AGENTS.md +4 -4
- package/templates/vue-tailwind/package.json.hbs +6 -0
- package/templates/vue-tailwind/vite.config.ts +4 -1
- package/templates/vue-tailwind-sync/AGENTS.md +4 -4
- package/templates/vue-tailwind-sync/server.ts +8 -0
- package/templates/vue-tailwind-sync/vite.config.ts +4 -1
- package/dist/chunk-4IKCHRJ6.js.map +0 -1
- package/dist/chunk-G2PBYMMA.js.map +0 -1
- package/dist/chunk-Z7GCXMGW.js.map +0 -1
- package/dist/studio-server-NIFKZI4F.js.map +0 -1
|
@@ -12,7 +12,7 @@ This is a **Kora.js** application: an offline-first app where all data lives in
|
|
|
12
12
|
2. **Never fetch application data over HTTP.** Do not add REST or GraphQL calls for app data, and do not talk to the sync server directly. Read and write through Kora collections only; sync happens automatically in the background.
|
|
13
13
|
3. **Await readiness before direct collection access.** Outside the UI bindings, `await app.ready` before calling `app.<collection>` methods. The bindings handle this for you inside components.
|
|
14
14
|
4. **Offline must keep working.** Any feature you add must function with the network off. Never gate a write or a read on connectivity. If you find yourself checking `navigator.onLine` before a data operation, you are doing it wrong.
|
|
15
|
-
5. **Surface mutation errors.** Fire-and-forget mutate calls
|
|
15
|
+
5. **Surface mutation errors.** Fire-and-forget mutate calls put errors into the mutation state. Always render the mutation's `error` in the UI or handle the promise from the async variant. Silent failure is the worst failure.
|
|
16
16
|
6. **Do not touch `src/kora-worker.ts`.** It wires the SQLite WASM binary URL for both dev and production builds. Changing it breaks production builds in ways that only show up after deploy.
|
|
17
17
|
7. **Do not add a state management library for server or app data.** Kora's reactive queries are the store. Local UI state (form inputs, toggles) can use the framework's normal state tools.
|
|
18
18
|
|
|
@@ -52,12 +52,12 @@ export default defineSchema({
|
|
|
52
52
|
})
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
If you
|
|
55
|
+
If you change a collection's shape, increment `version` and add a `migrations` entry for it (`migrate().addField(...)`): each device migrates its local database on its next open. `npx kora migrate` generates the matching migration for server databases and a transform stub for clients still on the older version.
|
|
56
56
|
|
|
57
57
|
## Project conventions
|
|
58
58
|
|
|
59
59
|
- Feature code lives in `src/modules/<feature>/` with the pattern: `<feature>.schema.ts` (collection definition, imported into `src/schema.ts`), `<feature>.queries.ts` (query builders), `<feature>.mutations.ts` (mutation functions taking a collection accessor).
|
|
60
|
-
- Conflict handling is declarative. Concurrent edits merge automatically (last-write-wins per field,
|
|
60
|
+
- Conflict handling is declarative. Concurrent edits merge automatically (last-write-wins per field, arrays as multisets that keep every device's additions and removals, objects per top-level key). If a field needs domain-specific merging (counters, quantities), add a `resolve` function in the schema rather than writing sync logic.
|
|
61
61
|
- `kora.config.ts` controls the dev environment (ports, sync server, schema watcher).
|
|
62
62
|
|
|
63
63
|
## Commands
|
|
@@ -77,11 +77,11 @@ Sync is configured in `src/main.*` via `createApp({ sync: { url, authClient } })
|
|
|
77
77
|
import { useCollection, useMutation, useQuery, useSyncStatus } from '@korajs/react'
|
|
78
78
|
|
|
79
79
|
const todos = useCollection('todos')
|
|
80
|
-
const rows = useQuery(orderedTodos(todos)) // reactive
|
|
80
|
+
const rows = useQuery(orderedTodos(todos)) // reactive; [] on the very first render
|
|
81
81
|
const addTodo = useMutation((data) => todos.insert(data))
|
|
82
82
|
addTodo.mutate({ title: 'x' }) // fire-and-forget; errors land in addTodo.error
|
|
83
83
|
await addTodo.mutateAsync({ title: 'x' }) // throws on failure; use when you need the error
|
|
84
84
|
const status = useSyncStatus() // status.status, status.pendingOperations
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
Rules: always render `mutation.error` somewhere. The app must work under React StrictMode (the framework's hooks are StrictMode-safe; keep `<StrictMode>` in `src/main.tsx`). `useQuery` returns
|
|
87
|
+
Rules: always render `mutation.error` somewhere. The app must work under React StrictMode (the framework's hooks are StrictMode-safe; keep `<StrictMode>` in `src/main.tsx`). `useQuery` reads the local store: no network, no spinner. Its very first render returns `[]` before the local query has run; use `useQueryState` when you must tell "loading" from "no rows".
|
|
@@ -3,7 +3,9 @@ import {
|
|
|
3
3
|
type CreateKoraAuthServerOptions,
|
|
4
4
|
createKoraAuthServer,
|
|
5
5
|
createPostgresOAuthStores,
|
|
6
|
+
createPostgresUserStore,
|
|
6
7
|
createSqliteOAuthStores,
|
|
8
|
+
createSqliteUserStore,
|
|
7
9
|
googleProvider,
|
|
8
10
|
} from '@korajs/auth/server'
|
|
9
11
|
import {
|
|
@@ -101,8 +103,14 @@ async function createAuth() {
|
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
const oauth = await createOAuthConfig()
|
|
106
|
+
// Users and token revocations persist in the same database (the user store's
|
|
107
|
+
// revocation store is used automatically), so sign-outs survive restarts.
|
|
108
|
+
const userStore = process.env.DATABASE_URL
|
|
109
|
+
? await createPostgresUserStore({ connectionString: process.env.DATABASE_URL })
|
|
110
|
+
: await createSqliteUserStore({ filename: process.env.KORA_AUTH_DB || './.kora/kora-auth.db' })
|
|
104
111
|
return createKoraAuthServer({
|
|
105
112
|
jwtSecret: process.env.KORA_AUTH_SECRET,
|
|
113
|
+
userStore,
|
|
106
114
|
...(oauth ? { oauth } : {}),
|
|
107
115
|
})
|
|
108
116
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, readdirSync } from 'node:fs'
|
|
2
2
|
import { join, resolve } from 'node:path'
|
|
3
|
+
import { koraServiceWorker } from '@korajs/cli/vite'
|
|
3
4
|
import react from '@vitejs/plugin-react'
|
|
4
5
|
import type { Plugin } from 'vite'
|
|
5
6
|
import { defineConfig } from 'vite'
|
|
@@ -70,8 +71,10 @@ function sqliteWasmHotfix(): Plugin {
|
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
// koraServiceWorker() (build only, keep it last): precaches the built app shell so the
|
|
75
|
+
// app opens offline, with a "new version available" reload prompt. See @korajs/cli/vite.
|
|
73
76
|
export default defineConfig({
|
|
74
|
-
plugins: [react(), crossOriginIsolation(), sqliteWasmHotfix()],
|
|
77
|
+
plugins: [react(), crossOriginIsolation(), sqliteWasmHotfix(), koraServiceWorker()],
|
|
75
78
|
worker: {
|
|
76
79
|
format: 'es',
|
|
77
80
|
},
|
|
@@ -12,7 +12,7 @@ This is a **Kora.js** application: an offline-first app where all data lives in
|
|
|
12
12
|
2. **Never fetch application data over HTTP.** Do not add REST or GraphQL calls for app data, and do not talk to the sync server directly. Read and write through Kora collections only; sync happens automatically in the background.
|
|
13
13
|
3. **Await readiness before direct collection access.** Outside the UI bindings, `await app.ready` before calling `app.<collection>` methods. The bindings handle this for you inside components.
|
|
14
14
|
4. **Offline must keep working.** Any feature you add must function with the network off. Never gate a write or a read on connectivity. If you find yourself checking `navigator.onLine` before a data operation, you are doing it wrong.
|
|
15
|
-
5. **Surface mutation errors.** Fire-and-forget mutate calls
|
|
15
|
+
5. **Surface mutation errors.** Fire-and-forget mutate calls put errors into the mutation state. Always render the mutation's `error` in the UI or handle the promise from the async variant. Silent failure is the worst failure.
|
|
16
16
|
6. **Do not touch `src/kora-worker.ts`.** It wires the SQLite WASM binary URL for both dev and production builds. Changing it breaks production builds in ways that only show up after deploy.
|
|
17
17
|
7. **Do not add a state management library for server or app data.** Kora's reactive queries are the store. Local UI state (form inputs, toggles) can use the framework's normal state tools.
|
|
18
18
|
|
|
@@ -52,12 +52,12 @@ export default defineSchema({
|
|
|
52
52
|
})
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
If you
|
|
55
|
+
If you change a collection's shape, increment `version` and add a `migrations` entry for it (`migrate().addField(...)`): each device migrates its local database on its next open. `npx kora migrate` generates the matching migration for server databases and a transform stub for clients still on the older version.
|
|
56
56
|
|
|
57
57
|
## Project conventions
|
|
58
58
|
|
|
59
59
|
- Feature code lives in `src/modules/<feature>/` with the pattern: `<feature>.schema.ts` (collection definition, imported into `src/schema.ts`), `<feature>.queries.ts` (query builders), `<feature>.mutations.ts` (mutation functions taking a collection accessor).
|
|
60
|
-
- Conflict handling is declarative. Concurrent edits merge automatically (last-write-wins per field,
|
|
60
|
+
- Conflict handling is declarative. Concurrent edits merge automatically (last-write-wins per field, arrays as multisets that keep every device's additions and removals, objects per top-level key). If a field needs domain-specific merging (counters, quantities), add a `resolve` function in the schema rather than writing sync logic.
|
|
61
61
|
- `kora.config.ts` controls the dev environment (ports, sync server, schema watcher).
|
|
62
62
|
|
|
63
63
|
## Commands
|
|
@@ -77,11 +77,11 @@ Sync is configured in `src/main.*` via `createApp({ sync: { url, authClient } })
|
|
|
77
77
|
import { useCollection, useMutation, useQuery, useSyncStatus } from '@korajs/react'
|
|
78
78
|
|
|
79
79
|
const todos = useCollection('todos')
|
|
80
|
-
const rows = useQuery(orderedTodos(todos)) // reactive
|
|
80
|
+
const rows = useQuery(orderedTodos(todos)) // reactive; [] on the very first render
|
|
81
81
|
const addTodo = useMutation((data) => todos.insert(data))
|
|
82
82
|
addTodo.mutate({ title: 'x' }) // fire-and-forget; errors land in addTodo.error
|
|
83
83
|
await addTodo.mutateAsync({ title: 'x' }) // throws on failure; use when you need the error
|
|
84
84
|
const status = useSyncStatus() // status.status, status.pendingOperations
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
Rules: always render `mutation.error` somewhere. The app must work under React StrictMode (the framework's hooks are StrictMode-safe; keep `<StrictMode>` in `src/main.tsx`). `useQuery` returns
|
|
87
|
+
Rules: always render `mutation.error` somewhere. The app must work under React StrictMode (the framework's hooks are StrictMode-safe; keep `<StrictMode>` in `src/main.tsx`). `useQuery` reads the local store: no network, no spinner. Its very first render returns `[]` before the local query has run; use `useQueryState` when you must tell "loading" from "no rows".
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, readdirSync } from 'node:fs'
|
|
2
2
|
import { join, resolve } from 'node:path'
|
|
3
|
+
import { koraServiceWorker } from '@korajs/cli/vite'
|
|
3
4
|
import tailwindcss from '@tailwindcss/vite'
|
|
4
5
|
import react from '@vitejs/plugin-react'
|
|
5
6
|
import type { Plugin } from 'vite'
|
|
@@ -71,8 +72,16 @@ function sqliteWasmHotfix(): Plugin {
|
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
// koraServiceWorker() (build only, keep it last): precaches the built app shell so the
|
|
76
|
+
// app opens offline, with a "new version available" reload prompt. See @korajs/cli/vite.
|
|
74
77
|
export default defineConfig({
|
|
75
|
-
plugins: [
|
|
78
|
+
plugins: [
|
|
79
|
+
react(),
|
|
80
|
+
tailwindcss(),
|
|
81
|
+
crossOriginIsolation(),
|
|
82
|
+
sqliteWasmHotfix(),
|
|
83
|
+
koraServiceWorker(),
|
|
84
|
+
],
|
|
76
85
|
worker: {
|
|
77
86
|
format: 'es',
|
|
78
87
|
},
|
|
@@ -12,7 +12,7 @@ This is a **Kora.js** application: an offline-first app where all data lives in
|
|
|
12
12
|
2. **Never fetch application data over HTTP.** Do not add REST or GraphQL calls for app data, and do not talk to the sync server directly. Read and write through Kora collections only; sync happens automatically in the background.
|
|
13
13
|
3. **Await readiness before direct collection access.** Outside the UI bindings, `await app.ready` before calling `app.<collection>` methods. The bindings handle this for you inside components.
|
|
14
14
|
4. **Offline must keep working.** Any feature you add must function with the network off. Never gate a write or a read on connectivity. If you find yourself checking `navigator.onLine` before a data operation, you are doing it wrong.
|
|
15
|
-
5. **Surface mutation errors.** Fire-and-forget mutate calls
|
|
15
|
+
5. **Surface mutation errors.** Fire-and-forget mutate calls put errors into the mutation state. Always render the mutation's `error` in the UI or handle the promise from the async variant. Silent failure is the worst failure.
|
|
16
16
|
6. **Do not touch `src/kora-worker.ts`.** It wires the SQLite WASM binary URL for both dev and production builds. Changing it breaks production builds in ways that only show up after deploy.
|
|
17
17
|
7. **Do not add a state management library for server or app data.** Kora's reactive queries are the store. Local UI state (form inputs, toggles) can use the framework's normal state tools.
|
|
18
18
|
|
|
@@ -52,12 +52,12 @@ export default defineSchema({
|
|
|
52
52
|
})
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
If you
|
|
55
|
+
If you change a collection's shape, increment `version` and add a `migrations` entry for it (`migrate().addField(...)`): each device migrates its local database on its next open. `npx kora migrate` generates the matching migration for server databases and a transform stub for clients still on the older version.
|
|
56
56
|
|
|
57
57
|
## Project conventions
|
|
58
58
|
|
|
59
59
|
- Feature code lives in `src/modules/<feature>/` with the pattern: `<feature>.schema.ts` (collection definition, imported into `src/schema.ts`), `<feature>.queries.ts` (query builders), `<feature>.mutations.ts` (mutation functions taking a collection accessor).
|
|
60
|
-
- Conflict handling is declarative. Concurrent edits merge automatically (last-write-wins per field,
|
|
60
|
+
- Conflict handling is declarative. Concurrent edits merge automatically (last-write-wins per field, arrays as multisets that keep every device's additions and removals, objects per top-level key). If a field needs domain-specific merging (counters, quantities), add a `resolve` function in the schema rather than writing sync logic.
|
|
61
61
|
- `kora.config.ts` controls the dev environment (ports, sync server, schema watcher).
|
|
62
62
|
|
|
63
63
|
## Commands
|
|
@@ -69,19 +69,21 @@ If you bump collections in a way that changes shapes, increment `version` and ru
|
|
|
69
69
|
|
|
70
70
|
## Sync and auth
|
|
71
71
|
|
|
72
|
-
Sync is configured in `src/
|
|
72
|
+
Sync is configured in `src/kora.ts` via `createApp({ sync: { url, authClient } })`. In dev, the sync URL is derived from the page host and proxied by Vite; in production set `VITE_SYNC_URL`. Auth (if present in this template) uses `@korajs/auth`; the client is created in `src/auth.ts`. Local writes work without sign-in; sync requires the server to accept the connection.
|
|
73
73
|
|
|
74
74
|
## React bindings
|
|
75
75
|
|
|
76
|
+
Import the hooks from `src/kora.ts`, where `createKoraHooks<typeof app>()` binds them to this app's schema: collection names, inserts, updates, `where` and the rows `useQuery` returns are type-checked. Do not import them from `@korajs/react` directly (those are untyped).
|
|
77
|
+
|
|
76
78
|
```tsx
|
|
77
|
-
import { useCollection, useMutation, useQuery, useSyncStatus } from '
|
|
79
|
+
import { useCollection, useMutation, useQuery, useSyncStatus } from './kora'
|
|
78
80
|
|
|
79
81
|
const todos = useCollection('todos')
|
|
80
|
-
const rows = useQuery(orderedTodos(todos)) // reactive
|
|
82
|
+
const rows = useQuery(orderedTodos(todos)) // reactive; [] on the very first render
|
|
81
83
|
const addTodo = useMutation((data) => todos.insert(data))
|
|
82
84
|
addTodo.mutate({ title: 'x' }) // fire-and-forget; errors land in addTodo.error
|
|
83
85
|
await addTodo.mutateAsync({ title: 'x' }) // throws on failure; use when you need the error
|
|
84
86
|
const status = useSyncStatus() // status.status, status.pendingOperations
|
|
85
87
|
```
|
|
86
88
|
|
|
87
|
-
Rules: always render `mutation.error` somewhere. The app must work under React StrictMode (the framework's hooks are StrictMode-safe; keep `<StrictMode>` in `src/main.tsx`). `useQuery` returns
|
|
89
|
+
Rules: always render `mutation.error` somewhere. The app must work under React StrictMode (the framework's hooks are StrictMode-safe; keep `<StrictMode>` in `src/main.tsx`). `useQuery` reads the local store: no network, no spinner. Its very first render returns `[]` before the local query has run; use `useQueryState` when you must tell "loading" from "no rows".
|
|
@@ -3,7 +3,9 @@ import {
|
|
|
3
3
|
type CreateKoraAuthServerOptions,
|
|
4
4
|
createKoraAuthServer,
|
|
5
5
|
createPostgresOAuthStores,
|
|
6
|
+
createPostgresUserStore,
|
|
6
7
|
createSqliteOAuthStores,
|
|
8
|
+
createSqliteUserStore,
|
|
7
9
|
googleProvider,
|
|
8
10
|
} from '@korajs/auth/server'
|
|
9
11
|
import {
|
|
@@ -101,8 +103,14 @@ async function createAuth() {
|
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
const oauth = await createOAuthConfig()
|
|
106
|
+
// Users and token revocations persist in the same database (the user store's
|
|
107
|
+
// revocation store is used automatically), so sign-outs survive restarts.
|
|
108
|
+
const userStore = process.env.DATABASE_URL
|
|
109
|
+
? await createPostgresUserStore({ connectionString: process.env.DATABASE_URL })
|
|
110
|
+
: await createSqliteUserStore({ filename: process.env.KORA_AUTH_DB || './.kora/kora-auth.db' })
|
|
104
111
|
return createKoraAuthServer({
|
|
105
112
|
jwtSecret: process.env.KORA_AUTH_SECRET,
|
|
113
|
+
userStore,
|
|
106
114
|
...(oauth ? { oauth } : {}),
|
|
107
115
|
})
|
|
108
116
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useAuth } from '@korajs/auth/react'
|
|
2
|
-
import { useSyncStatus } from '@korajs/react'
|
|
3
2
|
import {
|
|
4
3
|
AlertCircle,
|
|
5
4
|
CheckCircle2,
|
|
@@ -12,6 +11,7 @@ import {
|
|
|
12
11
|
WifiOff,
|
|
13
12
|
} from 'lucide-react'
|
|
14
13
|
import { useState } from 'react'
|
|
14
|
+
import { useSyncStatus } from './kora'
|
|
15
15
|
import { useTodos } from './modules/todos/useTodos'
|
|
16
16
|
|
|
17
17
|
type Filter = 'all' | 'active' | 'completed'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createKoraAuthSync } from '@korajs/auth'
|
|
2
|
+
import { createApp } from 'korajs'
|
|
3
|
+
import { createKoraHooks } from 'korajs/react'
|
|
4
|
+
import { authClient } from './auth'
|
|
5
|
+
import koraWorkerUrl from './kora-worker.ts?worker&url'
|
|
6
|
+
import schema from './schema'
|
|
7
|
+
|
|
8
|
+
// Build sync URL: use env var if set, otherwise derive from current page host.
|
|
9
|
+
// This allows the Vite proxy (/kora-sync → ws://localhost:3001) to work in dev,
|
|
10
|
+
// and also works through any tunnel (ngrok, cloudflared) without extra configuration.
|
|
11
|
+
const syncUrl =
|
|
12
|
+
import.meta.env.VITE_SYNC_URL ||
|
|
13
|
+
`${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/kora-sync`
|
|
14
|
+
|
|
15
|
+
export const app = createApp({
|
|
16
|
+
schema,
|
|
17
|
+
sync: {
|
|
18
|
+
url: syncUrl,
|
|
19
|
+
authClient: createKoraAuthSync({ authClient, schema }),
|
|
20
|
+
},
|
|
21
|
+
store: {
|
|
22
|
+
workerUrl: koraWorkerUrl,
|
|
23
|
+
},
|
|
24
|
+
devtools: import.meta.env.DEV,
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
/** The typed `todos` collection: inserts, updates and queries are checked against the schema. */
|
|
28
|
+
export type Todos = typeof app.todos
|
|
29
|
+
|
|
30
|
+
// Hooks bound to this app's schema: `useCollection('todoz')` is a type error, and the
|
|
31
|
+
// rows `useQuery` returns are typed from the query.
|
|
32
|
+
export const { useCollection, useQuery, useMutation, useSyncStatus } = createKoraHooks<typeof app>()
|
|
@@ -1,33 +1,11 @@
|
|
|
1
|
-
import { createKoraAuthSync } from '@korajs/auth'
|
|
2
1
|
import { AuthProvider } from '@korajs/auth/react'
|
|
3
2
|
import { KoraProvider } from '@korajs/react'
|
|
4
|
-
import { createApp } from 'korajs'
|
|
5
3
|
import { StrictMode } from 'react'
|
|
6
4
|
import { createRoot } from 'react-dom/client'
|
|
7
5
|
import { App } from './App'
|
|
8
6
|
import { authClient, completeOAuthCallbackFromLocation } from './auth'
|
|
9
|
-
import
|
|
7
|
+
import { app } from './kora'
|
|
10
8
|
import './index.css'
|
|
11
|
-
import koraWorkerUrl from './kora-worker.ts?worker&url'
|
|
12
|
-
|
|
13
|
-
// Build sync URL: use env var if set, otherwise derive from current page host.
|
|
14
|
-
// This allows the Vite proxy (/kora-sync → ws://localhost:3001) to work in dev,
|
|
15
|
-
// and also works through any tunnel (ngrok, cloudflared) without extra configuration.
|
|
16
|
-
const syncUrl =
|
|
17
|
-
import.meta.env.VITE_SYNC_URL ||
|
|
18
|
-
`${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/kora-sync`
|
|
19
|
-
|
|
20
|
-
const app = createApp({
|
|
21
|
-
schema,
|
|
22
|
-
sync: {
|
|
23
|
-
url: syncUrl,
|
|
24
|
-
authClient: createKoraAuthSync({ authClient, schema }),
|
|
25
|
-
},
|
|
26
|
-
store: {
|
|
27
|
-
workerUrl: koraWorkerUrl,
|
|
28
|
-
},
|
|
29
|
-
devtools: import.meta.env.DEV,
|
|
30
|
-
})
|
|
31
9
|
|
|
32
10
|
// Connect to sync server once the app is ready
|
|
33
11
|
app.ready.then(() => app.sync?.connect())
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Todos } from '../../kora'
|
|
2
2
|
|
|
3
3
|
export interface CreateTodoInput {
|
|
4
4
|
title: string
|
|
@@ -8,18 +8,14 @@ export interface UpdateTodoStatusInput {
|
|
|
8
8
|
completed: boolean
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export function createTodo(todos:
|
|
11
|
+
export function createTodo(todos: Todos, data: CreateTodoInput) {
|
|
12
12
|
return todos.insert({ title: data.title })
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function updateTodoStatus(
|
|
16
|
-
todos: CollectionAccessor,
|
|
17
|
-
id: string,
|
|
18
|
-
data: UpdateTodoStatusInput,
|
|
19
|
-
) {
|
|
15
|
+
export function updateTodoStatus(todos: Todos, id: string, data: UpdateTodoStatusInput) {
|
|
20
16
|
return todos.update(id, { completed: data.completed })
|
|
21
17
|
}
|
|
22
18
|
|
|
23
|
-
export function deleteTodo(todos:
|
|
19
|
+
export function deleteTodo(todos: Todos, id: string) {
|
|
24
20
|
return todos.delete(id)
|
|
25
21
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, readdirSync } from 'node:fs'
|
|
2
2
|
import { join, resolve } from 'node:path'
|
|
3
|
+
import { koraServiceWorker } from '@korajs/cli/vite'
|
|
3
4
|
import tailwindcss from '@tailwindcss/vite'
|
|
4
5
|
import react from '@vitejs/plugin-react'
|
|
5
6
|
import type { Plugin } from 'vite'
|
|
@@ -71,8 +72,16 @@ function sqliteWasmHotfix(): Plugin {
|
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
// koraServiceWorker() (build only, keep it last): precaches the built app shell so the
|
|
76
|
+
// app opens offline, with a "new version available" reload prompt. See @korajs/cli/vite.
|
|
74
77
|
export default defineConfig({
|
|
75
|
-
plugins: [
|
|
78
|
+
plugins: [
|
|
79
|
+
react(),
|
|
80
|
+
tailwindcss(),
|
|
81
|
+
crossOriginIsolation(),
|
|
82
|
+
sqliteWasmHotfix(),
|
|
83
|
+
koraServiceWorker(),
|
|
84
|
+
],
|
|
76
85
|
worker: {
|
|
77
86
|
format: 'es',
|
|
78
87
|
},
|
|
@@ -12,7 +12,7 @@ This is a **Kora.js** application: an offline-first app where all data lives in
|
|
|
12
12
|
2. **Never fetch application data over HTTP.** Do not add REST or GraphQL calls for app data, and do not talk to the sync server directly. Read and write through Kora collections only; sync happens automatically in the background.
|
|
13
13
|
3. **Await readiness before direct collection access.** Outside the UI bindings, `await app.ready` before calling `app.<collection>` methods. The bindings handle this for you inside components.
|
|
14
14
|
4. **Offline must keep working.** Any feature you add must function with the network off. Never gate a write or a read on connectivity. If you find yourself checking `navigator.onLine` before a data operation, you are doing it wrong.
|
|
15
|
-
5. **Surface mutation errors.** Fire-and-forget mutate calls
|
|
15
|
+
5. **Surface mutation errors.** Fire-and-forget mutate calls put errors into the mutation state. Always render the mutation's `error` in the UI or handle the promise from the async variant. Silent failure is the worst failure.
|
|
16
16
|
6. **Do not touch `src/kora-worker.ts`.** It wires the SQLite WASM binary URL for both dev and production builds. Changing it breaks production builds in ways that only show up after deploy.
|
|
17
17
|
7. **Do not add a state management library for server or app data.** Kora's reactive queries are the store. Local UI state (form inputs, toggles) can use the framework's normal state tools.
|
|
18
18
|
|
|
@@ -52,12 +52,12 @@ export default defineSchema({
|
|
|
52
52
|
})
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
If you
|
|
55
|
+
If you change a collection's shape, increment `version` and add a `migrations` entry for it (`migrate().addField(...)`): each device migrates its local database on its next open. `npx kora migrate` generates the matching migration for server databases and a transform stub for clients still on the older version.
|
|
56
56
|
|
|
57
57
|
## Project conventions
|
|
58
58
|
|
|
59
59
|
- Feature code lives in `src/modules/<feature>/` with the pattern: `<feature>.schema.ts` (collection definition, imported into `src/schema.ts`), `<feature>.queries.ts` (query builders), `<feature>.mutations.ts` (mutation functions taking a collection accessor).
|
|
60
|
-
- Conflict handling is declarative. Concurrent edits merge automatically (last-write-wins per field,
|
|
60
|
+
- Conflict handling is declarative. Concurrent edits merge automatically (last-write-wins per field, arrays as multisets that keep every device's additions and removals, objects per top-level key). If a field needs domain-specific merging (counters, quantities), add a `resolve` function in the schema rather than writing sync logic.
|
|
61
61
|
- `kora.config.ts` controls the dev environment (ports, sync server, schema watcher).
|
|
62
62
|
|
|
63
63
|
## Commands
|
|
@@ -73,4 +73,4 @@ Sync is configured in `src/main.*` via `createApp({ sync: { url, authClient } })
|
|
|
73
73
|
|
|
74
74
|
## Svelte bindings
|
|
75
75
|
|
|
76
|
-
Kora's Svelte package exposes store-based bindings from `@korajs/svelte`, wired through the provider set up in the app entry. Queries are reactive stores
|
|
76
|
+
Kora's Svelte package exposes store-based bindings from `@korajs/svelte`, wired through the provider set up in the app entry. Queries are reactive stores over the local database (no network, no spinner; `[]` until the first local result); mutations expose error state that must be surfaced in the UI.
|
|
@@ -17,10 +17,16 @@
|
|
|
17
17
|
"svelte": "^5.0.0",
|
|
18
18
|
"yjs": "^13.6.30"
|
|
19
19
|
},
|
|
20
|
+
"pnpm": {
|
|
21
|
+
"onlyBuiltDependencies": [
|
|
22
|
+
"esbuild"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
20
25
|
"devDependencies": {
|
|
21
26
|
"@korajs/cli": "{{koraVersion}}",
|
|
22
27
|
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
|
23
28
|
"svelte-check": "^4.1.0",
|
|
29
|
+
"tsx": "^4.19.0",
|
|
24
30
|
"typescript": "^5.7.0",
|
|
25
31
|
"vite": "^6.0.0"
|
|
26
32
|
}
|
|
@@ -6,7 +6,8 @@ type Filter = 'all' | 'active' | 'completed'
|
|
|
6
6
|
const { allTodos, activeTodos, completedTodos, addTodo, toggleTodo, deleteTodo } =
|
|
7
7
|
createTodosStores()
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
// biome-ignore lint/style/useConst: reassigned in the markup, which Biome does not see
|
|
10
|
+
let filter = $state<Filter>('all')
|
|
10
11
|
let input = $state('')
|
|
11
12
|
|
|
12
13
|
const filteredTodos = $derived(
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, readdirSync } from 'node:fs'
|
|
2
2
|
import { join, resolve } from 'node:path'
|
|
3
|
+
import { koraServiceWorker } from '@korajs/cli/vite'
|
|
3
4
|
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
|
4
5
|
import type { Plugin } from 'vite'
|
|
5
6
|
import { defineConfig } from 'vite'
|
|
@@ -43,8 +44,10 @@ function sqliteWasmHotfix(): Plugin {
|
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
// koraServiceWorker() (build only, keep it last): precaches the built app shell so the
|
|
48
|
+
// app opens offline, with a "new version available" reload prompt. See @korajs/cli/vite.
|
|
46
49
|
export default defineConfig({
|
|
47
|
-
plugins: [svelte(), crossOriginIsolation(), sqliteWasmHotfix()],
|
|
50
|
+
plugins: [svelte(), crossOriginIsolation(), sqliteWasmHotfix(), koraServiceWorker()],
|
|
48
51
|
worker: { format: 'es' },
|
|
49
52
|
optimizeDeps: {
|
|
50
53
|
exclude: ['@sqlite.org/sqlite-wasm', '@korajs/store'],
|
|
@@ -12,7 +12,7 @@ This is a **Kora.js** application: an offline-first app where all data lives in
|
|
|
12
12
|
2. **Never fetch application data over HTTP.** Do not add REST or GraphQL calls for app data, and do not talk to the sync server directly. Read and write through Kora collections only; sync happens automatically in the background.
|
|
13
13
|
3. **Await readiness before direct collection access.** Outside the UI bindings, `await app.ready` before calling `app.<collection>` methods. The bindings handle this for you inside components.
|
|
14
14
|
4. **Offline must keep working.** Any feature you add must function with the network off. Never gate a write or a read on connectivity. If you find yourself checking `navigator.onLine` before a data operation, you are doing it wrong.
|
|
15
|
-
5. **Surface mutation errors.** Fire-and-forget mutate calls
|
|
15
|
+
5. **Surface mutation errors.** Fire-and-forget mutate calls put errors into the mutation state. Always render the mutation's `error` in the UI or handle the promise from the async variant. Silent failure is the worst failure.
|
|
16
16
|
6. **Do not touch `src/kora-worker.ts`.** It wires the SQLite WASM binary URL for both dev and production builds. Changing it breaks production builds in ways that only show up after deploy.
|
|
17
17
|
7. **Do not add a state management library for server or app data.** Kora's reactive queries are the store. Local UI state (form inputs, toggles) can use the framework's normal state tools.
|
|
18
18
|
|
|
@@ -52,12 +52,12 @@ export default defineSchema({
|
|
|
52
52
|
})
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
If you
|
|
55
|
+
If you change a collection's shape, increment `version` and add a `migrations` entry for it (`migrate().addField(...)`): each device migrates its local database on its next open. `npx kora migrate` generates the matching migration for server databases and a transform stub for clients still on the older version.
|
|
56
56
|
|
|
57
57
|
## Project conventions
|
|
58
58
|
|
|
59
59
|
- Feature code lives in `src/modules/<feature>/` with the pattern: `<feature>.schema.ts` (collection definition, imported into `src/schema.ts`), `<feature>.queries.ts` (query builders), `<feature>.mutations.ts` (mutation functions taking a collection accessor).
|
|
60
|
-
- Conflict handling is declarative. Concurrent edits merge automatically (last-write-wins per field,
|
|
60
|
+
- Conflict handling is declarative. Concurrent edits merge automatically (last-write-wins per field, arrays as multisets that keep every device's additions and removals, objects per top-level key). If a field needs domain-specific merging (counters, quantities), add a `resolve` function in the schema rather than writing sync logic.
|
|
61
61
|
- `kora.config.ts` controls the dev environment (ports, sync server, schema watcher).
|
|
62
62
|
|
|
63
63
|
## Commands
|
|
@@ -73,4 +73,4 @@ Sync is configured in `src/main.*` via `createApp({ sync: { url, authClient } })
|
|
|
73
73
|
|
|
74
74
|
## Svelte bindings
|
|
75
75
|
|
|
76
|
-
Kora's Svelte package exposes store-based bindings from `@korajs/svelte`, wired through the provider set up in the app entry. Queries are reactive stores
|
|
76
|
+
Kora's Svelte package exposes store-based bindings from `@korajs/svelte`, wired through the provider set up in the app entry. Queries are reactive stores over the local database (no network, no spinner; `[]` until the first local result); mutations expose error state that must be surfaced in the UI.
|
|
@@ -3,7 +3,9 @@ import {
|
|
|
3
3
|
type CreateKoraAuthServerOptions,
|
|
4
4
|
createKoraAuthServer,
|
|
5
5
|
createPostgresOAuthStores,
|
|
6
|
+
createPostgresUserStore,
|
|
6
7
|
createSqliteOAuthStores,
|
|
8
|
+
createSqliteUserStore,
|
|
7
9
|
googleProvider,
|
|
8
10
|
} from '@korajs/auth/server'
|
|
9
11
|
import {
|
|
@@ -101,8 +103,14 @@ async function createAuth() {
|
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
const oauth = await createOAuthConfig()
|
|
106
|
+
// Users and token revocations persist in the same database (the user store's
|
|
107
|
+
// revocation store is used automatically), so sign-outs survive restarts.
|
|
108
|
+
const userStore = process.env.DATABASE_URL
|
|
109
|
+
? await createPostgresUserStore({ connectionString: process.env.DATABASE_URL })
|
|
110
|
+
: await createSqliteUserStore({ filename: process.env.KORA_AUTH_DB || './.kora/kora-auth.db' })
|
|
104
111
|
return createKoraAuthServer({
|
|
105
112
|
jwtSecret: process.env.KORA_AUTH_SECRET,
|
|
113
|
+
userStore,
|
|
106
114
|
...(oauth ? { oauth } : {}),
|
|
107
115
|
})
|
|
108
116
|
}
|
|
@@ -10,7 +10,8 @@ const auth = createAuthStore()
|
|
|
10
10
|
const todos = createTodosStores()
|
|
11
11
|
const { allTodos, activeTodos, completedTodos, addTodo, toggleTodo, deleteTodo } = todos
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
// biome-ignore lint/style/useConst: reassigned in the markup, which Biome does not see
|
|
14
|
+
let filter = $state<Filter>('all')
|
|
14
15
|
let input = $state('')
|
|
15
16
|
|
|
16
17
|
const filteredTodos = $derived(
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, readdirSync } from 'node:fs'
|
|
2
2
|
import { join, resolve } from 'node:path'
|
|
3
|
+
import { koraServiceWorker } from '@korajs/cli/vite'
|
|
3
4
|
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
|
4
5
|
import type { Plugin } from 'vite'
|
|
5
6
|
import { defineConfig } from 'vite'
|
|
@@ -56,8 +57,10 @@ function sqliteWasmHotfix(): Plugin {
|
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
// koraServiceWorker() (build only, keep it last): precaches the built app shell so the
|
|
61
|
+
// app opens offline, with a "new version available" reload prompt. See @korajs/cli/vite.
|
|
59
62
|
export default defineConfig({
|
|
60
|
-
plugins: [svelte(), crossOriginIsolation(), sqliteWasmHotfix()],
|
|
63
|
+
plugins: [svelte(), crossOriginIsolation(), sqliteWasmHotfix(), koraServiceWorker()],
|
|
61
64
|
worker: {
|
|
62
65
|
format: 'es',
|
|
63
66
|
},
|
|
@@ -12,7 +12,7 @@ This is a **Kora.js** application: an offline-first app where all data lives in
|
|
|
12
12
|
2. **Never fetch application data over HTTP.** Do not add REST or GraphQL calls for app data, and do not talk to the sync server directly. Read and write through Kora collections only; sync happens automatically in the background.
|
|
13
13
|
3. **Await readiness before direct collection access.** Outside the UI bindings, `await app.ready` before calling `app.<collection>` methods. The bindings handle this for you inside components.
|
|
14
14
|
4. **Offline must keep working.** Any feature you add must function with the network off. Never gate a write or a read on connectivity. If you find yourself checking `navigator.onLine` before a data operation, you are doing it wrong.
|
|
15
|
-
5. **Surface mutation errors.** Fire-and-forget mutate calls
|
|
15
|
+
5. **Surface mutation errors.** Fire-and-forget mutate calls put errors into the mutation state. Always render the mutation's `error` in the UI or handle the promise from the async variant. Silent failure is the worst failure.
|
|
16
16
|
6. **Do not touch `src/kora-worker.ts`.** It wires the SQLite WASM binary URL for both dev and production builds. Changing it breaks production builds in ways that only show up after deploy.
|
|
17
17
|
7. **Do not add a state management library for server or app data.** Kora's reactive queries are the store. Local UI state (form inputs, toggles) can use the framework's normal state tools.
|
|
18
18
|
|
|
@@ -52,12 +52,12 @@ export default defineSchema({
|
|
|
52
52
|
})
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
If you
|
|
55
|
+
If you change a collection's shape, increment `version` and add a `migrations` entry for it (`migrate().addField(...)`): each device migrates its local database on its next open. `npx kora migrate` generates the matching migration for server databases and a transform stub for clients still on the older version.
|
|
56
56
|
|
|
57
57
|
## Project conventions
|
|
58
58
|
|
|
59
59
|
- Feature code lives in `src/modules/<feature>/` with the pattern: `<feature>.schema.ts` (collection definition, imported into `src/schema.ts`), `<feature>.queries.ts` (query builders), `<feature>.mutations.ts` (mutation functions taking a collection accessor).
|
|
60
|
-
- Conflict handling is declarative. Concurrent edits merge automatically (last-write-wins per field,
|
|
60
|
+
- Conflict handling is declarative. Concurrent edits merge automatically (last-write-wins per field, arrays as multisets that keep every device's additions and removals, objects per top-level key). If a field needs domain-specific merging (counters, quantities), add a `resolve` function in the schema rather than writing sync logic.
|
|
61
61
|
- `kora.config.ts` controls the dev environment (ports, sync server, schema watcher).
|
|
62
62
|
|
|
63
63
|
## Commands
|
|
@@ -73,4 +73,4 @@ Sync is configured in `src/main.*` via `createApp({ sync: { url, authClient } })
|
|
|
73
73
|
|
|
74
74
|
## Svelte bindings
|
|
75
75
|
|
|
76
|
-
Kora's Svelte package exposes store-based bindings from `@korajs/svelte`, wired through the provider set up in the app entry. Queries are reactive stores
|
|
76
|
+
Kora's Svelte package exposes store-based bindings from `@korajs/svelte`, wired through the provider set up in the app entry. Queries are reactive stores over the local database (no network, no spinner; `[]` until the first local result); mutations expose error state that must be surfaced in the UI.
|