@korajs/cli 0.6.0 → 1.0.0-beta.0
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/LICENSE +21 -0
- package/dist/bin.cjs +2582 -202
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +152 -96
- package/dist/bin.js.map +1 -1
- package/dist/chunk-5NI2FEQL.js +92 -0
- package/dist/chunk-5NI2FEQL.js.map +1 -0
- package/dist/chunk-6C4BHSRA.js +82 -0
- package/dist/chunk-6C4BHSRA.js.map +1 -0
- package/dist/{chunk-VLTPEATY.js → chunk-B5YS4STN.js} +9 -96
- package/dist/chunk-B5YS4STN.js.map +1 -0
- package/dist/{chunk-EEZNRI5W.js → chunk-BWTKRKNJ.js} +13 -5
- package/dist/{chunk-EEZNRI5W.js.map → chunk-BWTKRKNJ.js.map} +1 -1
- package/dist/{chunk-Q2FBCOQD.js → chunk-EOWLAAIV.js} +5 -3
- package/dist/{chunk-Q2FBCOQD.js.map → chunk-EOWLAAIV.js.map} +1 -1
- package/dist/create.cjs +8 -2
- package/dist/create.cjs.map +1 -1
- package/dist/create.js +3 -2
- package/dist/create.js.map +1 -1
- package/dist/index.js +3 -2
- package/dist/lab-manager-KUDII6BT.js +280 -0
- package/dist/lab-manager-KUDII6BT.js.map +1 -0
- package/dist/schema-loader-GGHECMTM.js +9 -0
- package/dist/schema-loader-GGHECMTM.js.map +1 -0
- package/dist/spectator-manager-E2LH2P54.js +142 -0
- package/dist/spectator-manager-E2LH2P54.js.map +1 -0
- package/dist/studio-server-NIFKZI4F.js +1738 -0
- package/dist/studio-server-NIFKZI4F.js.map +1 -0
- package/package.json +8 -6
- package/templates/react-basic/AGENTS.md +87 -0
- package/templates/react-sync/AGENTS.md +87 -0
- package/templates/react-sync/src/App.tsx +8 -0
- package/templates/react-sync/src/index.css +11 -0
- package/templates/react-tailwind/AGENTS.md +87 -0
- package/templates/react-tailwind-sync/AGENTS.md +87 -0
- package/templates/react-tailwind-sync/src/App.tsx +28 -0
- package/templates/svelte-basic/AGENTS.md +76 -0
- package/templates/svelte-basic/src/App.svelte +27 -31
- package/templates/svelte-basic/src/Root.svelte +4 -4
- package/templates/svelte-basic/src/modules/todos/useTodos.ts +1 -1
- package/templates/svelte-basic/vite.config.ts +1 -1
- package/templates/svelte-sync/AGENTS.md +76 -0
- package/templates/svelte-sync/src/App.svelte +31 -35
- package/templates/svelte-sync/src/Root.svelte +6 -6
- package/templates/svelte-sync/src/modules/todos/useTodos.ts +1 -1
- package/templates/svelte-tailwind/AGENTS.md +76 -0
- package/templates/svelte-tailwind/src/App.svelte +17 -21
- package/templates/svelte-tailwind/src/Root.svelte +4 -4
- package/templates/svelte-tailwind/src/modules/todos/useTodos.ts +1 -1
- package/templates/svelte-tailwind/vite.config.ts +2 -2
- package/templates/svelte-tailwind-sync/AGENTS.md +76 -0
- package/templates/svelte-tailwind-sync/src/App.svelte +52 -56
- package/templates/svelte-tailwind-sync/src/Root.svelte +6 -6
- package/templates/svelte-tailwind-sync/src/modules/todos/useTodos.ts +1 -1
- package/templates/svelte-tailwind-sync/vite.config.ts +1 -1
- package/templates/tauri-react/AGENTS.md +91 -0
- package/templates/vue-basic/AGENTS.md +84 -0
- package/templates/vue-basic/src/modules/todos/useTodos.ts +1 -1
- package/templates/vue-sync/AGENTS.md +84 -0
- package/templates/vue-sync/src/main.ts +5 -1
- package/templates/vue-sync/src/modules/todos/useTodos.ts +1 -1
- package/templates/vue-tailwind/AGENTS.md +84 -0
- package/templates/vue-tailwind/src/App.vue +1 -8
- package/templates/vue-tailwind/src/modules/todos/useTodos.ts +1 -1
- package/templates/vue-tailwind-sync/AGENTS.md +84 -0
- package/templates/vue-tailwind-sync/src/modules/todos/useTodos.ts +1 -1
- package/dist/chunk-VLTPEATY.js.map +0 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Guidance for AI coding agents working in this project. Humans: this is useful for you too.
|
|
4
|
+
|
|
5
|
+
## What this project is
|
|
6
|
+
|
|
7
|
+
This is a **Kora.js** application: an offline-first app where all data lives in a local SQLite database (WASM + OPFS in the browser) and optionally syncs across devices through a Kora sync server. Offline is the normal state, not an error state. Full docs: https://korajs.dev and machine-readable index at https://korajs.dev/llms.txt (complete docs in one file: https://korajs.dev/llms-full.txt).
|
|
8
|
+
|
|
9
|
+
## Golden rules
|
|
10
|
+
|
|
11
|
+
1. **The schema is the source of truth.** All collections are defined in `src/schema.ts` with `defineSchema` and the `t.*` field builders. To add or change data shapes, edit the schema first; types flow from it automatically. Never hand-write types for records.
|
|
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
|
+
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
|
+
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 swallow 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
|
+
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
|
+
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
|
+
|
|
19
|
+
## Data API cheat sheet
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
await app.ready
|
|
23
|
+
const rec = await app.todos.insert({ title: 'x' }) // defaults and .auto() fields applied
|
|
24
|
+
await app.todos.update(rec.id, { completed: true }) // partial update, changed fields only
|
|
25
|
+
await app.todos.delete(rec.id)
|
|
26
|
+
const one = await app.todos.findById(rec.id)
|
|
27
|
+
const unsubscribe = app.todos
|
|
28
|
+
.where({ completed: false })
|
|
29
|
+
.orderBy('createdAt', 'desc')
|
|
30
|
+
.subscribe((rows) => {/* fires immediately, then on every change */})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Schema example (`src/schema.ts`):
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { defineSchema, t } from 'korajs'
|
|
37
|
+
|
|
38
|
+
export default defineSchema({
|
|
39
|
+
version: 1,
|
|
40
|
+
collections: {
|
|
41
|
+
todos: {
|
|
42
|
+
fields: {
|
|
43
|
+
title: t.string(),
|
|
44
|
+
completed: t.boolean().default(false),
|
|
45
|
+
tags: t.array(t.string()).default([]),
|
|
46
|
+
priority: t.enum(['low', 'medium', 'high']).default('medium'),
|
|
47
|
+
createdAt: t.timestamp().auto(), // set automatically; never pass it on insert
|
|
48
|
+
},
|
|
49
|
+
indexes: ['completed', 'createdAt'],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
If you bump collections in a way that changes shapes, increment `version` and run `npx kora migrate`.
|
|
56
|
+
|
|
57
|
+
## Project conventions
|
|
58
|
+
|
|
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, add-wins for arrays). If a field needs domain-specific merging (counters, quantities), add a `resolve` function in the schema rather than writing sync logic.
|
|
61
|
+
- `kora.config.ts` controls the dev environment (ports, sync server, schema watcher).
|
|
62
|
+
|
|
63
|
+
## Commands
|
|
64
|
+
|
|
65
|
+
- `npm run dev` starts everything: Vite dev server (port 5173), local sync server (port 3001), and the schema watcher.
|
|
66
|
+
- `npm run build` type-checks and builds for production.
|
|
67
|
+
- `npx kora doctor` diagnoses a broken setup.
|
|
68
|
+
- DevTools overlay: press Ctrl+Shift+K (Cmd+Shift+K on macOS) in the running app to inspect operations, merges, and sync status.
|
|
69
|
+
|
|
70
|
+
## Sync and auth
|
|
71
|
+
|
|
72
|
+
Sync is configured in `src/main.*` 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
|
+
|
|
74
|
+
## React bindings
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { useCollection, useMutation, useQuery, useSyncStatus } from '@korajs/react'
|
|
78
|
+
|
|
79
|
+
const todos = useCollection('todos')
|
|
80
|
+
const rows = useQuery(orderedTodos(todos)) // reactive, no loading state for local data
|
|
81
|
+
const addTodo = useMutation((data) => todos.insert(data))
|
|
82
|
+
addTodo.mutate({ title: 'x' }) // fire-and-forget; errors land in addTodo.error
|
|
83
|
+
await addTodo.mutateAsync({ title: 'x' }) // throws on failure; use when you need the error
|
|
84
|
+
const status = useSyncStatus() // status.status, status.pendingOperations
|
|
85
|
+
```
|
|
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 data synchronously from the local store, so do not add loading spinners for local reads.
|
|
@@ -80,6 +80,8 @@ export function App() {
|
|
|
80
80
|
</div>
|
|
81
81
|
)}
|
|
82
82
|
|
|
83
|
+
<ClockErrorBanner status={status} />
|
|
84
|
+
|
|
83
85
|
{/* Stats */}
|
|
84
86
|
<div className="grid grid-cols-3 gap-4 mb-8">
|
|
85
87
|
<StatCard label="Total" value={allTodos.length} color="text-gray-300" />
|
|
@@ -216,6 +218,7 @@ function SyncBadge({ status }: { status: { status: string; pendingOperations?: n
|
|
|
216
218
|
synced: { icon: Wifi, color: 'text-emerald-400', label: 'Synced' },
|
|
217
219
|
offline: { icon: WifiOff, color: 'text-gray-500', label: 'Offline' },
|
|
218
220
|
error: { icon: AlertCircle, color: 'text-red-400', label: 'Error' },
|
|
221
|
+
'clock-error': { icon: AlertCircle, color: 'text-red-400', label: 'Clock error' },
|
|
219
222
|
}
|
|
220
223
|
|
|
221
224
|
const { icon: Icon, color, label } = config[s] ?? config.offline!
|
|
@@ -233,6 +236,31 @@ function SyncBadge({ status }: { status: { status: string; pendingOperations?: n
|
|
|
233
236
|
)
|
|
234
237
|
}
|
|
235
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Shown when sync is paused because this device's clock is set too far ahead.
|
|
241
|
+
* Local changes keep saving; they sync automatically once the clock is fixed.
|
|
242
|
+
*/
|
|
243
|
+
function ClockErrorBanner({
|
|
244
|
+
status,
|
|
245
|
+
}: {
|
|
246
|
+
status: { status: string; clockSkewMs?: number | null }
|
|
247
|
+
}) {
|
|
248
|
+
if (status.status !== 'clock-error') return null
|
|
249
|
+
const skew = status.clockSkewMs
|
|
250
|
+
const minutes =
|
|
251
|
+
typeof skew === 'number' && Number.isFinite(skew) ? Math.round(Math.abs(skew) / 60000) : null
|
|
252
|
+
return (
|
|
253
|
+
<div className="mb-4 rounded-lg border border-amber-400/40 bg-amber-400/10 px-4 py-3 text-sm text-amber-200">
|
|
254
|
+
<strong>
|
|
255
|
+
This device's clock looks wrong{minutes !== null ? ` (about ${minutes} min ahead)` : ''}.
|
|
256
|
+
</strong>{' '}
|
|
257
|
+
Your changes are safe on this device, but they can't be shared with your other devices until
|
|
258
|
+
the clock is corrected. Open your device's Settings, find Date & Time, and turn on "Set
|
|
259
|
+
automatically", then return here.
|
|
260
|
+
</div>
|
|
261
|
+
)
|
|
262
|
+
}
|
|
263
|
+
|
|
236
264
|
function StatCard({ label, value, color }: { label: string; value: number; color: string }) {
|
|
237
265
|
return (
|
|
238
266
|
<div className="rounded-lg border border-gray-800 bg-gray-900 p-4">
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Guidance for AI coding agents working in this project. Humans: this is useful for you too.
|
|
4
|
+
|
|
5
|
+
## What this project is
|
|
6
|
+
|
|
7
|
+
This is a **Kora.js** application: an offline-first app where all data lives in a local SQLite database (WASM + OPFS in the browser) and optionally syncs across devices through a Kora sync server. Offline is the normal state, not an error state. Full docs: https://korajs.dev and machine-readable index at https://korajs.dev/llms.txt (complete docs in one file: https://korajs.dev/llms-full.txt).
|
|
8
|
+
|
|
9
|
+
## Golden rules
|
|
10
|
+
|
|
11
|
+
1. **The schema is the source of truth.** All collections are defined in `src/schema.ts` with `defineSchema` and the `t.*` field builders. To add or change data shapes, edit the schema first; types flow from it automatically. Never hand-write types for records.
|
|
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
|
+
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
|
+
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 swallow 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
|
+
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
|
+
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
|
+
|
|
19
|
+
## Data API cheat sheet
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
await app.ready
|
|
23
|
+
const rec = await app.todos.insert({ title: 'x' }) // defaults and .auto() fields applied
|
|
24
|
+
await app.todos.update(rec.id, { completed: true }) // partial update, changed fields only
|
|
25
|
+
await app.todos.delete(rec.id)
|
|
26
|
+
const one = await app.todos.findById(rec.id)
|
|
27
|
+
const unsubscribe = app.todos
|
|
28
|
+
.where({ completed: false })
|
|
29
|
+
.orderBy('createdAt', 'desc')
|
|
30
|
+
.subscribe((rows) => {/* fires immediately, then on every change */})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Schema example (`src/schema.ts`):
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { defineSchema, t } from 'korajs'
|
|
37
|
+
|
|
38
|
+
export default defineSchema({
|
|
39
|
+
version: 1,
|
|
40
|
+
collections: {
|
|
41
|
+
todos: {
|
|
42
|
+
fields: {
|
|
43
|
+
title: t.string(),
|
|
44
|
+
completed: t.boolean().default(false),
|
|
45
|
+
tags: t.array(t.string()).default([]),
|
|
46
|
+
priority: t.enum(['low', 'medium', 'high']).default('medium'),
|
|
47
|
+
createdAt: t.timestamp().auto(), // set automatically; never pass it on insert
|
|
48
|
+
},
|
|
49
|
+
indexes: ['completed', 'createdAt'],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
If you bump collections in a way that changes shapes, increment `version` and run `npx kora migrate`.
|
|
56
|
+
|
|
57
|
+
## Project conventions
|
|
58
|
+
|
|
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, add-wins for arrays). If a field needs domain-specific merging (counters, quantities), add a `resolve` function in the schema rather than writing sync logic.
|
|
61
|
+
- `kora.config.ts` controls the dev environment (ports, sync server, schema watcher).
|
|
62
|
+
|
|
63
|
+
## Commands
|
|
64
|
+
|
|
65
|
+
- `npm run dev` starts everything: Vite dev server (port 5173), local sync server (port 3001), and the schema watcher.
|
|
66
|
+
- `npm run build` type-checks and builds for production.
|
|
67
|
+
- `npx kora doctor` diagnoses a broken setup.
|
|
68
|
+
- DevTools overlay: press Ctrl+Shift+K (Cmd+Shift+K on macOS) in the running app to inspect operations, merges, and sync status.
|
|
69
|
+
|
|
70
|
+
## Sync and auth
|
|
71
|
+
|
|
72
|
+
Sync is configured in `src/main.*` 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
|
+
|
|
74
|
+
## Svelte bindings
|
|
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 that read synchronously from the local database; mutations expose error state that must be surfaced in the UI. Avoid loading spinners for local reads.
|
|
@@ -1,44 +1,40 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
2
|
+
import { createTodosStores } from './modules/todos/useTodos'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
type Filter = 'all' | 'active' | 'completed'
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const { allTodos, activeTodos, completedTodos, addTodo, toggleTodo, deleteTodo } =
|
|
7
|
+
createTodosStores()
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const filter = $state<Filter>('all')
|
|
10
|
+
let input = $state('')
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
: filter === 'completed'
|
|
16
|
-
? $completedTodos
|
|
17
|
-
: $allTodos,
|
|
18
|
-
)
|
|
12
|
+
const filteredTodos = $derived(
|
|
13
|
+
filter === 'active' ? $activeTodos : filter === 'completed' ? $completedTodos : $allTodos,
|
|
14
|
+
)
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
function handleSubmit(event: SubmitEvent): void {
|
|
17
|
+
event.preventDefault()
|
|
18
|
+
const title = input.trim()
|
|
19
|
+
if (!title) return
|
|
20
|
+
addTodo.mutate({ title })
|
|
21
|
+
input = ''
|
|
22
|
+
}
|
|
27
23
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
24
|
+
function clearCompleted(): void {
|
|
25
|
+
for (const todo of $completedTodos) {
|
|
26
|
+
deleteTodo.mutate(todo.id)
|
|
32
27
|
}
|
|
28
|
+
}
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
return date.toLocaleDateString([], { month: 'short', day: 'numeric' })
|
|
30
|
+
function formatTime(timestamp: number): string {
|
|
31
|
+
const date = new Date(timestamp)
|
|
32
|
+
const now = new Date()
|
|
33
|
+
if (date.toDateString() === now.toDateString()) {
|
|
34
|
+
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
|
41
35
|
}
|
|
36
|
+
return date.toLocaleDateString([], { month: 'short', day: 'numeric' })
|
|
37
|
+
}
|
|
42
38
|
</script>
|
|
43
39
|
|
|
44
40
|
<div class="app">
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import type { KoraAppLike } from '@korajs/svelte'
|
|
3
|
+
import KoraProvider from '@korajs/svelte/KoraProvider.svelte'
|
|
4
|
+
import App from './App.svelte'
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
const { kora }: { kora: KoraAppLike } = $props()
|
|
7
7
|
</script>
|
|
8
8
|
|
|
9
9
|
<KoraProvider app={kora}>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
|
2
1
|
import { copyFileSync, existsSync, readdirSync } from 'node:fs'
|
|
3
2
|
import { join, resolve } from 'node:path'
|
|
3
|
+
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
|
4
4
|
import type { Plugin } from 'vite'
|
|
5
5
|
import { defineConfig } from 'vite'
|
|
6
6
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Guidance for AI coding agents working in this project. Humans: this is useful for you too.
|
|
4
|
+
|
|
5
|
+
## What this project is
|
|
6
|
+
|
|
7
|
+
This is a **Kora.js** application: an offline-first app where all data lives in a local SQLite database (WASM + OPFS in the browser) and optionally syncs across devices through a Kora sync server. Offline is the normal state, not an error state. Full docs: https://korajs.dev and machine-readable index at https://korajs.dev/llms.txt (complete docs in one file: https://korajs.dev/llms-full.txt).
|
|
8
|
+
|
|
9
|
+
## Golden rules
|
|
10
|
+
|
|
11
|
+
1. **The schema is the source of truth.** All collections are defined in `src/schema.ts` with `defineSchema` and the `t.*` field builders. To add or change data shapes, edit the schema first; types flow from it automatically. Never hand-write types for records.
|
|
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
|
+
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
|
+
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 swallow 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
|
+
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
|
+
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
|
+
|
|
19
|
+
## Data API cheat sheet
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
await app.ready
|
|
23
|
+
const rec = await app.todos.insert({ title: 'x' }) // defaults and .auto() fields applied
|
|
24
|
+
await app.todos.update(rec.id, { completed: true }) // partial update, changed fields only
|
|
25
|
+
await app.todos.delete(rec.id)
|
|
26
|
+
const one = await app.todos.findById(rec.id)
|
|
27
|
+
const unsubscribe = app.todos
|
|
28
|
+
.where({ completed: false })
|
|
29
|
+
.orderBy('createdAt', 'desc')
|
|
30
|
+
.subscribe((rows) => {/* fires immediately, then on every change */})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Schema example (`src/schema.ts`):
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { defineSchema, t } from 'korajs'
|
|
37
|
+
|
|
38
|
+
export default defineSchema({
|
|
39
|
+
version: 1,
|
|
40
|
+
collections: {
|
|
41
|
+
todos: {
|
|
42
|
+
fields: {
|
|
43
|
+
title: t.string(),
|
|
44
|
+
completed: t.boolean().default(false),
|
|
45
|
+
tags: t.array(t.string()).default([]),
|
|
46
|
+
priority: t.enum(['low', 'medium', 'high']).default('medium'),
|
|
47
|
+
createdAt: t.timestamp().auto(), // set automatically; never pass it on insert
|
|
48
|
+
},
|
|
49
|
+
indexes: ['completed', 'createdAt'],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
If you bump collections in a way that changes shapes, increment `version` and run `npx kora migrate`.
|
|
56
|
+
|
|
57
|
+
## Project conventions
|
|
58
|
+
|
|
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, add-wins for arrays). If a field needs domain-specific merging (counters, quantities), add a `resolve` function in the schema rather than writing sync logic.
|
|
61
|
+
- `kora.config.ts` controls the dev environment (ports, sync server, schema watcher).
|
|
62
|
+
|
|
63
|
+
## Commands
|
|
64
|
+
|
|
65
|
+
- `npm run dev` starts everything: Vite dev server (port 5173), local sync server (port 3001), and the schema watcher.
|
|
66
|
+
- `npm run build` type-checks and builds for production.
|
|
67
|
+
- `npx kora doctor` diagnoses a broken setup.
|
|
68
|
+
- DevTools overlay: press Ctrl+Shift+K (Cmd+Shift+K on macOS) in the running app to inspect operations, merges, and sync status.
|
|
69
|
+
|
|
70
|
+
## Sync and auth
|
|
71
|
+
|
|
72
|
+
Sync is configured in `src/main.*` 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
|
+
|
|
74
|
+
## Svelte bindings
|
|
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 that read synchronously from the local database; mutations expose error state that must be surfaced in the UI. Avoid loading spinners for local reads.
|
|
@@ -1,48 +1,44 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { createAuthStore } from '@korajs/auth/svelte'
|
|
3
|
+
import { createSyncStatusStore } from '@korajs/svelte'
|
|
4
|
+
import { createTodosStores } from './modules/todos/useTodos'
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
type Filter = 'all' | 'active' | 'completed'
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
const syncStatus = createSyncStatusStore()
|
|
9
|
+
const auth = createAuthStore()
|
|
10
|
+
const todos = createTodosStores()
|
|
11
|
+
const { allTodos, activeTodos, completedTodos, addTodo, toggleTodo, deleteTodo } = todos
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
const filter = $state<Filter>('all')
|
|
14
|
+
let input = $state('')
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
: filter === 'completed'
|
|
20
|
-
? $completedTodos
|
|
21
|
-
: $allTodos,
|
|
22
|
-
)
|
|
16
|
+
const filteredTodos = $derived(
|
|
17
|
+
filter === 'active' ? $activeTodos : filter === 'completed' ? $completedTodos : $allTodos,
|
|
18
|
+
)
|
|
23
19
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
function handleSubmit(event: SubmitEvent): void {
|
|
21
|
+
event.preventDefault()
|
|
22
|
+
const title = input.trim()
|
|
23
|
+
if (!title) return
|
|
24
|
+
addTodo.mutate({ title })
|
|
25
|
+
input = ''
|
|
26
|
+
}
|
|
31
27
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
28
|
+
function clearCompleted(): void {
|
|
29
|
+
for (const todo of $completedTodos) {
|
|
30
|
+
deleteTodo.mutate(todo.id)
|
|
36
31
|
}
|
|
32
|
+
}
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
return date.toLocaleDateString([], { month: 'short', day: 'numeric' })
|
|
34
|
+
function formatTime(timestamp: number): string {
|
|
35
|
+
const date = new Date(timestamp)
|
|
36
|
+
const now = new Date()
|
|
37
|
+
if (date.toDateString() === now.toDateString()) {
|
|
38
|
+
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
|
45
39
|
}
|
|
40
|
+
return date.toLocaleDateString([], { month: 'short', day: 'numeric' })
|
|
41
|
+
}
|
|
46
42
|
</script>
|
|
47
43
|
|
|
48
44
|
<div class="app">
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import type { AuthClient } from '@korajs/auth'
|
|
3
|
+
import AuthProvider from '@korajs/auth/svelte/AuthProvider.svelte'
|
|
4
|
+
import type { KoraAppLike } from '@korajs/svelte'
|
|
5
|
+
import KoraProvider from '@korajs/svelte/KoraProvider.svelte'
|
|
6
|
+
import App from './App.svelte'
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const { kora, authClient }: { kora: KoraAppLike; authClient: AuthClient } = $props()
|
|
9
9
|
</script>
|
|
10
10
|
|
|
11
11
|
<AuthProvider client={authClient}>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Guidance for AI coding agents working in this project. Humans: this is useful for you too.
|
|
4
|
+
|
|
5
|
+
## What this project is
|
|
6
|
+
|
|
7
|
+
This is a **Kora.js** application: an offline-first app where all data lives in a local SQLite database (WASM + OPFS in the browser) and optionally syncs across devices through a Kora sync server. Offline is the normal state, not an error state. Full docs: https://korajs.dev and machine-readable index at https://korajs.dev/llms.txt (complete docs in one file: https://korajs.dev/llms-full.txt).
|
|
8
|
+
|
|
9
|
+
## Golden rules
|
|
10
|
+
|
|
11
|
+
1. **The schema is the source of truth.** All collections are defined in `src/schema.ts` with `defineSchema` and the `t.*` field builders. To add or change data shapes, edit the schema first; types flow from it automatically. Never hand-write types for records.
|
|
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
|
+
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
|
+
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 swallow 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
|
+
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
|
+
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
|
+
|
|
19
|
+
## Data API cheat sheet
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
await app.ready
|
|
23
|
+
const rec = await app.todos.insert({ title: 'x' }) // defaults and .auto() fields applied
|
|
24
|
+
await app.todos.update(rec.id, { completed: true }) // partial update, changed fields only
|
|
25
|
+
await app.todos.delete(rec.id)
|
|
26
|
+
const one = await app.todos.findById(rec.id)
|
|
27
|
+
const unsubscribe = app.todos
|
|
28
|
+
.where({ completed: false })
|
|
29
|
+
.orderBy('createdAt', 'desc')
|
|
30
|
+
.subscribe((rows) => {/* fires immediately, then on every change */})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Schema example (`src/schema.ts`):
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { defineSchema, t } from 'korajs'
|
|
37
|
+
|
|
38
|
+
export default defineSchema({
|
|
39
|
+
version: 1,
|
|
40
|
+
collections: {
|
|
41
|
+
todos: {
|
|
42
|
+
fields: {
|
|
43
|
+
title: t.string(),
|
|
44
|
+
completed: t.boolean().default(false),
|
|
45
|
+
tags: t.array(t.string()).default([]),
|
|
46
|
+
priority: t.enum(['low', 'medium', 'high']).default('medium'),
|
|
47
|
+
createdAt: t.timestamp().auto(), // set automatically; never pass it on insert
|
|
48
|
+
},
|
|
49
|
+
indexes: ['completed', 'createdAt'],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
If you bump collections in a way that changes shapes, increment `version` and run `npx kora migrate`.
|
|
56
|
+
|
|
57
|
+
## Project conventions
|
|
58
|
+
|
|
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, add-wins for arrays). If a field needs domain-specific merging (counters, quantities), add a `resolve` function in the schema rather than writing sync logic.
|
|
61
|
+
- `kora.config.ts` controls the dev environment (ports, sync server, schema watcher).
|
|
62
|
+
|
|
63
|
+
## Commands
|
|
64
|
+
|
|
65
|
+
- `npm run dev` starts everything: Vite dev server (port 5173), local sync server (port 3001), and the schema watcher.
|
|
66
|
+
- `npm run build` type-checks and builds for production.
|
|
67
|
+
- `npx kora doctor` diagnoses a broken setup.
|
|
68
|
+
- DevTools overlay: press Ctrl+Shift+K (Cmd+Shift+K on macOS) in the running app to inspect operations, merges, and sync status.
|
|
69
|
+
|
|
70
|
+
## Sync and auth
|
|
71
|
+
|
|
72
|
+
Sync is configured in `src/main.*` 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
|
+
|
|
74
|
+
## Svelte bindings
|
|
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 that read synchronously from the local database; mutations expose error state that must be surfaced in the UI. Avoid loading spinners for local reads.
|
|
@@ -1,30 +1,26 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { CheckCircle2, Circle, ClipboardList, Plus, Trash2 } from '@lucide/svelte'
|
|
3
|
+
import { createTodosStores } from './modules/todos/useTodos'
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
type Filter = 'all' | 'active' | 'completed'
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
const { allTodos, activeTodos, completedTodos, addTodo, toggleTodo, deleteTodo } =
|
|
8
|
+
createTodosStores()
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
const filter = $state<Filter>('all')
|
|
11
|
+
let input = $state('')
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
: filter === 'completed'
|
|
17
|
-
? $completedTodos
|
|
18
|
-
: $allTodos,
|
|
19
|
-
)
|
|
13
|
+
const filteredTodos = $derived(
|
|
14
|
+
filter === 'active' ? $activeTodos : filter === 'completed' ? $completedTodos : $allTodos,
|
|
15
|
+
)
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
17
|
+
function handleSubmit(event: SubmitEvent): void {
|
|
18
|
+
event.preventDefault()
|
|
19
|
+
const title = input.trim()
|
|
20
|
+
if (!title) return
|
|
21
|
+
addTodo.mutate({ title })
|
|
22
|
+
input = ''
|
|
23
|
+
}
|
|
28
24
|
</script>
|
|
29
25
|
|
|
30
26
|
<div class="min-h-screen bg-gray-950 text-gray-100">
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import type { KoraAppLike } from '@korajs/svelte'
|
|
3
|
+
import KoraProvider from '@korajs/svelte/KoraProvider.svelte'
|
|
4
|
+
import App from './App.svelte'
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
const { kora }: { kora: KoraAppLike } = $props()
|
|
7
7
|
</script>
|
|
8
8
|
|
|
9
9
|
<KoraProvider app={kora}>
|