@kuratchi/js 0.0.21 → 0.0.23

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';
@@ -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
+ }
@@ -392,14 +392,22 @@ function __attachCookies(response) {
392
392
  const csrfCookie = getCsrfCookieHeader();
393
393
  const hasCookies = (cookies && cookies.length > 0) || csrfCookie;
394
394
  if (hasCookies) {
395
- const newResponse = new Response(response.body, response);
395
+ // Clone the response properly to avoid body stream issues with WARP/proxy layers.
396
+ // Using response.clone() ensures the body stream is properly duplicated.
397
+ const cloned = response.clone();
398
+ const newHeaders = new Headers(cloned.headers);
396
399
  if (cookies) {
397
400
  for (const header of cookies)
398
- newResponse.headers.append('Set-Cookie', header);
401
+ newHeaders.append('Set-Cookie', header);
399
402
  }
400
403
  if (csrfCookie) {
401
- newResponse.headers.append('Set-Cookie', csrfCookie);
404
+ newHeaders.append('Set-Cookie', csrfCookie);
402
405
  }
406
+ const newResponse = new Response(cloned.body, {
407
+ status: cloned.status,
408
+ statusText: cloned.statusText,
409
+ headers: newHeaders,
410
+ });
403
411
  return __secHeaders(newResponse);
404
412
  }
405
413
  return __secHeaders(response);
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@kuratchi/js",
3
- "version": "0.0.21",
3
+ "version": "0.0.23",
4
4
  "description": "A thin, Cloudflare Workers-native web framework with Svelte-inspired syntax",
5
5
  "license": "MIT",
6
6
  "type": "module",