@kuratchi/js 0.0.20 → 0.0.22

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/create.js CHANGED
@@ -192,11 +192,11 @@ function scaffold(dir, opts) {
192
192
  if (orm || enableDO) {
193
193
  dirs.push('src/schemas');
194
194
  }
195
- if (orm) {
196
- dirs.push('src/database');
195
+ if (orm || enableDO || auth) {
196
+ dirs.push('src/server');
197
197
  }
198
198
  if (enableDO) {
199
- dirs.push('src/server', 'src/routes/notes');
199
+ dirs.push('src/routes/notes');
200
200
  }
201
201
  if (auth) {
202
202
  dirs.push('src/routes/auth', 'src/routes/auth/login', 'src/routes/auth/signup', 'src/routes/admin');
@@ -214,19 +214,19 @@ function scaffold(dir, opts) {
214
214
  write(dir, 'src/routes/index.html', genLandingPage(opts));
215
215
  if (orm) {
216
216
  write(dir, 'src/schemas/app.ts', genSchema(opts));
217
- write(dir, 'src/database/items.ts', genItemsCrud());
217
+ write(dir, 'src/server/items.ts', genItemsCrud());
218
218
  write(dir, 'src/routes/items/index.html', genItemsPage());
219
219
  }
220
220
  if (enableDO) {
221
221
  write(dir, 'src/schemas/notes.ts', genNotesSchema());
222
222
  write(dir, 'src/server/notes.do.ts', genNotesDoHandler());
223
- write(dir, 'src/database/notes.ts', genNotesDb());
223
+ write(dir, 'src/server/notes.ts', genNotesDb());
224
224
  write(dir, 'src/routes/notes/index.html', genNotesPage());
225
225
  }
226
226
  if (auth) {
227
227
  write(dir, '.dev.vars', genDevVars());
228
- write(dir, 'src/database/auth.ts', genAuthFunctions());
229
- write(dir, 'src/database/admin.ts', genAdminLoader());
228
+ write(dir, 'src/server/auth.ts', genAuthFunctions());
229
+ write(dir, 'src/server/admin.ts', genAdminLoader());
230
230
  write(dir, 'src/routes/auth/login/index.html', genLoginPage());
231
231
  write(dir, 'src/routes/auth/signup/index.html', genSignupPage());
232
232
  write(dir, 'src/routes/admin/index.html', genAdminPage());
@@ -449,7 +449,7 @@ export async function deleteNote(id: number): Promise<void> {
449
449
  }
450
450
  function genNotesPage() {
451
451
  return `<script>
452
- import { getNotes, addNote, deleteNote } from '$database/notes';
452
+ import { getNotes, addNote, deleteNote } from '$server/notes';
453
453
 
454
454
  const notes = await getNotes();
455
455
  </script>
@@ -473,7 +473,7 @@ if (notes.length === 0) {
473
473
  for (const note of notes) {
474
474
  <article>
475
475
  <span>{note.title}</span>
476
- <button data-action="deleteNote" data-args={JSON.stringify([note.id])}>Remove</button>
476
+ <button data-post={deleteNote(note.id)} data-refresh="" type="button">Remove</button>
477
477
  </article>
478
478
  }
479
479
  </section>
@@ -631,10 +631,10 @@ ${types}
631
631
  `;
632
632
  }
633
633
  function genItemsCrud() {
634
- return `import { env } from 'cloudflare:workers';
635
- import { kuratchiORM } from '@kuratchi/orm';
636
- import { redirect } from '${FRAMEWORK_PACKAGE_NAME}';
637
- import type { Item } from './schemas/app';
634
+ return `import { env } from 'cloudflare:workers';
635
+ import { kuratchiORM } from '@kuratchi/orm';
636
+ import { redirect } from '${FRAMEWORK_PACKAGE_NAME}';
637
+ import type { Item } from '../schemas/app';
638
638
 
639
639
  const db = kuratchiORM(() => (env as any).DB);
640
640
 
@@ -664,7 +664,7 @@ export async function toggleItem(id: number): Promise<void> {
664
664
  }
665
665
  function genItemsPage() {
666
666
  return `<script>
667
- import { getItems, addItem, deleteItem, toggleItem } from '$database/items';
667
+ import { getItems, addItem, deleteItem, toggleItem } from '$server/items';
668
668
  import EmptyState from '@kuratchi/ui/empty-state.html';
669
669
 
670
670
  const items = await getItems();
@@ -690,10 +690,10 @@ if (items.length === 0) {
690
690
  <article>
691
691
  <span style={item.done ? 'text-decoration: line-through; opacity: 0.5' : ''}>{item.title}</span>
692
692
  <div>
693
- <button data-action="toggleItem" data-args={JSON.stringify([item.id])}>
693
+ <button data-post={toggleItem(item.id)} data-refresh="" type="button">
694
694
  {item.done ? '↩' : '✓'}
695
695
  </button>
696
- <button data-action="deleteItem" data-args={JSON.stringify([item.id])}>✕</button>
696
+ <button data-post={deleteItem(item.id)} data-refresh="" type="button">✕</button>
697
697
  </div>
698
698
  </article>
699
699
  }
@@ -905,7 +905,7 @@ export async function getAdminData() {
905
905
  }
906
906
  function genLoginPage() {
907
907
  return `<script>
908
- import { signIn } from '$database/auth';
908
+ import { signIn } from '$server/auth';
909
909
  import AuthCard from '@kuratchi/ui/auth-card.html';
910
910
  </script>
911
911
 
@@ -933,7 +933,7 @@ function genLoginPage() {
933
933
  }
934
934
  function genSignupPage() {
935
935
  return `<script>
936
- import { signUp } from '$database/auth';
936
+ import { signUp } from '$server/auth';
937
937
  import AuthCard from '@kuratchi/ui/auth-card.html';
938
938
  </script>
939
939
 
@@ -965,7 +965,7 @@ function genSignupPage() {
965
965
  }
966
966
  function genAdminPage() {
967
967
  return `<script>
968
- import { getAdminData, signOut } from '$database/admin';
968
+ import { getAdminData, signOut } from '$server/admin';
969
969
  import Badge from '@kuratchi/ui/badge.html';
970
970
  import Card from '@kuratchi/ui/card.html';
971
971
  import DataList from '@kuratchi/ui/data-list.html';
package/dist/index.d.ts CHANGED
@@ -12,7 +12,7 @@ export { SchemaValidationError, schema, } from './runtime/schema.js';
12
12
  export { ActionError } from './runtime/action.js';
13
13
  export { PageError } from './runtime/page-error.js';
14
14
  export { extractSubdomainSlug, extractSlugFromPrefix, matchContainerViewPath, rewriteProxyLocationHeader, buildContainerRequest, createContainerEnvVars, startContainer, proxyToContainer, handleContainerRouting, forwardJsonPostToContainerDO, matchSiteViewPath, buildSiteContainerRequest, createWpContainerEnvVars, startSiteContainer, proxyToSiteContainer, } from './runtime/containers.js';
15
- export type { AppConfig, kuratchiConfig, DatabaseConfig, AuthConfig, RouteContext, RouteModule, RuntimeContext, RuntimeDefinition, RuntimeStep, RuntimeNext, RuntimeErrorResult, } from './runtime/types.js';
15
+ export type { AppConfig, kuratchiConfig, DatabaseConfig, DesktopConfig, DesktopRemoteBindingConfig, DesktopWindowConfig, AuthConfig, RouteContext, RouteModule, RuntimeContext, RuntimeDefinition, RuntimeStep, RuntimeNext, RuntimeErrorResult, } from './runtime/types.js';
16
16
  export type { RpcOf } from './runtime/do.js';
17
17
  export type { SchemaType, InferSchema } from './runtime/schema.js';
18
18
  export { url, pathname, searchParams, headers, method, params, slug } from './runtime/request.js';
@@ -43,7 +43,9 @@ export declare function getParam(name: string): string | undefined;
43
43
  * Throws a redirect signal consumed by the framework's PRG flow.
44
44
  */
45
45
  export declare function redirect(path: string, status?: number): never;
46
- /** Backward-compatible alias for redirect() */
46
+ /**
47
+ * @deprecated Use redirect() instead. This alias will be removed in a future version.
48
+ */
47
49
  export declare function goto(path: string, status?: number): never;
48
50
  export declare function setBreadcrumbs(items: BreadcrumbItem[]): void;
49
51
  export declare function getBreadcrumbs(): BreadcrumbItem[];
@@ -104,7 +104,9 @@ export function redirect(path, status = 303) {
104
104
  __locals.__redirectStatus = status;
105
105
  throw new RedirectError(path, status);
106
106
  }
107
- /** Backward-compatible alias for redirect() */
107
+ /**
108
+ * @deprecated Use redirect() instead. This alias will be removed in a future version.
109
+ */
108
110
  export function goto(path, status = 303) {
109
111
  redirect(path, status);
110
112
  }
@@ -0,0 +1,19 @@
1
+ export interface DesktopNotificationPayload {
2
+ title: string;
3
+ body?: string;
4
+ }
5
+ export interface DesktopCommandRequest {
6
+ command: string;
7
+ workingDirectory?: string;
8
+ timeoutMs?: number;
9
+ }
10
+ export interface DesktopCommandResult {
11
+ ok: boolean;
12
+ error?: string | null;
13
+ exitCode: number;
14
+ durationMs: number;
15
+ stdout: string;
16
+ stderr: string;
17
+ }
18
+ export declare function showDesktopNotification(payload: DesktopNotificationPayload): Promise<boolean>;
19
+ export declare function runDesktopCommand(request: DesktopCommandRequest): Promise<DesktopCommandResult | null>;
@@ -0,0 +1,82 @@
1
+ import { getRequest } from './context.js';
2
+ function parseCookie(header, name) {
3
+ if (!header)
4
+ return null;
5
+ for (const part of header.split(';')) {
6
+ const [rawKey, ...rest] = part.split('=');
7
+ if ((rawKey ?? '').trim() !== name)
8
+ continue;
9
+ return rest.join('=').trim() || null;
10
+ }
11
+ return null;
12
+ }
13
+ function getDesktopApiOrigin() {
14
+ const request = getRequest();
15
+ const headerOrigin = request.headers.get('x-kuratchi-desktop-api-origin')
16
+ || request.headers.get('x-kuratchi-desktop-origin');
17
+ if (headerOrigin)
18
+ return headerOrigin;
19
+ const cookieOrigin = parseCookie(request.headers.get('cookie'), '__kuratchi_desktop_api');
20
+ if (!cookieOrigin)
21
+ return null;
22
+ try {
23
+ return decodeURIComponent(cookieOrigin);
24
+ }
25
+ catch {
26
+ return cookieOrigin;
27
+ }
28
+ }
29
+ export async function showDesktopNotification(payload) {
30
+ const title = payload.title?.trim();
31
+ if (!title) {
32
+ throw new Error('showDesktopNotification requires a title.');
33
+ }
34
+ const desktopApiOrigin = getDesktopApiOrigin();
35
+ if (!desktopApiOrigin) {
36
+ return false;
37
+ }
38
+ const response = await fetch(new URL('/notifications/show', desktopApiOrigin), {
39
+ method: 'POST',
40
+ headers: {
41
+ 'content-type': 'application/json',
42
+ },
43
+ body: JSON.stringify({
44
+ title,
45
+ body: payload.body ?? '',
46
+ }),
47
+ });
48
+ if (response.status === 404) {
49
+ return false;
50
+ }
51
+ if (!response.ok) {
52
+ console.warn(`[kuratchi] Desktop notification request failed with status ${response.status}.`);
53
+ return false;
54
+ }
55
+ const payloadResult = await response.json().catch(() => null);
56
+ return payloadResult?.ok === true;
57
+ }
58
+ export async function runDesktopCommand(request) {
59
+ const command = request.command?.trim();
60
+ if (!command) {
61
+ throw new Error('runDesktopCommand requires a command.');
62
+ }
63
+ const desktopApiOrigin = getDesktopApiOrigin();
64
+ if (!desktopApiOrigin) {
65
+ return null;
66
+ }
67
+ const response = await fetch(new URL('/commands/run', desktopApiOrigin), {
68
+ method: 'POST',
69
+ headers: {
70
+ 'content-type': 'application/json',
71
+ },
72
+ body: JSON.stringify({
73
+ command,
74
+ workingDirectory: request.workingDirectory ?? null,
75
+ timeoutMs: request.timeoutMs ?? 30000,
76
+ }),
77
+ });
78
+ if (!response.ok) {
79
+ throw new Error(`[kuratchi] Desktop command request failed with status ${response.status}.`);
80
+ }
81
+ return await response.json();
82
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Navigation helpers for kuratchi routes.
3
+ * Import via: import { redirect } from 'kuratchi:navigation';
4
+ *
5
+ * redirect() is server-side only — works in route scripts, server modules, and form actions.
6
+ * It throws a RedirectError that the framework catches and converts to a 303 redirect response.
7
+ */
8
+ export { redirect } from './context.js';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Navigation helpers for kuratchi routes.
3
+ * Import via: import { redirect } from 'kuratchi:navigation';
4
+ *
5
+ * redirect() is server-side only — works in route scripts, server modules, and form actions.
6
+ * It throws a RedirectError that the framework catches and converts to a 303 redirect response.
7
+ */
8
+ export { redirect } from './context.js';
@@ -1,3 +1,11 @@
1
+ declare global {
2
+ namespace App {
3
+ /** Request-scoped locals set by runtime hooks. Extend in your app.d.ts */
4
+ interface Locals {
5
+ [key: string]: unknown;
6
+ }
7
+ }
8
+ }
1
9
  export declare let url: URL;
2
10
  export declare let pathname: string;
3
11
  export declare let searchParams: URLSearchParams;
@@ -5,5 +13,25 @@ export declare let headers: Headers;
5
13
  export declare let method: string;
6
14
  export declare let params: Record<string, string>;
7
15
  export declare let slug: string | undefined;
16
+ /**
17
+ * Get request-scoped locals with full type safety.
18
+ * Define your Locals type in src/app.d.ts:
19
+ * ```
20
+ * declare global {
21
+ * namespace App {
22
+ * interface Locals {
23
+ * userId: number;
24
+ * userEmail: string;
25
+ * }
26
+ * }
27
+ * }
28
+ * ```
29
+ */
30
+ export declare function getLocals(): App.Locals;
31
+ /**
32
+ * Direct access to request-scoped locals.
33
+ * Type is inferred from App.Locals declared in your app.d.ts.
34
+ */
35
+ export declare const locals: App.Locals;
8
36
  export declare function __setRequestState(request: Request): void;
9
37
  export declare function __setRequestParams(nextParams: Record<string, string> | null | undefined): void;
@@ -1,3 +1,4 @@
1
+ import { __getLocals } from './context.js';
1
2
  export let url = new URL('http://localhost/');
2
3
  export let pathname = '/';
3
4
  export let searchParams = url.searchParams;
@@ -5,6 +6,49 @@ export let headers = new Headers();
5
6
  export let method = 'GET';
6
7
  export let params = {};
7
8
  export let slug = undefined;
9
+ /**
10
+ * Get request-scoped locals with full type safety.
11
+ * Define your Locals type in src/app.d.ts:
12
+ * ```
13
+ * declare global {
14
+ * namespace App {
15
+ * interface Locals {
16
+ * userId: number;
17
+ * userEmail: string;
18
+ * }
19
+ * }
20
+ * }
21
+ * ```
22
+ */
23
+ export function getLocals() {
24
+ return __getLocals();
25
+ }
26
+ /**
27
+ * Direct access to request-scoped locals.
28
+ * Type is inferred from App.Locals declared in your app.d.ts.
29
+ */
30
+ export const locals = new Proxy({}, {
31
+ get(_target, prop) {
32
+ return __getLocals()[prop];
33
+ },
34
+ set(_target, prop, value) {
35
+ __getLocals()[prop] = value;
36
+ return true;
37
+ },
38
+ has(_target, prop) {
39
+ return prop in __getLocals();
40
+ },
41
+ ownKeys() {
42
+ return Reflect.ownKeys(__getLocals());
43
+ },
44
+ getOwnPropertyDescriptor(_target, prop) {
45
+ const locals = __getLocals();
46
+ if (prop in locals) {
47
+ return { configurable: true, enumerable: true, value: locals[prop] };
48
+ }
49
+ return undefined;
50
+ },
51
+ });
8
52
  function __syncDerivedState() {
9
53
  pathname = url.pathname;
10
54
  searchParams = url.searchParams;
@@ -78,6 +78,35 @@ export interface DatabaseConfig {
78
78
  type?: 'd1' | 'do';
79
79
  /** Skip migrations for this database (e.g., in production). Default: false */
80
80
  skipMigrations?: boolean;
81
+ /** Use the deployed Cloudflare resource during local development. */
82
+ remote?: boolean;
83
+ }
84
+ export interface DesktopWindowConfig {
85
+ title?: string;
86
+ width?: number;
87
+ height?: number;
88
+ }
89
+ export interface DesktopRemoteBindingConfig {
90
+ type: 'd1' | 'r2';
91
+ remote?: boolean;
92
+ }
93
+ export interface DesktopConfig {
94
+ /** Human-readable app name shown by the desktop host. */
95
+ appName?: string;
96
+ /** Stable app identifier used by the host/runtime. */
97
+ appId?: string;
98
+ /** Initial route loaded by the host. Defaults to '/'. */
99
+ initialPath?: string;
100
+ /** Single-window host settings. */
101
+ window?: DesktopWindowConfig;
102
+ /** Desktop-native bindings exposed by the host. */
103
+ bindings?: {
104
+ notifications?: boolean;
105
+ files?: boolean;
106
+ [key: string]: any;
107
+ };
108
+ /** Remote Cloudflare bindings the desktop runtime should proxy. */
109
+ remoteBindings?: Record<string, DesktopRemoteBindingConfig>;
81
110
  }
82
111
  /**
83
112
  * Framework configuration â€" the user-facing config file (kuratchi.config.ts)
@@ -108,6 +137,12 @@ export interface kuratchiConfig<E extends Env = Env> {
108
137
  ui?: {
109
138
  /** Theme to inject: 'default' uses @kuratchi/ui's built-in theme, or a path to a custom CSS file */
110
139
  theme?: 'default' | string;
140
+ /** Corner radius preference used by the built-in UI helpers. */
141
+ radius?: 'default' | 'none' | 'full';
142
+ /** Optional first-party styling library integration. */
143
+ library?: 'tailwindcss';
144
+ /** Optional plugin list for the selected UI library. */
145
+ plugins?: string[];
111
146
  };
112
147
  /** Auth configuration â€" @kuratchi/auth plugin setup */
113
148
  auth?: AuthConfig | Record<string, any>;
@@ -137,6 +172,8 @@ export interface kuratchiConfig<E extends Env = Env> {
137
172
  /** DO source files (e.g. ['auth.do.ts', 'sites.do.ts']) */
138
173
  files?: string[];
139
174
  }>;
175
+ /** Desktop target configuration consumed by `kuratchi run`. */
176
+ desktop?: DesktopConfig;
140
177
  }
141
178
  /** Auth configuration for kuratchi.config.ts */
142
179
  export interface AuthConfig {
package/package.json CHANGED
@@ -1,72 +1,76 @@
1
- {
2
- "name": "@kuratchi/js",
3
- "version": "0.0.20",
4
- "description": "A thin, Cloudflare Workers-native web framework with Svelte-inspired syntax",
5
- "license": "MIT",
6
- "type": "module",
7
- "main": "./dist/index.js",
8
- "types": "./dist/index.d.ts",
9
- "bin": {
10
- "kuratchi": "dist/cli.js"
11
- },
12
- "files": [
13
- "dist",
14
- "README.md",
15
- "LICENSE"
16
- ],
17
- "scripts": {
18
- "build": "tsc -p tsconfig.build.json",
19
- "check": "tsc -p tsconfig.build.json --noEmit",
20
- "test": "bun test",
21
- "test:watch": "bun test --watch",
22
- "prepublishOnly": "npm run build"
23
- },
24
- "exports": {
25
- ".": {
26
- "types": "./dist/index.d.ts",
27
- "import": "./dist/index.js"
28
- },
29
- "./runtime/context.js": {
30
- "types": "./dist/runtime/context.d.ts",
31
- "import": "./dist/runtime/context.js"
32
- },
33
- "./request": {
34
- "types": "./dist/runtime/request.d.ts",
35
- "import": "./dist/runtime/request.js"
36
- },
37
- "./runtime/do.js": {
38
- "types": "./dist/runtime/do.d.ts",
39
- "import": "./dist/runtime/do.js"
40
- },
41
- "./runtime/schema.js": {
42
- "types": "./dist/runtime/schema.d.ts",
43
- "import": "./dist/runtime/schema.js"
44
- },
45
- "./runtime/generated-worker.js": {
46
- "types": "./dist/runtime/generated-worker.d.ts",
47
- "import": "./dist/runtime/generated-worker.js"
48
- },
49
- "./compiler": {
50
- "types": "./dist/compiler/index.d.ts",
51
- "import": "./dist/compiler/index.js"
52
- },
53
- "./environment": {
54
- "types": "./dist/index.d.ts",
55
- "import": "./dist/index.js"
56
- },
57
- "./package.json": "./package.json"
58
- },
59
- "engines": {
60
- "node": ">=18"
61
- },
62
- "publishConfig": {
63
- "access": "public"
64
- },
65
- "dependencies": {
66
- "typescript": "^5.8.0"
67
- },
68
- "devDependencies": {
69
- "@cloudflare/workers-types": "^4.20260223.0",
70
- "@types/node": "^24.4.0"
71
- }
72
- }
1
+ {
2
+ "name": "@kuratchi/js",
3
+ "version": "0.0.22",
4
+ "description": "A thin, Cloudflare Workers-native web framework with Svelte-inspired syntax",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "bin": {
10
+ "kuratchi": "dist/cli.js"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc -p tsconfig.build.json",
19
+ "check": "tsc -p tsconfig.build.json --noEmit",
20
+ "test": "bun test",
21
+ "test:watch": "bun test --watch",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js"
28
+ },
29
+ "./runtime/context.js": {
30
+ "types": "./dist/runtime/context.d.ts",
31
+ "import": "./dist/runtime/context.js"
32
+ },
33
+ "./request": {
34
+ "types": "./dist/runtime/request.d.ts",
35
+ "import": "./dist/runtime/request.js"
36
+ },
37
+ "./navigation": {
38
+ "types": "./dist/runtime/navigation.d.ts",
39
+ "import": "./dist/runtime/navigation.js"
40
+ },
41
+ "./runtime/do.js": {
42
+ "types": "./dist/runtime/do.d.ts",
43
+ "import": "./dist/runtime/do.js"
44
+ },
45
+ "./runtime/schema.js": {
46
+ "types": "./dist/runtime/schema.d.ts",
47
+ "import": "./dist/runtime/schema.js"
48
+ },
49
+ "./runtime/generated-worker.js": {
50
+ "types": "./dist/runtime/generated-worker.d.ts",
51
+ "import": "./dist/runtime/generated-worker.js"
52
+ },
53
+ "./compiler": {
54
+ "types": "./dist/compiler/index.d.ts",
55
+ "import": "./dist/compiler/index.js"
56
+ },
57
+ "./environment": {
58
+ "types": "./dist/index.d.ts",
59
+ "import": "./dist/index.js"
60
+ },
61
+ "./package.json": "./package.json"
62
+ },
63
+ "engines": {
64
+ "node": ">=18"
65
+ },
66
+ "publishConfig": {
67
+ "access": "public"
68
+ },
69
+ "dependencies": {
70
+ "typescript": "^5.8.0"
71
+ },
72
+ "devDependencies": {
73
+ "@cloudflare/workers-types": "^4.20260223.0",
74
+ "@types/node": "^24.4.0"
75
+ }
76
+ }