@korajs/cli 0.5.0 → 0.6.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/dist/bin.cjs +76 -7
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +3 -3
- package/dist/{chunk-PSRM56B7.js → chunk-EEZNRI5W.js} +51 -6
- package/dist/chunk-EEZNRI5W.js.map +1 -0
- package/dist/{chunk-SOTZIWIF.js → chunk-Q2FBCOQD.js} +2 -2
- package/dist/{chunk-3WNFM3QB.js → chunk-VLTPEATY.js} +70 -4
- package/dist/chunk-VLTPEATY.js.map +1 -0
- package/dist/create.cjs +76 -7
- package/dist/create.cjs.map +1 -1
- package/dist/create.js +2 -2
- package/dist/index.cjs +65 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/package.json +6 -6
- package/templates/svelte-basic/.env.example +3 -0
- package/templates/svelte-basic/README.md.hbs +62 -0
- package/templates/svelte-basic/index.html.hbs +12 -0
- package/templates/svelte-basic/kora.config.ts +12 -0
- package/templates/svelte-basic/package.json.hbs +27 -0
- package/templates/svelte-basic/src/App.svelte +131 -0
- package/templates/svelte-basic/src/Root.svelte +14 -0
- package/templates/svelte-basic/src/index.css +287 -0
- package/templates/svelte-basic/src/kora-worker.ts +9 -0
- package/templates/svelte-basic/src/main.ts +16 -0
- package/templates/svelte-basic/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/svelte-basic/src/modules/todos/todo.queries.ts +5 -0
- package/templates/svelte-basic/src/modules/todos/todo.schema.ts +10 -0
- package/templates/svelte-basic/src/modules/todos/useTodos.ts +25 -0
- package/templates/svelte-basic/src/schema.ts +9 -0
- package/templates/svelte-basic/src/vite-env.d.ts +12 -0
- package/templates/svelte-basic/svelte.config.js +5 -0
- package/templates/svelte-basic/tsconfig.json.hbs +14 -0
- package/templates/svelte-basic/vite.config.ts +52 -0
- package/templates/svelte-sync/.env.example +25 -0
- package/templates/svelte-sync/README.md.hbs +105 -0
- package/templates/svelte-sync/index.html.hbs +12 -0
- package/templates/svelte-sync/kora.config.ts +17 -0
- package/templates/svelte-sync/package.json.hbs +43 -0
- package/templates/svelte-sync/server.ts +90 -0
- package/templates/svelte-sync/src/App.svelte +154 -0
- package/templates/svelte-sync/src/Root.svelte +22 -0
- package/templates/svelte-sync/src/auth.ts +25 -0
- package/templates/svelte-sync/src/index.css +339 -0
- package/templates/svelte-sync/src/kora-worker.ts +9 -0
- package/templates/svelte-sync/src/main.ts +32 -0
- package/templates/svelte-sync/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/svelte-sync/src/modules/todos/todo.queries.ts +5 -0
- package/templates/svelte-sync/src/modules/todos/todo.schema.ts +10 -0
- package/templates/svelte-sync/src/modules/todos/useTodos.ts +25 -0
- package/templates/svelte-sync/src/schema.ts +9 -0
- package/templates/svelte-sync/src/vite-env.d.ts +21 -0
- package/templates/svelte-sync/svelte.config.js +5 -0
- package/templates/svelte-sync/tsconfig.json.hbs +14 -0
- package/templates/svelte-sync/vite.config.ts +79 -0
- package/templates/svelte-tailwind/README.md.hbs +62 -0
- package/templates/svelte-tailwind/index.html.hbs +12 -0
- package/templates/svelte-tailwind/kora.config.ts +12 -0
- package/templates/svelte-tailwind/package.json.hbs +30 -0
- package/templates/svelte-tailwind/src/App.svelte +79 -0
- package/templates/svelte-tailwind/src/Root.svelte +14 -0
- package/templates/svelte-tailwind/src/index.css +7 -0
- package/templates/svelte-tailwind/src/kora-worker.ts +9 -0
- package/templates/svelte-tailwind/src/main.ts +16 -0
- package/templates/svelte-tailwind/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/svelte-tailwind/src/modules/todos/todo.queries.ts +5 -0
- package/templates/svelte-tailwind/src/modules/todos/todo.schema.ts +10 -0
- package/templates/svelte-tailwind/src/modules/todos/useTodos.ts +25 -0
- package/templates/svelte-tailwind/src/schema.ts +9 -0
- package/templates/svelte-tailwind/src/vite-env.d.ts +12 -0
- package/templates/svelte-tailwind/svelte.config.js +5 -0
- package/templates/svelte-tailwind/tsconfig.json.hbs +14 -0
- package/templates/svelte-tailwind/vite.config.ts +53 -0
- package/templates/svelte-tailwind-sync/.env.example +25 -0
- package/templates/svelte-tailwind-sync/README.md.hbs +105 -0
- package/templates/svelte-tailwind-sync/index.html.hbs +12 -0
- package/templates/svelte-tailwind-sync/kora.config.ts +17 -0
- package/templates/svelte-tailwind-sync/package.json.hbs +46 -0
- package/templates/svelte-tailwind-sync/server.ts +90 -0
- package/templates/svelte-tailwind-sync/src/App.svelte +196 -0
- package/templates/svelte-tailwind-sync/src/Root.svelte +26 -0
- package/templates/svelte-tailwind-sync/src/auth.ts +25 -0
- package/templates/svelte-tailwind-sync/src/index.css +7 -0
- package/templates/svelte-tailwind-sync/src/kora-worker.ts +9 -0
- package/templates/svelte-tailwind-sync/src/main.ts +32 -0
- package/templates/svelte-tailwind-sync/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/svelte-tailwind-sync/src/modules/todos/todo.queries.ts +5 -0
- package/templates/svelte-tailwind-sync/src/modules/todos/todo.schema.ts +10 -0
- package/templates/svelte-tailwind-sync/src/modules/todos/useTodos.ts +25 -0
- package/templates/svelte-tailwind-sync/src/schema.ts +9 -0
- package/templates/svelte-tailwind-sync/src/vite-env.d.ts +21 -0
- package/templates/svelte-tailwind-sync/svelte.config.js +5 -0
- package/templates/svelte-tailwind-sync/tsconfig.json.hbs +14 -0
- package/templates/svelte-tailwind-sync/vite.config.ts +80 -0
- package/templates/vue-basic/README.md.hbs +62 -0
- package/templates/vue-basic/index.html.hbs +12 -0
- package/templates/vue-basic/kora.config.ts +12 -0
- package/templates/vue-basic/package.json.hbs +26 -0
- package/templates/vue-basic/src/App.vue +117 -0
- package/templates/vue-basic/src/index.css +287 -0
- package/templates/vue-basic/src/kora-worker.ts +9 -0
- package/templates/vue-basic/src/main.ts +20 -0
- package/templates/vue-basic/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/vue-basic/src/modules/todos/todo.queries.ts +5 -0
- package/templates/vue-basic/src/modules/todos/todo.schema.ts +10 -0
- package/templates/vue-basic/src/modules/todos/useTodos.ts +32 -0
- package/templates/vue-basic/src/schema.ts +9 -0
- package/templates/vue-basic/src/vite-env.d.ts +17 -0
- package/templates/vue-basic/tsconfig.json.hbs +15 -0
- package/templates/vue-basic/vite.config.ts +69 -0
- package/templates/vue-sync/.env.example +25 -0
- package/templates/vue-sync/README.md.hbs +105 -0
- package/templates/vue-sync/index.html.hbs +12 -0
- package/templates/vue-sync/kora.config.ts +17 -0
- package/templates/vue-sync/package.json.hbs +42 -0
- package/templates/vue-sync/server.ts +90 -0
- package/templates/vue-sync/src/App.vue +135 -0
- package/templates/vue-sync/src/auth.ts +25 -0
- package/templates/vue-sync/src/index.css +339 -0
- package/templates/vue-sync/src/kora-worker.ts +9 -0
- package/templates/vue-sync/src/main.ts +43 -0
- package/templates/vue-sync/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/vue-sync/src/modules/todos/todo.queries.ts +5 -0
- package/templates/vue-sync/src/modules/todos/todo.schema.ts +10 -0
- package/templates/vue-sync/src/modules/todos/useTodos.ts +32 -0
- package/templates/vue-sync/src/schema.ts +9 -0
- package/templates/vue-sync/src/vite-env.d.ts +26 -0
- package/templates/vue-sync/tsconfig.json.hbs +15 -0
- package/templates/vue-sync/vite.config.ts +79 -0
- package/templates/vue-tailwind/README.md.hbs +62 -0
- package/templates/vue-tailwind/index.html.hbs +12 -0
- package/templates/vue-tailwind/kora.config.ts +12 -0
- package/templates/vue-tailwind/package.json.hbs +29 -0
- package/templates/vue-tailwind/src/App.vue +142 -0
- package/templates/vue-tailwind/src/index.css +7 -0
- package/templates/vue-tailwind/src/kora-worker.ts +9 -0
- package/templates/vue-tailwind/src/main.ts +31 -0
- package/templates/vue-tailwind/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/vue-tailwind/src/modules/todos/todo.queries.ts +5 -0
- package/templates/vue-tailwind/src/modules/todos/todo.schema.ts +10 -0
- package/templates/vue-tailwind/src/modules/todos/useTodos.ts +32 -0
- package/templates/vue-tailwind/src/schema.ts +9 -0
- package/templates/vue-tailwind/src/vite-env.d.ts +17 -0
- package/templates/vue-tailwind/tsconfig.json.hbs +15 -0
- package/templates/vue-tailwind/vite.config.ts +70 -0
- package/templates/vue-tailwind-sync/.env.example +25 -0
- package/templates/vue-tailwind-sync/README.md.hbs +105 -0
- package/templates/vue-tailwind-sync/index.html.hbs +12 -0
- package/templates/vue-tailwind-sync/kora.config.ts +17 -0
- package/templates/vue-tailwind-sync/package.json.hbs +45 -0
- package/templates/vue-tailwind-sync/server.ts +90 -0
- package/templates/vue-tailwind-sync/src/App.vue +245 -0
- package/templates/vue-tailwind-sync/src/auth.ts +25 -0
- package/templates/vue-tailwind-sync/src/index.css +7 -0
- package/templates/vue-tailwind-sync/src/kora-worker.ts +9 -0
- package/templates/vue-tailwind-sync/src/main.ts +56 -0
- package/templates/vue-tailwind-sync/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/vue-tailwind-sync/src/modules/todos/todo.queries.ts +5 -0
- package/templates/vue-tailwind-sync/src/modules/todos/todo.schema.ts +10 -0
- package/templates/vue-tailwind-sync/src/modules/todos/useTodos.ts +32 -0
- package/templates/vue-tailwind-sync/src/schema.ts +9 -0
- package/templates/vue-tailwind-sync/src/vite-env.d.ts +26 -0
- package/templates/vue-tailwind-sync/tsconfig.json.hbs +15 -0
- package/templates/vue-tailwind-sync/vite.config.ts +80 -0
- package/dist/chunk-3WNFM3QB.js.map +0 -1
- package/dist/chunk-PSRM56B7.js.map +0 -1
- /package/dist/{chunk-SOTZIWIF.js.map → chunk-Q2FBCOQD.js.map} +0 -0
package/dist/index.d.cts
CHANGED
|
@@ -5,7 +5,7 @@ import * as citty from 'citty';
|
|
|
5
5
|
declare const PACKAGE_MANAGERS: readonly ["pnpm", "npm", "yarn", "bun"];
|
|
6
6
|
type PackageManager = (typeof PACKAGE_MANAGERS)[number];
|
|
7
7
|
/** Available project templates */
|
|
8
|
-
declare const TEMPLATES: readonly ["react-tailwind-sync", "react-tailwind", "react-sync", "react-basic", "tauri-react"];
|
|
8
|
+
declare const TEMPLATES: readonly ["react-tailwind-sync", "react-tailwind", "react-sync", "react-basic", "vue-sync", "vue-basic", "vue-tailwind-sync", "vue-tailwind", "svelte-sync", "svelte-basic", "svelte-tailwind-sync", "svelte-tailwind", "tauri-react"];
|
|
9
9
|
type TemplateName = (typeof TEMPLATES)[number];
|
|
10
10
|
/** Metadata for a project template */
|
|
11
11
|
interface TemplateInfo {
|
|
@@ -556,13 +556,13 @@ type DatabaseOption = 'none' | 'sqlite' | 'postgres';
|
|
|
556
556
|
type DatabaseProviderOption = 'none' | 'local' | 'supabase' | 'neon' | 'railway' | 'vercel-postgres' | 'custom';
|
|
557
557
|
interface TemplateSelectionInput {
|
|
558
558
|
platform: PlatformOption;
|
|
559
|
+
framework: FrameworkOption;
|
|
559
560
|
tailwind: boolean;
|
|
560
561
|
sync: boolean;
|
|
561
562
|
db: DatabaseOption;
|
|
562
563
|
}
|
|
563
564
|
/**
|
|
564
|
-
* Converts high-level scaffold selections into
|
|
565
|
-
* concrete template names.
|
|
565
|
+
* Converts high-level scaffold selections into a concrete template name.
|
|
566
566
|
*/
|
|
567
567
|
declare function determineTemplateFromSelections(input: TemplateSelectionInput): TemplateName;
|
|
568
568
|
declare function isFrameworkValue(value: string): value is FrameworkOption;
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import * as citty from 'citty';
|
|
|
5
5
|
declare const PACKAGE_MANAGERS: readonly ["pnpm", "npm", "yarn", "bun"];
|
|
6
6
|
type PackageManager = (typeof PACKAGE_MANAGERS)[number];
|
|
7
7
|
/** Available project templates */
|
|
8
|
-
declare const TEMPLATES: readonly ["react-tailwind-sync", "react-tailwind", "react-sync", "react-basic", "tauri-react"];
|
|
8
|
+
declare const TEMPLATES: readonly ["react-tailwind-sync", "react-tailwind", "react-sync", "react-basic", "vue-sync", "vue-basic", "vue-tailwind-sync", "vue-tailwind", "svelte-sync", "svelte-basic", "svelte-tailwind-sync", "svelte-tailwind", "tauri-react"];
|
|
9
9
|
type TemplateName = (typeof TEMPLATES)[number];
|
|
10
10
|
/** Metadata for a project template */
|
|
11
11
|
interface TemplateInfo {
|
|
@@ -556,13 +556,13 @@ type DatabaseOption = 'none' | 'sqlite' | 'postgres';
|
|
|
556
556
|
type DatabaseProviderOption = 'none' | 'local' | 'supabase' | 'neon' | 'railway' | 'vercel-postgres' | 'custom';
|
|
557
557
|
interface TemplateSelectionInput {
|
|
558
558
|
platform: PlatformOption;
|
|
559
|
+
framework: FrameworkOption;
|
|
559
560
|
tailwind: boolean;
|
|
560
561
|
sync: boolean;
|
|
561
562
|
db: DatabaseOption;
|
|
562
563
|
}
|
|
563
564
|
/**
|
|
564
|
-
* Converts high-level scaffold selections into
|
|
565
|
-
* concrete template names.
|
|
565
|
+
* Converts high-level scaffold selections into a concrete template name.
|
|
566
566
|
*/
|
|
567
567
|
declare function determineTemplateFromSelections(input: TemplateSelectionInput): TemplateName;
|
|
568
568
|
declare function isFrameworkValue(value: string): value is FrameworkOption;
|
package/dist/index.js
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
writeDockerfileArtifact,
|
|
28
28
|
writeFlyTomlArtifact,
|
|
29
29
|
writeRailwayJsonArtifact
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-Q2FBCOQD.js";
|
|
31
31
|
import {
|
|
32
32
|
ClackPromptClient,
|
|
33
33
|
CliError,
|
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
saveResolvedPreferences,
|
|
55
55
|
shouldSavePreferences,
|
|
56
56
|
validateProjectName
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-VLTPEATY.js";
|
|
58
58
|
export {
|
|
59
59
|
ClackPromptClient,
|
|
60
60
|
CliError,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@korajs/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Kora.js CLI tooling and project scaffolding",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"conf": "^15.1.0",
|
|
37
37
|
"esbuild": "^0.28.0",
|
|
38
38
|
"validate-npm-package-name": "^7.0.2",
|
|
39
|
-
"@korajs/core": "0.
|
|
39
|
+
"@korajs/core": "0.6.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/better-sqlite3": "^7.6.13",
|
|
@@ -46,10 +46,10 @@
|
|
|
46
46
|
"tsup": "^8.3.6",
|
|
47
47
|
"typescript": "^5.7.3",
|
|
48
48
|
"vitest": "^3.0.4",
|
|
49
|
-
"@korajs/
|
|
50
|
-
"@korajs/
|
|
51
|
-
"@korajs/
|
|
52
|
-
"korajs": "0.
|
|
49
|
+
"@korajs/merge": "0.6.0",
|
|
50
|
+
"@korajs/store": "0.6.0",
|
|
51
|
+
"@korajs/sync": "0.6.0",
|
|
52
|
+
"korajs": "0.6.0"
|
|
53
53
|
},
|
|
54
54
|
"license": "MIT",
|
|
55
55
|
"scripts": {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# {{projectName}}
|
|
2
|
+
|
|
3
|
+
An offline-first application built with [Kora.js](https://ehoneahobed.github.io/kora/).
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run dev
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- **App**: http://localhost:5173
|
|
12
|
+
|
|
13
|
+
## Project Structure
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
src/
|
|
17
|
+
schema.ts # Schema entry point
|
|
18
|
+
main.tsx # App initialization and Kora setup
|
|
19
|
+
App.tsx # Main application component
|
|
20
|
+
modules/
|
|
21
|
+
todos/
|
|
22
|
+
todo.schema.ts # Todo collection definition
|
|
23
|
+
todo.queries.ts # Reusable todo query builders
|
|
24
|
+
todo.mutations.ts # Todo mutation helpers
|
|
25
|
+
useTodos.ts # React hook connecting todos to the UI
|
|
26
|
+
kora-worker.ts # SQLite WASM web worker
|
|
27
|
+
index.css # Styles
|
|
28
|
+
kora.config.ts # Kora configuration
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
In a feature module, `schema` defines data shape, `queries` contain reads only, `mutations` contain
|
|
32
|
+
writes, and `useTodos.ts` is the React binding that exposes todo data and actions to components.
|
|
33
|
+
|
|
34
|
+
## Scripts
|
|
35
|
+
|
|
36
|
+
| Script | Description |
|
|
37
|
+
|--------|-------------|
|
|
38
|
+
| `npm run dev` | Start dev server |
|
|
39
|
+
| `npm run build` | Build for production |
|
|
40
|
+
| `npm run preview` | Preview production build |
|
|
41
|
+
|
|
42
|
+
## Adding Sync
|
|
43
|
+
|
|
44
|
+
To enable real-time sync across devices, update `src/main.tsx`:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
const app = createApp({
|
|
48
|
+
schema,
|
|
49
|
+
sync: {
|
|
50
|
+
url: import.meta.env.VITE_SYNC_URL || 'ws://localhost:3001',
|
|
51
|
+
},
|
|
52
|
+
// ...
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Then create a `server.ts` and install `@korajs/server`. See the [Sync Configuration guide](https://ehoneahobed.github.io/kora/guide/sync-configuration).
|
|
57
|
+
|
|
58
|
+
## Learn More
|
|
59
|
+
|
|
60
|
+
- [Documentation](https://ehoneahobed.github.io/kora/)
|
|
61
|
+
- [Storage Configuration](https://ehoneahobed.github.io/kora/guide/storage-configuration)
|
|
62
|
+
- [Schema Design](https://ehoneahobed.github.io/kora/guide/schema-design)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>{{projectName}}</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app"></div>
|
|
10
|
+
<script type="module" src="/src/main.ts"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "kora dev",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"check": "svelte-check --tsconfig ./tsconfig.json",
|
|
10
|
+
"preview": "vite preview"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"korajs": "{{koraVersion}}",
|
|
14
|
+
"@korajs/svelte": "{{koraVersion}}",
|
|
15
|
+
"@korajs/store": "{{koraVersion}}",
|
|
16
|
+
"@sqlite.org/sqlite-wasm": ">=3.51.0-build1",
|
|
17
|
+
"svelte": "^5.0.0",
|
|
18
|
+
"yjs": "^13.6.30"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@korajs/cli": "{{koraVersion}}",
|
|
22
|
+
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
|
23
|
+
"svelte-check": "^4.1.0",
|
|
24
|
+
"typescript": "^5.7.0",
|
|
25
|
+
"vite": "^6.0.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { createTodosStores } from './modules/todos/useTodos'
|
|
3
|
+
|
|
4
|
+
type Filter = 'all' | 'active' | 'completed'
|
|
5
|
+
|
|
6
|
+
const { allTodos, activeTodos, completedTodos, addTodo, toggleTodo, deleteTodo } =
|
|
7
|
+
createTodosStores()
|
|
8
|
+
|
|
9
|
+
let filter = $state<Filter>('all')
|
|
10
|
+
let input = $state('')
|
|
11
|
+
|
|
12
|
+
const filteredTodos = $derived(
|
|
13
|
+
filter === 'active'
|
|
14
|
+
? $activeTodos
|
|
15
|
+
: filter === 'completed'
|
|
16
|
+
? $completedTodos
|
|
17
|
+
: $allTodos,
|
|
18
|
+
)
|
|
19
|
+
|
|
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
|
+
}
|
|
27
|
+
|
|
28
|
+
function clearCompleted(): void {
|
|
29
|
+
for (const todo of $completedTodos) {
|
|
30
|
+
deleteTodo.mutate(todo.id)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
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' })
|
|
39
|
+
}
|
|
40
|
+
return date.toLocaleDateString([], { month: 'short', day: 'numeric' })
|
|
41
|
+
}
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<div class="app">
|
|
45
|
+
<div class="header">
|
|
46
|
+
<h1>My Tasks</h1>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div class="stats">
|
|
50
|
+
<div class="stat-card">
|
|
51
|
+
<div class="label">Total</div>
|
|
52
|
+
<div class="value muted">{$allTodos.length}</div>
|
|
53
|
+
</div>
|
|
54
|
+
<div class="stat-card">
|
|
55
|
+
<div class="label">Remaining</div>
|
|
56
|
+
<div class="value warning">{$activeTodos.length}</div>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="stat-card">
|
|
59
|
+
<div class="label">Done</div>
|
|
60
|
+
<div class="value success">{$completedTodos.length}</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<form class="add-form" onsubmit={handleSubmit}>
|
|
65
|
+
<input bind:value={input} type="text" placeholder="What needs to be done?" />
|
|
66
|
+
<button type="submit" disabled={!input.trim()}>Add</button>
|
|
67
|
+
</form>
|
|
68
|
+
|
|
69
|
+
<div class="filters">
|
|
70
|
+
{#each ['all', 'active', 'completed'] as const as f}
|
|
71
|
+
<button
|
|
72
|
+
type="button"
|
|
73
|
+
class="filter-btn {filter === f ? 'active' : ''}"
|
|
74
|
+
onclick={() => (filter = f)}
|
|
75
|
+
>
|
|
76
|
+
{f.charAt(0).toUpperCase() + f.slice(1)}
|
|
77
|
+
<span class="badge">
|
|
78
|
+
{f === 'all'
|
|
79
|
+
? $allTodos.length
|
|
80
|
+
: f === 'active'
|
|
81
|
+
? $activeTodos.length
|
|
82
|
+
: $completedTodos.length}
|
|
83
|
+
</span>
|
|
84
|
+
</button>
|
|
85
|
+
{/each}
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div class="todo-list">
|
|
89
|
+
{#if filteredTodos.length === 0}
|
|
90
|
+
<div class="empty-state">
|
|
91
|
+
{#if filter === 'all'}
|
|
92
|
+
No tasks yet. Add one above!
|
|
93
|
+
{:else if filter === 'active'}
|
|
94
|
+
All caught up! No active tasks.
|
|
95
|
+
{:else}
|
|
96
|
+
No completed tasks yet.
|
|
97
|
+
{/if}
|
|
98
|
+
</div>
|
|
99
|
+
{:else}
|
|
100
|
+
{#each filteredTodos as todo (todo.id)}
|
|
101
|
+
<div class="todo-item">
|
|
102
|
+
<button
|
|
103
|
+
type="button"
|
|
104
|
+
class="toggle {todo.completed ? 'checked' : ''}"
|
|
105
|
+
onclick={() => toggleTodo.mutate(todo.id, { completed: !todo.completed })}
|
|
106
|
+
>
|
|
107
|
+
{todo.completed ? '✓' : ''}
|
|
108
|
+
</button>
|
|
109
|
+
<span class="title {todo.completed ? 'done' : ''}">{String(todo.title)}</span>
|
|
110
|
+
{#if todo.createdAt}
|
|
111
|
+
<span class="time">{formatTime(Number(todo.createdAt))}</span>
|
|
112
|
+
{/if}
|
|
113
|
+
<button type="button" class="delete-btn" onclick={() => deleteTodo.mutate(todo.id)}>
|
|
114
|
+
×
|
|
115
|
+
</button>
|
|
116
|
+
</div>
|
|
117
|
+
{/each}
|
|
118
|
+
{/if}
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
{#if $allTodos.length > 0}
|
|
122
|
+
<div class="footer">
|
|
123
|
+
<span>{$activeTodos.length} item{$activeTodos.length !== 1 ? 's' : ''} left</span>
|
|
124
|
+
{#if $completedTodos.length > 0}
|
|
125
|
+
<button type="button" onclick={clearCompleted}>Clear completed</button>
|
|
126
|
+
{/if}
|
|
127
|
+
</div>
|
|
128
|
+
{/if}
|
|
129
|
+
|
|
130
|
+
<p class="branding">Powered by Kora — offline-first</p>
|
|
131
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { KoraAppLike } from '@korajs/svelte'
|
|
3
|
+
import KoraProvider from '@korajs/svelte/KoraProvider.svelte'
|
|
4
|
+
import App from './App.svelte'
|
|
5
|
+
|
|
6
|
+
let { kora }: { kora: KoraAppLike } = $props()
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<KoraProvider app={kora}>
|
|
10
|
+
{#snippet fallback()}
|
|
11
|
+
<div>Loading...</div>
|
|
12
|
+
{/snippet}
|
|
13
|
+
<App />
|
|
14
|
+
</KoraProvider>
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
*,
|
|
2
|
+
*::before,
|
|
3
|
+
*::after {
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
margin: 0;
|
|
6
|
+
padding: 0;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
:root {
|
|
10
|
+
--color-bg: #0a0a0f;
|
|
11
|
+
--color-surface: #111118;
|
|
12
|
+
--color-border: #1e1e2a;
|
|
13
|
+
--color-border-hover: #2a2a3a;
|
|
14
|
+
--color-text: #e4e4e7;
|
|
15
|
+
--color-text-muted: #71717a;
|
|
16
|
+
--color-text-dim: #3f3f46;
|
|
17
|
+
--color-primary: #6366f1;
|
|
18
|
+
--color-primary-hover: #818cf8;
|
|
19
|
+
--color-success: #34d399;
|
|
20
|
+
--color-warning: #fbbf24;
|
|
21
|
+
--color-danger: #f87171;
|
|
22
|
+
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
23
|
+
--radius: 8px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
body {
|
|
27
|
+
font-family: var(--font-sans);
|
|
28
|
+
background: var(--color-bg);
|
|
29
|
+
color: var(--color-text);
|
|
30
|
+
line-height: 1.6;
|
|
31
|
+
-webkit-font-smoothing: antialiased;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.app {
|
|
35
|
+
max-width: 640px;
|
|
36
|
+
margin: 0 auto;
|
|
37
|
+
padding: 48px 16px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.header {
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
justify-content: space-between;
|
|
44
|
+
margin-bottom: 32px;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.header h1 {
|
|
48
|
+
font-size: 1.5rem;
|
|
49
|
+
font-weight: 700;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.local-badge {
|
|
53
|
+
display: inline-flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
gap: 6px;
|
|
56
|
+
padding: 4px 12px;
|
|
57
|
+
border-radius: 999px;
|
|
58
|
+
background: var(--color-surface);
|
|
59
|
+
font-size: 0.8rem;
|
|
60
|
+
color: var(--color-text-muted);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.stats {
|
|
64
|
+
display: grid;
|
|
65
|
+
grid-template-columns: repeat(3, 1fr);
|
|
66
|
+
gap: 12px;
|
|
67
|
+
margin-bottom: 32px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.stat-card {
|
|
71
|
+
background: var(--color-surface);
|
|
72
|
+
border: 1px solid var(--color-border);
|
|
73
|
+
border-radius: var(--radius);
|
|
74
|
+
padding: 16px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.stat-card .label {
|
|
78
|
+
font-size: 0.8rem;
|
|
79
|
+
color: var(--color-text-muted);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.stat-card .value {
|
|
83
|
+
font-size: 1.5rem;
|
|
84
|
+
font-weight: 700;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.stat-card .value.muted {
|
|
88
|
+
color: var(--color-text-muted);
|
|
89
|
+
}
|
|
90
|
+
.stat-card .value.warning {
|
|
91
|
+
color: var(--color-warning);
|
|
92
|
+
}
|
|
93
|
+
.stat-card .value.success {
|
|
94
|
+
color: var(--color-success);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.add-form {
|
|
98
|
+
display: flex;
|
|
99
|
+
gap: 12px;
|
|
100
|
+
margin-bottom: 32px;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.add-form input {
|
|
104
|
+
flex: 1;
|
|
105
|
+
padding: 10px 16px;
|
|
106
|
+
border: 1px solid var(--color-border);
|
|
107
|
+
border-radius: var(--radius);
|
|
108
|
+
background: var(--color-surface);
|
|
109
|
+
color: var(--color-text);
|
|
110
|
+
font-size: 0.95rem;
|
|
111
|
+
outline: none;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.add-form input:focus {
|
|
115
|
+
border-color: var(--color-primary);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.add-form input::placeholder {
|
|
119
|
+
color: var(--color-text-muted);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.add-form button {
|
|
123
|
+
padding: 10px 20px;
|
|
124
|
+
border: none;
|
|
125
|
+
border-radius: var(--radius);
|
|
126
|
+
background: var(--color-primary);
|
|
127
|
+
color: white;
|
|
128
|
+
font-size: 0.95rem;
|
|
129
|
+
font-weight: 500;
|
|
130
|
+
cursor: pointer;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.add-form button:hover {
|
|
134
|
+
background: var(--color-primary-hover);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.add-form button:disabled {
|
|
138
|
+
opacity: 0.5;
|
|
139
|
+
cursor: not-allowed;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.filters {
|
|
143
|
+
display: flex;
|
|
144
|
+
gap: 8px;
|
|
145
|
+
margin-bottom: 24px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.filter-btn {
|
|
149
|
+
padding: 6px 16px;
|
|
150
|
+
border: none;
|
|
151
|
+
border-radius: var(--radius);
|
|
152
|
+
background: var(--color-surface);
|
|
153
|
+
color: var(--color-text-muted);
|
|
154
|
+
font-size: 0.85rem;
|
|
155
|
+
font-weight: 500;
|
|
156
|
+
cursor: pointer;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.filter-btn:hover {
|
|
160
|
+
background: var(--color-border-hover);
|
|
161
|
+
color: var(--color-text);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.filter-btn.active {
|
|
165
|
+
background: var(--color-primary);
|
|
166
|
+
color: white;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.filter-btn .badge {
|
|
170
|
+
display: inline-block;
|
|
171
|
+
margin-left: 6px;
|
|
172
|
+
padding: 1px 6px;
|
|
173
|
+
border-radius: 999px;
|
|
174
|
+
font-size: 0.75rem;
|
|
175
|
+
background: rgba(255, 255, 255, 0.15);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.filter-btn:not(.active) .badge {
|
|
179
|
+
background: var(--color-border);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.todo-list {
|
|
183
|
+
display: flex;
|
|
184
|
+
flex-direction: column;
|
|
185
|
+
gap: 8px;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.todo-item {
|
|
189
|
+
display: flex;
|
|
190
|
+
align-items: center;
|
|
191
|
+
gap: 12px;
|
|
192
|
+
padding: 12px 16px;
|
|
193
|
+
border: 1px solid var(--color-border);
|
|
194
|
+
border-radius: var(--radius);
|
|
195
|
+
background: var(--color-surface);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.todo-item:hover {
|
|
199
|
+
border-color: var(--color-border-hover);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.todo-item .toggle {
|
|
203
|
+
background: none;
|
|
204
|
+
border: 2px solid var(--color-text-muted);
|
|
205
|
+
width: 20px;
|
|
206
|
+
height: 20px;
|
|
207
|
+
border-radius: 50%;
|
|
208
|
+
cursor: pointer;
|
|
209
|
+
display: flex;
|
|
210
|
+
align-items: center;
|
|
211
|
+
justify-content: center;
|
|
212
|
+
color: transparent;
|
|
213
|
+
font-size: 0.7rem;
|
|
214
|
+
flex-shrink: 0;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.todo-item .toggle.checked {
|
|
218
|
+
border-color: var(--color-success);
|
|
219
|
+
background: var(--color-success);
|
|
220
|
+
color: white;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.todo-item .title {
|
|
224
|
+
flex: 1;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.todo-item .title.done {
|
|
228
|
+
text-decoration: line-through;
|
|
229
|
+
color: var(--color-text-muted);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.todo-item .time {
|
|
233
|
+
font-size: 0.75rem;
|
|
234
|
+
color: var(--color-text-dim);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.todo-item .delete-btn {
|
|
238
|
+
background: none;
|
|
239
|
+
border: none;
|
|
240
|
+
color: var(--color-text-dim);
|
|
241
|
+
cursor: pointer;
|
|
242
|
+
padding: 4px;
|
|
243
|
+
opacity: 0;
|
|
244
|
+
transition: opacity 0.15s;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.todo-item:hover .delete-btn {
|
|
248
|
+
opacity: 1;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.todo-item .delete-btn:hover {
|
|
252
|
+
color: var(--color-danger);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.empty-state {
|
|
256
|
+
padding: 48px 0;
|
|
257
|
+
text-align: center;
|
|
258
|
+
color: var(--color-text-dim);
|
|
259
|
+
border: 1px dashed var(--color-border);
|
|
260
|
+
border-radius: var(--radius);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.footer {
|
|
264
|
+
display: flex;
|
|
265
|
+
justify-content: space-between;
|
|
266
|
+
margin-top: 24px;
|
|
267
|
+
font-size: 0.85rem;
|
|
268
|
+
color: var(--color-text-muted);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.footer button {
|
|
272
|
+
background: none;
|
|
273
|
+
border: none;
|
|
274
|
+
color: var(--color-text-muted);
|
|
275
|
+
cursor: pointer;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.footer button:hover {
|
|
279
|
+
color: var(--color-text);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.branding {
|
|
283
|
+
margin-top: 48px;
|
|
284
|
+
text-align: center;
|
|
285
|
+
font-size: 0.75rem;
|
|
286
|
+
color: var(--color-text-dim);
|
|
287
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Web Worker entry point for SQLite WASM.
|
|
2
|
+
// Loaded automatically by the Kora store to run SQLite in a background thread.
|
|
3
|
+
|
|
4
|
+
// Import the WASM binary URL so Vite resolves it with the correct content hash.
|
|
5
|
+
// Without this, production builds fail because sqlite3 looks for the unhashed filename.
|
|
6
|
+
import sqliteWasmUrl from '@sqlite.org/sqlite-wasm/sqlite3.wasm?url'
|
|
7
|
+
;(globalThis as Record<string, unknown>).__KORA_SQLITE_WASM_URL = sqliteWasmUrl
|
|
8
|
+
|
|
9
|
+
import '@korajs/store/sqlite-wasm/worker'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createApp as createKoraApp } from 'korajs'
|
|
2
|
+
import { mount } from 'svelte'
|
|
3
|
+
import Root from './Root.svelte'
|
|
4
|
+
import schema from './schema'
|
|
5
|
+
import './index.css'
|
|
6
|
+
import koraWorkerUrl from './kora-worker.ts?worker&url'
|
|
7
|
+
|
|
8
|
+
const kora = createKoraApp({
|
|
9
|
+
schema,
|
|
10
|
+
store: {
|
|
11
|
+
workerUrl: koraWorkerUrl,
|
|
12
|
+
},
|
|
13
|
+
devtools: true,
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
mount(Root, { target: document.getElementById('app')!, props: { kora } })
|