@donkeylabs/adapter-sveltekit 2.0.6 → 2.0.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donkeylabs/adapter-sveltekit",
3
- "version": "2.0.6",
3
+ "version": "2.0.9",
4
4
  "type": "module",
5
5
  "description": "SvelteKit adapter for @donkeylabs/server - seamless SSR/browser API integration",
6
6
  "main": "./src/index.ts",
@@ -55,4 +55,4 @@
55
55
  "directory": "packages/adapter-sveltekit"
56
56
  },
57
57
  "license": "MIT"
58
- }
58
+ }
@@ -33,7 +33,7 @@ function isRouteInfo(route: RouteInfo | ExtractedRoute): route is RouteInfo {
33
33
  /** SvelteKit-specific generator options */
34
34
  export const svelteKitGeneratorOptions: ClientGeneratorOptions = {
35
35
  baseImport:
36
- 'import { UnifiedApiClientBase, SSEConnection, type ClientOptions, type RequestOptions } from "@donkeylabs/adapter-sveltekit/client";',
36
+ 'import { UnifiedApiClientBase, SSEConnection, type ClientOptions, type RequestOptions, type SSEConnectionOptions } from "@donkeylabs/adapter-sveltekit/client";',
37
37
  baseClass: "UnifiedApiClientBase",
38
38
  constructorSignature: "options?: ClientOptions",
39
39
  constructorBody: "super(options);",
@@ -178,7 +178,7 @@ function generateTypedSvelteKitClient(routes: RouteInfo[]): string {
178
178
  const outputType = `Routes.${pascalNs}.${pascalRoute}.Output`;
179
179
  // Use original route name with prefix for the request
180
180
  const fullRouteName = commonPrefix ? `${commonPrefix}.${r.name}` : r.name;
181
- return ` ${methodName}: (input: ${inputType}): Promise<${outputType}> => this.request("${fullRouteName}", input)`;
181
+ return ` ${methodName}: (input: ${inputType}, options?: RequestOptions): Promise<${outputType}> => this.request("${fullRouteName}", input, options)`;
182
182
  });
183
183
 
184
184
  const rawMethodEntries = nsRoutes
@@ -220,7 +220,13 @@ function generateTypedSvelteKitClient(routes: RouteInfo[]): string {
220
220
  const eventsType = `Routes.${pascalNs}.${pascalRoute}.Events`;
221
221
  const fullRouteName = commonPrefix ? `${commonPrefix}.${r.name}` : r.name;
222
222
  // SSE returns typed SSEConnection for type-safe event handling
223
- return ` ${methodName}: (${hasInput ? `input: ${inputType}` : ""}): SSEConnection<${eventsType}> => this.sseConnect("${fullRouteName}"${hasInput ? ", input" : ""})`;
223
+ // With input: (input, options?) => sseConnect(route, input, options)
224
+ // Without input: (options?) => sseConnect(route, undefined, options)
225
+ if (hasInput) {
226
+ return ` ${methodName}: (input: ${inputType}, options?: SSEConnectionOptions): SSEConnection<${eventsType}> => this.sseConnect("${fullRouteName}", input, options)`;
227
+ } else {
228
+ return ` ${methodName}: (options?: SSEConnectionOptions): SSEConnection<${eventsType}> => this.sseConnect("${fullRouteName}", undefined, options)`;
229
+ }
224
230
  });
225
231
 
226
232
  const formDataMethodEntries = nsRoutes
package/src/vite.ts CHANGED
@@ -7,8 +7,12 @@
7
7
  */
8
8
 
9
9
  import type { Plugin, ViteDevServer } from "vite";
10
- import { spawn, type ChildProcess } from "node:child_process";
11
- import { resolve } from "node:path";
10
+ import { spawn, type ChildProcess, exec } from "node:child_process";
11
+ import { resolve, join } from "node:path";
12
+ import { watch, type FSWatcher } from "node:fs";
13
+ import { promisify } from "node:util";
14
+
15
+ const execAsync = promisify(exec);
12
16
  import http from "node:http";
13
17
 
14
18
  export interface DevPluginOptions {
@@ -25,6 +29,18 @@ export interface DevPluginOptions {
25
29
  * @default 3001
26
30
  */
27
31
  backendPort?: number;
32
+
33
+ /**
34
+ * Watch server files and auto-regenerate types on changes.
35
+ * @default true
36
+ */
37
+ watchTypes?: boolean;
38
+
39
+ /**
40
+ * Directory to watch for server file changes.
41
+ * @default "./src/server"
42
+ */
43
+ watchDir?: string;
28
44
  }
29
45
 
30
46
  // Check if running with Bun runtime (bun --bun)
@@ -66,7 +82,12 @@ function setDevServer(server: any) {
66
82
  * });
67
83
  */
68
84
  export function donkeylabsDev(options: DevPluginOptions = {}): Plugin {
69
- const { serverEntry = "./src/server/index.ts", backendPort = 3001 } = options;
85
+ const {
86
+ serverEntry = "./src/server/index.ts",
87
+ backendPort = 3001,
88
+ watchTypes = true,
89
+ watchDir = "./src/server",
90
+ } = options;
70
91
 
71
92
  // State for subprocess mode
72
93
  let backendProcess: ChildProcess | null = null;
@@ -76,6 +97,71 @@ export function donkeylabsDev(options: DevPluginOptions = {}): Plugin {
76
97
  let appServer: any = null;
77
98
  let serverReady = false;
78
99
 
100
+ // State for file watcher
101
+ let fileWatcher: FSWatcher | null = null;
102
+ let isGenerating = false;
103
+ let lastGenerationTime = 0;
104
+ let debounceTimer: ReturnType<typeof setTimeout> | null = null;
105
+
106
+ const COOLDOWN_MS = 2000;
107
+ const DEBOUNCE_MS = 500;
108
+
109
+ // Patterns to ignore (generated files)
110
+ const IGNORED_PATTERNS = [/schema\.ts$/, /\.d\.ts$/];
111
+
112
+ function shouldIgnoreFile(filename: string): boolean {
113
+ return IGNORED_PATTERNS.some((pattern) => pattern.test(filename));
114
+ }
115
+
116
+ async function regenerateTypes() {
117
+ const now = Date.now();
118
+ if (now - lastGenerationTime < COOLDOWN_MS || isGenerating) return;
119
+
120
+ isGenerating = true;
121
+ lastGenerationTime = now;
122
+ console.log("\x1b[36m[donkeylabs-dev]\x1b[0m Server files changed, regenerating types...");
123
+
124
+ try {
125
+ await execAsync("bunx donkeylabs generate");
126
+ console.log("\x1b[32m[donkeylabs-dev]\x1b[0m Types regenerated successfully");
127
+ } catch (e: any) {
128
+ console.error("\x1b[31m[donkeylabs-dev]\x1b[0m Error regenerating types:", e.message);
129
+ } finally {
130
+ isGenerating = false;
131
+ lastGenerationTime = Date.now();
132
+ }
133
+ }
134
+
135
+ function debouncedRegenerate() {
136
+ if (debounceTimer) clearTimeout(debounceTimer);
137
+ debounceTimer = setTimeout(regenerateTypes, DEBOUNCE_MS);
138
+ }
139
+
140
+ function startFileWatcher() {
141
+ if (!watchTypes || fileWatcher) return;
142
+
143
+ const watchPath = resolve(process.cwd(), watchDir);
144
+ try {
145
+ fileWatcher = watch(watchPath, { recursive: true }, (_eventType, filename) => {
146
+ if (!filename) return;
147
+ if (!filename.endsWith(".ts")) return;
148
+ if (shouldIgnoreFile(filename)) return;
149
+ debouncedRegenerate();
150
+ });
151
+ console.log(`\x1b[36m[donkeylabs-dev]\x1b[0m Watching ${watchDir} for changes...`);
152
+ } catch (err) {
153
+ console.warn(`\x1b[33m[donkeylabs-dev]\x1b[0m Could not watch ${watchDir}:`, err);
154
+ }
155
+ }
156
+
157
+ function stopFileWatcher() {
158
+ if (debounceTimer) clearTimeout(debounceTimer);
159
+ if (fileWatcher) {
160
+ fileWatcher.close();
161
+ fileWatcher = null;
162
+ }
163
+ }
164
+
79
165
  return {
80
166
  name: "donkeylabs-dev",
81
167
  enforce: "pre",
@@ -97,6 +183,9 @@ export function donkeylabsDev(options: DevPluginOptions = {}): Plugin {
97
183
  async configureServer(server: ViteDevServer) {
98
184
  const serverEntryResolved = resolve(process.cwd(), serverEntry);
99
185
 
186
+ // Start file watcher for auto type regeneration
187
+ startFileWatcher();
188
+
100
189
  if (isBunRuntime) {
101
190
  // ========== IN-PROCESS MODE (bun --bun run dev) ==========
102
191
  // Import and initialize server directly - no subprocess, no proxy
@@ -486,6 +575,7 @@ export function donkeylabsDev(options: DevPluginOptions = {}): Plugin {
486
575
  },
487
576
 
488
577
  async closeBundle() {
578
+ stopFileWatcher();
489
579
  if (backendProcess) {
490
580
  backendProcess.kill();
491
581
  backendProcess = null;