@donkeylabs/adapter-sveltekit 2.0.15 → 2.0.16

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/index.js ADDED
@@ -0,0 +1,322 @@
1
+ /**
2
+ * @donkeylabs/adapter-sveltekit
3
+ *
4
+ * SvelteKit adapter that integrates with @donkeylabs/server.
5
+ * - Single Bun process serves both SvelteKit pages and API routes
6
+ * - Direct service calls during SSR (no HTTP overhead)
7
+ * - Unified API client works in both SSR and browser
8
+ */
9
+ import { fileURLToPath } from "node:url";
10
+ import { dirname, join, relative, resolve } from "node:path";
11
+ import { writeFileSync, mkdirSync } from "node:fs";
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+ export default function adapter(options = {}) {
15
+ const { out = "build", serverEntry = "./src/server/index.ts", precompress = true, envPrefix = "", development = false, } = options;
16
+ return {
17
+ name: "@donkeylabs/adapter-sveltekit",
18
+ async adapt(builder) {
19
+ const serverDir = join(out, "server");
20
+ const clientDir = join(out, "client");
21
+ const prerenderedDir = join(out, "prerendered");
22
+ // 1. Clean and create output directories
23
+ builder.rimraf(out);
24
+ mkdirSync(serverDir, { recursive: true });
25
+ builder.log.minor("Writing SvelteKit server files...");
26
+ // 2. Write SvelteKit server files
27
+ builder.writeServer(serverDir);
28
+ // 3. Copy static assets and prerendered pages
29
+ const clientFiles = builder.writeClient(clientDir);
30
+ const prerenderedFiles = builder.writePrerendered(prerenderedDir);
31
+ // 4. Generate the manifest
32
+ const relativePath = relative(serverDir, ".");
33
+ builder.generateManifest({ relativePath });
34
+ // 5. Generate the unified runtime entry point
35
+ const serverEntryResolved = resolve(serverEntry);
36
+ const serverEntryRelative = relative(serverDir, serverEntryResolved);
37
+ const entryCode = generateEntryPoint({
38
+ serverEntryRelative,
39
+ envPrefix,
40
+ development,
41
+ clientDir: relative(serverDir, clientDir),
42
+ prerenderedDir: relative(serverDir, prerenderedDir),
43
+ });
44
+ writeFileSync(join(serverDir, "entry.js"), entryCode);
45
+ // 6. Write runtime handler (inline the full implementation)
46
+ writeFileSync(join(serverDir, "handler.js"), generateRuntimeHandler());
47
+ // 7. Precompress if enabled
48
+ if (precompress) {
49
+ builder.log.minor("Compressing assets...");
50
+ await builder.compress(clientDir);
51
+ if (prerenderedFiles.length > 0) {
52
+ await builder.compress(prerenderedDir);
53
+ }
54
+ }
55
+ builder.log.success(`Adapter output written to ${out}`);
56
+ builder.log.minor(`Run with: bun ${out}/server/entry.js`);
57
+ },
58
+ };
59
+ }
60
+ function generateEntryPoint(config) {
61
+ const { serverEntryRelative, envPrefix, development, clientDir, prerenderedDir } = config;
62
+ return `// Generated by @donkeylabs/adapter-sveltekit
63
+ import { Server } from "./index.js";
64
+ import { manifest } from "./manifest.js";
65
+ import { createUnifiedServer } from "./handler.js";
66
+
67
+ // Import user's @donkeylabs/server setup
68
+ const serverModule = await import("${serverEntryRelative}");
69
+ const donkeylabsServer = serverModule.server || serverModule.default;
70
+
71
+ if (!donkeylabsServer) {
72
+ throw new Error(
73
+ "@donkeylabs/adapter-sveltekit: Could not find server export. " +
74
+ "Make sure your server entry file exports 'server' or uses default export."
75
+ );
76
+ }
77
+
78
+ // Initialize @donkeylabs/server (migrations, plugins, routes)
79
+ await donkeylabsServer.initialize();
80
+
81
+ // Create SvelteKit server
82
+ const svelteServer = new Server(manifest);
83
+ await svelteServer.init({ env: process.env });
84
+
85
+ // Configuration
86
+ const port = Number(process.env.${envPrefix}PORT) || 3000;
87
+ const host = process.env.${envPrefix}HOST || "0.0.0.0";
88
+ const development = ${development} || process.env.NODE_ENV === "development";
89
+
90
+ // Start unified server
91
+ createUnifiedServer({
92
+ svelteServer,
93
+ donkeylabsServer,
94
+ port,
95
+ host,
96
+ clientDir: "${clientDir}",
97
+ prerenderedDir: "${prerenderedDir}",
98
+ development,
99
+ });
100
+
101
+ console.log(\`Server running at http://\${host}:\${port}\`);
102
+ `;
103
+ }
104
+ function generateRuntimeHandler() {
105
+ // Inline the full runtime handler implementation
106
+ return `// Generated runtime handler by @donkeylabs/adapter-sveltekit
107
+ import { resolve, dirname } from "node:path";
108
+ import { fileURLToPath } from "node:url";
109
+
110
+ const __dirname = dirname(fileURLToPath(import.meta.url));
111
+
112
+ /**
113
+ * Create a unified Bun server that handles both SvelteKit and @donkeylabs/server requests.
114
+ */
115
+ export function createUnifiedServer(config) {
116
+ const {
117
+ svelteServer,
118
+ donkeylabsServer,
119
+ port,
120
+ host,
121
+ clientDir: clientDirRelative,
122
+ prerenderedDir: prerenderedDirRelative,
123
+ development = false,
124
+ } = config;
125
+
126
+ // Resolve paths relative to this script's directory
127
+ const clientDir = resolve(__dirname, clientDirRelative);
128
+ const prerenderedDir = resolve(__dirname, prerenderedDirRelative);
129
+
130
+ // Get services and core from @donkeylabs/server for injection into SvelteKit
131
+ const services = donkeylabsServer.getServices();
132
+ const core = donkeylabsServer.getCore();
133
+
134
+ Bun.serve({
135
+ port,
136
+ hostname: host,
137
+
138
+ async fetch(req, server) {
139
+ const url = new URL(req.url);
140
+ const pathname = url.pathname;
141
+ const ip = extractClientIP(req, server.requestIP(req)?.address);
142
+
143
+ // 1. Handle CORS preflight for API routes
144
+ if (req.method === "OPTIONS") {
145
+ return new Response(null, {
146
+ status: 204,
147
+ headers: getCorsHeaders(),
148
+ });
149
+ }
150
+
151
+ // 2. API routes (GET or POST - stream/html/sse use GET, typed uses POST)
152
+ if ((req.method === "GET" || req.method === "POST") && /^\\/[a-zA-Z][a-zA-Z0-9_.]*$/.test(pathname)) {
153
+ const routeName = pathname.slice(1); // Remove leading /
154
+
155
+ // Check if this is a registered API route
156
+ if (donkeylabsServer.hasRoute(routeName)) {
157
+ const response = await donkeylabsServer.handleRequest(req, routeName, ip, {
158
+ corsHeaders: getCorsHeaders(),
159
+ });
160
+ if (response) return response;
161
+ }
162
+ }
163
+
164
+ // 3. SSE endpoint
165
+ if (pathname === "/sse" && req.method === "GET") {
166
+ return handleSSE(req, core, ip);
167
+ }
168
+
169
+ // 4. Static assets (/_app/*, etc.)
170
+ if (isStaticAsset(pathname)) {
171
+ const staticResponse = await serveStatic(clientDir, pathname);
172
+ if (staticResponse) return staticResponse;
173
+ }
174
+
175
+ // 5. Prerendered pages
176
+ const prerenderedResponse = await servePrerendered(prerenderedDir, pathname);
177
+ if (prerenderedResponse) return prerenderedResponse;
178
+
179
+ // 6. SvelteKit pages
180
+ return handleSvelteKit(req, svelteServer, {
181
+ services,
182
+ core,
183
+ ip,
184
+ donkeylabsServer,
185
+ });
186
+ },
187
+ });
188
+ }
189
+
190
+ function isStaticAsset(pathname) {
191
+ return (
192
+ pathname.startsWith("/_app/") ||
193
+ pathname.startsWith("/static/") ||
194
+ pathname === "/favicon.ico" ||
195
+ pathname === "/robots.txt" ||
196
+ pathname === "/sitemap.xml" ||
197
+ /\\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|webp|avif|json|webmanifest)$/.test(pathname)
198
+ );
199
+ }
200
+
201
+ async function serveStatic(clientDir, pathname) {
202
+ const filePath = clientDir + pathname;
203
+ const file = Bun.file(filePath);
204
+
205
+ if (await file.exists()) {
206
+ // Check for precompressed versions
207
+ const brFile = Bun.file(filePath + ".br");
208
+ const gzFile = Bun.file(filePath + ".gz");
209
+
210
+ if (await brFile.exists()) {
211
+ return new Response(brFile, {
212
+ headers: {
213
+ "Content-Type": file.type || "application/octet-stream",
214
+ "Content-Encoding": "br",
215
+ "Cache-Control": "public, max-age=31536000, immutable",
216
+ },
217
+ });
218
+ }
219
+
220
+ if (await gzFile.exists()) {
221
+ return new Response(gzFile, {
222
+ headers: {
223
+ "Content-Type": file.type || "application/octet-stream",
224
+ "Content-Encoding": "gzip",
225
+ "Cache-Control": "public, max-age=31536000, immutable",
226
+ },
227
+ });
228
+ }
229
+
230
+ return new Response(file, {
231
+ headers: {
232
+ "Content-Type": file.type || "application/octet-stream",
233
+ "Cache-Control": "public, max-age=31536000, immutable",
234
+ },
235
+ });
236
+ }
237
+
238
+ return null;
239
+ }
240
+
241
+ async function servePrerendered(prerenderedDir, pathname) {
242
+ let filePath = prerenderedDir + pathname;
243
+ if (!pathname.endsWith(".html") && !pathname.includes(".")) {
244
+ filePath = prerenderedDir + (pathname === "/" ? "/index" : pathname) + ".html";
245
+ }
246
+
247
+ const file = Bun.file(filePath);
248
+ if (await file.exists()) {
249
+ return new Response(file, {
250
+ headers: {
251
+ "Content-Type": "text/html",
252
+ "Cache-Control": "public, max-age=0, must-revalidate",
253
+ },
254
+ });
255
+ }
256
+
257
+ return null;
258
+ }
259
+
260
+ async function handleSvelteKit(req, svelteServer, context) {
261
+ return svelteServer.respond(req, {
262
+ getClientAddress: () => context.ip,
263
+ platform: {
264
+ donkeylabs: {
265
+ services: context.services,
266
+ core: context.core,
267
+ // Direct route handler for SSR API calls (no HTTP!)
268
+ handleRoute: async (routeName, input) => {
269
+ return context.donkeylabsServer.callRoute(routeName, input, context.ip);
270
+ },
271
+ },
272
+ },
273
+ });
274
+ }
275
+
276
+ function handleSSE(req, core, ip) {
277
+ const url = new URL(req.url);
278
+ const channels = url.searchParams.get("channels")?.split(",") || [];
279
+
280
+ if (channels.length === 0) {
281
+ return new Response("Missing channels parameter", { status: 400 });
282
+ }
283
+
284
+ const lastEventId = req.headers.get("Last-Event-ID") || undefined;
285
+ const { client, response } = core.sse.addClient({ lastEventId });
286
+
287
+ for (const channel of channels) {
288
+ core.sse.subscribe(client.id, channel);
289
+ }
290
+
291
+ req.signal.addEventListener("abort", () => {
292
+ core.sse.removeClient(client.id);
293
+ });
294
+
295
+ const headers = new Headers(response.headers);
296
+ const corsHeaders = getCorsHeaders();
297
+ Object.entries(corsHeaders).forEach(([k, v]) => headers.set(k, v));
298
+
299
+ return new Response(response.body, {
300
+ status: response.status,
301
+ headers,
302
+ });
303
+ }
304
+
305
+ function extractClientIP(req, socketIP) {
306
+ return (
307
+ req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ||
308
+ req.headers.get("x-real-ip") ||
309
+ socketIP ||
310
+ "127.0.0.1"
311
+ );
312
+ }
313
+
314
+ function getCorsHeaders() {
315
+ return {
316
+ "Access-Control-Allow-Origin": "*",
317
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
318
+ "Access-Control-Allow-Headers": "Content-Type, Authorization",
319
+ };
320
+ }
321
+ `;
322
+ }
package/dist/vite.d.ts ADDED
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Vite plugin for @donkeylabs/adapter-sveltekit dev server integration
3
+ *
4
+ * Supports two modes:
5
+ * - `bun --bun run dev`: Single-process mode (in-process, one port)
6
+ * - `bun run dev`: Subprocess mode (two processes, proxy)
7
+ */
8
+ import type { Plugin } from "vite";
9
+ export interface DevPluginOptions {
10
+ /**
11
+ * Path to your @donkeylabs/server entry file.
12
+ * This file should export a configured AppServer instance.
13
+ *
14
+ * @default "./src/server/index.ts"
15
+ */
16
+ serverEntry?: string;
17
+ /**
18
+ * Port for the backend server (subprocess mode only).
19
+ * @default 3001
20
+ */
21
+ backendPort?: number;
22
+ /**
23
+ * Watch server files and auto-regenerate types on changes.
24
+ * @default true
25
+ */
26
+ watchTypes?: boolean;
27
+ /**
28
+ * Directory to watch for server file changes.
29
+ * @default "./src/server"
30
+ */
31
+ watchDir?: string;
32
+ /**
33
+ * Enable hot reload for route files (dev mode only).
34
+ * When a route file changes, the router will be reloaded without server restart.
35
+ * @default true
36
+ */
37
+ hotReloadRoutes?: boolean;
38
+ /**
39
+ * Glob patterns for route files to watch for hot reload.
40
+ * @default ["**\/routes\/**\/*.ts"]
41
+ */
42
+ routePatterns?: string[];
43
+ }
44
+ declare global {
45
+ var __donkeylabs_dev_server__: any;
46
+ }
47
+ /**
48
+ * Get the global app server instance for SSR direct calls.
49
+ * This allows hooks to access the server without HTTP.
50
+ */
51
+ export declare function getDevServer(): any;
52
+ /**
53
+ * Vite plugin that integrates @donkeylabs/server with the dev server.
54
+ *
55
+ * - With `bun --bun run dev`: Runs in-process (single port, recommended)
56
+ * - With `bun run dev`: Spawns subprocess (two ports, fallback)
57
+ *
58
+ * @example
59
+ * // vite.config.ts
60
+ * import { donkeylabsDev } from "@donkeylabs/adapter-sveltekit/vite";
61
+ *
62
+ * export default defineConfig({
63
+ * plugins: [
64
+ * donkeylabsDev({ serverEntry: "./src/server/index.ts" }),
65
+ * sveltekit()
66
+ * ]
67
+ * });
68
+ */
69
+ export declare function donkeylabsDev(options?: DevPluginOptions): Plugin;
70
+ export default donkeylabsDev;
71
+ //# sourceMappingURL=vite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAC;AASlD,MAAM,WAAW,gBAAgB;IAC/B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAOD,OAAO,CAAC,MAAM,CAAC;IACb,IAAI,yBAAyB,EAAE,GAAG,CAAC;CACpC;AAED;;;GAGG;AACH,wBAAgB,YAAY,IAAI,GAAG,CAElC;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAsnBpE;AAED,eAAe,aAAa,CAAC"}