@donkeylabs/adapter-sveltekit 2.0.14 → 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/client/index.d.ts +231 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +501 -0
- package/dist/generator/index.d.ts +17 -0
- package/dist/generator/index.d.ts.map +1 -0
- package/dist/generator/index.js +330 -0
- package/dist/hooks/index.d.ts +53 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +96 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/{src/index.ts → dist/index.js} +51 -120
- package/dist/vite.d.ts +71 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +611 -0
- package/package.json +16 -14
- package/src/client/index.ts +0 -659
- package/src/generator/index.ts +0 -401
- package/src/hooks/index.ts +0 -124
- package/src/vite.ts +0 -729
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SvelteKit-specific client generator
|
|
3
|
+
*
|
|
4
|
+
* This generator extends the core @donkeylabs/server generator
|
|
5
|
+
* to produce clients that work with both SSR (direct calls) and browser (HTTP).
|
|
6
|
+
*/
|
|
7
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
8
|
+
import { dirname } from "node:path";
|
|
9
|
+
import { zodToTypeScript, toPascalCase, toCamelCase, } from "@donkeylabs/server/generator";
|
|
10
|
+
/**
|
|
11
|
+
* Type guard to check if a route is a full RouteInfo (with prefix and routeName)
|
|
12
|
+
*/
|
|
13
|
+
function isRouteInfo(route) {
|
|
14
|
+
return (typeof route === "object" &&
|
|
15
|
+
route !== null &&
|
|
16
|
+
"prefix" in route &&
|
|
17
|
+
"routeName" in route &&
|
|
18
|
+
typeof route.prefix === "string");
|
|
19
|
+
}
|
|
20
|
+
/** SvelteKit-specific generator options */
|
|
21
|
+
export const svelteKitGeneratorOptions = {
|
|
22
|
+
baseImport: 'import { UnifiedApiClientBase, SSEConnection, type ClientOptions, type RequestOptions, type SSEConnectionOptions } from "@donkeylabs/adapter-sveltekit/client";',
|
|
23
|
+
baseClass: "UnifiedApiClientBase",
|
|
24
|
+
constructorSignature: "options?: ClientOptions",
|
|
25
|
+
constructorBody: "super(options);",
|
|
26
|
+
factoryFunction: `/**
|
|
27
|
+
* Create an API client instance
|
|
28
|
+
*
|
|
29
|
+
* @param options.locals - Pass SvelteKit locals for SSR direct calls (no HTTP overhead)
|
|
30
|
+
* @param options.baseUrl - Override the base URL for HTTP calls
|
|
31
|
+
*
|
|
32
|
+
* @example SSR usage in +page.server.ts:
|
|
33
|
+
* \`\`\`ts
|
|
34
|
+
* export const load = async ({ locals }) => {
|
|
35
|
+
* const api = createApi({ locals });
|
|
36
|
+
* const data = await api.myRoute.get({}); // Direct call, no HTTP!
|
|
37
|
+
* return { data };
|
|
38
|
+
* };
|
|
39
|
+
* \`\`\`
|
|
40
|
+
*
|
|
41
|
+
* @example Browser usage in +page.svelte:
|
|
42
|
+
* \`\`\`svelte
|
|
43
|
+
* <script>
|
|
44
|
+
* import { createApi } from '$lib/api';
|
|
45
|
+
* const api = createApi(); // HTTP calls
|
|
46
|
+
* let data = $state(null);
|
|
47
|
+
* async function load() {
|
|
48
|
+
* data = await api.myRoute.get({});
|
|
49
|
+
* }
|
|
50
|
+
* </script>
|
|
51
|
+
* \`\`\`
|
|
52
|
+
*/
|
|
53
|
+
export function createApi(options?: ClientOptions) {
|
|
54
|
+
return new ApiClient(options);
|
|
55
|
+
}`,
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Build a nested tree structure from routes
|
|
59
|
+
* e.g., routes "api.counter.get", "api.cache.set" become:
|
|
60
|
+
* api -> { counter -> { get }, cache -> { set } }
|
|
61
|
+
*/
|
|
62
|
+
function buildRouteTree(routes, commonPrefix) {
|
|
63
|
+
const tree = new Map();
|
|
64
|
+
for (const route of routes) {
|
|
65
|
+
// Get the path parts for nesting (e.g., "api.counter.get" -> ["api", "counter", "get"])
|
|
66
|
+
const parts = route.name.split(".");
|
|
67
|
+
const methodName = parts[parts.length - 1]; // Last part is the method
|
|
68
|
+
const namespaceParts = parts.slice(0, -1); // Everything before is namespace path
|
|
69
|
+
if (namespaceParts.length === 0) {
|
|
70
|
+
// Root level method
|
|
71
|
+
if (!tree.has("_root")) {
|
|
72
|
+
tree.set("_root", { methods: [], children: new Map() });
|
|
73
|
+
}
|
|
74
|
+
tree.get("_root").methods.push(generateMethodAndType(route, methodName, "Root", commonPrefix));
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
// Navigate/create the tree path
|
|
78
|
+
let current = tree;
|
|
79
|
+
for (let i = 0; i < namespaceParts.length; i++) {
|
|
80
|
+
const part = namespaceParts[i];
|
|
81
|
+
if (!current.has(part)) {
|
|
82
|
+
current.set(part, { methods: [], children: new Map() });
|
|
83
|
+
}
|
|
84
|
+
if (i === namespaceParts.length - 1) {
|
|
85
|
+
// At the final namespace level - add the method here
|
|
86
|
+
const pascalNs = toPascalCase(namespaceParts.join("."));
|
|
87
|
+
current.get(part).methods.push(generateMethodAndType(route, methodName, pascalNs, commonPrefix));
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
// Continue traversing
|
|
91
|
+
current = current.get(part).children;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return tree;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Generate method definition and type definition for a route
|
|
99
|
+
*/
|
|
100
|
+
function generateMethodAndType(route, methodName, pascalNs, commonPrefix) {
|
|
101
|
+
const camelMethod = toCamelCase(methodName);
|
|
102
|
+
const pascalRoute = toPascalCase(methodName);
|
|
103
|
+
const fullRouteName = route.name; // Already includes full path
|
|
104
|
+
// Generate input type
|
|
105
|
+
const inputType = route.inputSource
|
|
106
|
+
? (route.inputSource.trim().startsWith("z.") ? zodToTypeScript(route.inputSource) : route.inputSource)
|
|
107
|
+
: "Record<string, never>";
|
|
108
|
+
// Generate type definition
|
|
109
|
+
let typeDef = "";
|
|
110
|
+
let methodDef = "";
|
|
111
|
+
if (route.handler === "stream" || route.handler === "html") {
|
|
112
|
+
typeDef = ` export namespace ${pascalRoute} {
|
|
113
|
+
export type Input = Expand<${inputType}>;
|
|
114
|
+
}
|
|
115
|
+
export type ${pascalRoute} = { Input: ${pascalRoute}.Input };`;
|
|
116
|
+
if (route.handler === "stream") {
|
|
117
|
+
methodDef = `${camelMethod}: {
|
|
118
|
+
/** POST request with JSON body (programmatic) */
|
|
119
|
+
fetch: (input: Routes.${pascalNs}.${pascalRoute}.Input, options?: RequestOptions): Promise<Response> => this.streamRequest("${fullRouteName}", input, options),
|
|
120
|
+
/** GET URL for browser src attributes (video, img, download links) */
|
|
121
|
+
url: (input: Routes.${pascalNs}.${pascalRoute}.Input): string => this.streamUrl("${fullRouteName}", input),
|
|
122
|
+
/** GET request with query params */
|
|
123
|
+
get: (input: Routes.${pascalNs}.${pascalRoute}.Input, options?: RequestOptions): Promise<Response> => this.streamGet("${fullRouteName}", input, options),
|
|
124
|
+
}`;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
const hasInput = route.inputSource;
|
|
128
|
+
methodDef = `${camelMethod}: (${hasInput ? `input: Routes.${pascalNs}.${pascalRoute}.Input` : ""}): Promise<string> => this.htmlRequest("${fullRouteName}"${hasInput ? ", input" : ""})`;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else if (route.handler === "sse") {
|
|
132
|
+
const eventsEntries = route.eventsSource
|
|
133
|
+
? Object.entries(route.eventsSource).map(([eventName, eventSchema]) => {
|
|
134
|
+
const eventType = eventSchema.trim().startsWith("z.")
|
|
135
|
+
? zodToTypeScript(eventSchema)
|
|
136
|
+
: eventSchema;
|
|
137
|
+
return ` "${eventName}": Expand<${eventType}>;`;
|
|
138
|
+
})
|
|
139
|
+
: [];
|
|
140
|
+
const eventsType = eventsEntries.length > 0
|
|
141
|
+
? `{\n${eventsEntries.join("\n")}\n }`
|
|
142
|
+
: "Record<string, unknown>";
|
|
143
|
+
typeDef = ` export namespace ${pascalRoute} {
|
|
144
|
+
export type Input = Expand<${inputType}>;
|
|
145
|
+
export type Events = ${eventsType};
|
|
146
|
+
}
|
|
147
|
+
export type ${pascalRoute} = { Input: ${pascalRoute}.Input; Events: ${pascalRoute}.Events };`;
|
|
148
|
+
const hasInput = route.inputSource;
|
|
149
|
+
if (hasInput) {
|
|
150
|
+
methodDef = `${camelMethod}: (input: Routes.${pascalNs}.${pascalRoute}.Input, options?: SSEConnectionOptions): SSEConnection<Routes.${pascalNs}.${pascalRoute}.Events> => this.sseConnect("${fullRouteName}", input, options)`;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
methodDef = `${camelMethod}: (options?: SSEConnectionOptions): SSEConnection<Routes.${pascalNs}.${pascalRoute}.Events> => this.sseConnect("${fullRouteName}", undefined, options)`;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else if (route.handler === "raw") {
|
|
157
|
+
typeDef = ""; // Raw routes don't have types
|
|
158
|
+
methodDef = `${camelMethod}: (init?: RequestInit): Promise<Response> => this.rawRequest("${fullRouteName}", init)`;
|
|
159
|
+
}
|
|
160
|
+
else if (route.handler === "formData") {
|
|
161
|
+
const outputType = route.outputSource
|
|
162
|
+
? (route.outputSource.trim().startsWith("z.") ? zodToTypeScript(route.outputSource) : route.outputSource)
|
|
163
|
+
: "unknown";
|
|
164
|
+
typeDef = ` export namespace ${pascalRoute} {
|
|
165
|
+
export type Input = Expand<${inputType}>;
|
|
166
|
+
export type Output = Expand<${outputType}>;
|
|
167
|
+
}
|
|
168
|
+
export type ${pascalRoute} = { Input: ${pascalRoute}.Input; Output: ${pascalRoute}.Output };`;
|
|
169
|
+
methodDef = `${camelMethod}: (fields: Routes.${pascalNs}.${pascalRoute}.Input, files: File[]): Promise<Routes.${pascalNs}.${pascalRoute}.Output> => this.formDataRequest("${fullRouteName}", fields, files)`;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
// typed handler (default)
|
|
173
|
+
const outputType = route.outputSource
|
|
174
|
+
? (route.outputSource.trim().startsWith("z.") ? zodToTypeScript(route.outputSource) : route.outputSource)
|
|
175
|
+
: "unknown";
|
|
176
|
+
typeDef = ` export namespace ${pascalRoute} {
|
|
177
|
+
export type Input = Expand<${inputType}>;
|
|
178
|
+
export type Output = Expand<${outputType}>;
|
|
179
|
+
}
|
|
180
|
+
export type ${pascalRoute} = { Input: ${pascalRoute}.Input; Output: ${pascalRoute}.Output };`;
|
|
181
|
+
methodDef = `${camelMethod}: (input: Routes.${pascalNs}.${pascalRoute}.Input, options?: RequestOptions): Promise<Routes.${pascalNs}.${pascalRoute}.Output> => this.request("${fullRouteName}", input, options)`;
|
|
182
|
+
}
|
|
183
|
+
return { methodDef, typeDef };
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Generate nested object code from a tree node
|
|
187
|
+
*/
|
|
188
|
+
function generateNestedMethods(node, indent = " ") {
|
|
189
|
+
const parts = [];
|
|
190
|
+
// Add methods at this level
|
|
191
|
+
for (const { methodDef } of node.methods) {
|
|
192
|
+
// Indent each line of the method definition
|
|
193
|
+
const indented = methodDef.split("\n").map((line, i) => i === 0 ? `${indent}${line}` : `${indent}${line}`).join("\n");
|
|
194
|
+
parts.push(indented);
|
|
195
|
+
}
|
|
196
|
+
// Add nested namespaces
|
|
197
|
+
for (const [childName, childNode] of node.children) {
|
|
198
|
+
const childContent = generateNestedMethods(childNode, indent + " ");
|
|
199
|
+
parts.push(`${indent}${childName}: {\n${childContent}\n${indent}}`);
|
|
200
|
+
}
|
|
201
|
+
return parts.join(",\n");
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Collect all type definitions from a tree
|
|
205
|
+
*/
|
|
206
|
+
function collectTypeDefs(tree, prefix = "") {
|
|
207
|
+
const result = new Map();
|
|
208
|
+
for (const [name, node] of tree) {
|
|
209
|
+
const nsPath = prefix ? `${prefix}.${name}` : name;
|
|
210
|
+
const pascalNs = name === "_root" ? "Root" : toPascalCase(nsPath);
|
|
211
|
+
// Collect types from this node's methods
|
|
212
|
+
const typeDefs = node.methods
|
|
213
|
+
.map(m => m.typeDef)
|
|
214
|
+
.filter(t => t.length > 0);
|
|
215
|
+
if (typeDefs.length > 0) {
|
|
216
|
+
if (!result.has(pascalNs)) {
|
|
217
|
+
result.set(pascalNs, []);
|
|
218
|
+
}
|
|
219
|
+
result.get(pascalNs).push(...typeDefs);
|
|
220
|
+
}
|
|
221
|
+
// Recursively collect from children
|
|
222
|
+
const childTypes = collectTypeDefs(node.children, nsPath);
|
|
223
|
+
for (const [childNs, childDefs] of childTypes) {
|
|
224
|
+
if (!result.has(childNs)) {
|
|
225
|
+
result.set(childNs, []);
|
|
226
|
+
}
|
|
227
|
+
result.get(childNs).push(...childDefs);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Generate a fully-typed SvelteKit-compatible API client
|
|
234
|
+
*/
|
|
235
|
+
function generateTypedSvelteKitClient(routes) {
|
|
236
|
+
const opts = svelteKitGeneratorOptions;
|
|
237
|
+
const commonPrefix = ""; // We don't strip prefixes anymore - nested structure handles it
|
|
238
|
+
// Build nested tree structure from routes
|
|
239
|
+
const tree = buildRouteTree(routes, commonPrefix);
|
|
240
|
+
// Collect type definitions from tree
|
|
241
|
+
const typesByNamespace = collectTypeDefs(tree);
|
|
242
|
+
const typeBlocks = [];
|
|
243
|
+
for (const [nsName, typeDefs] of typesByNamespace) {
|
|
244
|
+
if (typeDefs.length > 0) {
|
|
245
|
+
typeBlocks.push(` export namespace ${nsName} {\n${typeDefs.join("\n\n")}\n }`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Generate method blocks from tree
|
|
249
|
+
const methodBlocks = [];
|
|
250
|
+
for (const [topLevel, node] of tree) {
|
|
251
|
+
if (topLevel === "_root") {
|
|
252
|
+
// Root level methods become direct class properties
|
|
253
|
+
for (const { methodDef } of node.methods) {
|
|
254
|
+
methodBlocks.push(` ${methodDef};`);
|
|
255
|
+
}
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
const content = generateNestedMethods(node, " ");
|
|
259
|
+
methodBlocks.push(` ${topLevel} = {\n${content}\n };`);
|
|
260
|
+
}
|
|
261
|
+
return `// Auto-generated by donkeylabs generate
|
|
262
|
+
// DO NOT EDIT MANUALLY
|
|
263
|
+
|
|
264
|
+
${opts.baseImport}
|
|
265
|
+
|
|
266
|
+
// Utility type that forces TypeScript to expand types on hover
|
|
267
|
+
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
|
|
268
|
+
|
|
269
|
+
// ============================================
|
|
270
|
+
// Route Types
|
|
271
|
+
// ============================================
|
|
272
|
+
|
|
273
|
+
export namespace Routes {
|
|
274
|
+
${typeBlocks.join("\n\n") || " // No typed routes found"}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ============================================
|
|
278
|
+
// API Client
|
|
279
|
+
// ============================================
|
|
280
|
+
|
|
281
|
+
export class ApiClient extends ${opts.baseClass} {
|
|
282
|
+
constructor(${opts.constructorSignature}) {
|
|
283
|
+
${opts.constructorBody}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
${methodBlocks.join("\n\n") || " // No routes defined"}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
${opts.factoryFunction}
|
|
290
|
+
`;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Generate a SvelteKit-compatible API client
|
|
294
|
+
*
|
|
295
|
+
* This is called by the donkeylabs CLI when adapter is set to "@donkeylabs/adapter-sveltekit"
|
|
296
|
+
*/
|
|
297
|
+
export async function generateClient(_config, routes, outputPath) {
|
|
298
|
+
let code;
|
|
299
|
+
// Always try typed generation if we have routes
|
|
300
|
+
if (routes.length > 0 && isRouteInfo(routes[0])) {
|
|
301
|
+
// Full RouteInfo - generate typed client
|
|
302
|
+
code = generateTypedSvelteKitClient(routes);
|
|
303
|
+
}
|
|
304
|
+
else if (routes.length > 0) {
|
|
305
|
+
// Convert ExtractedRoute to RouteInfo for typed generation
|
|
306
|
+
const routeInfos = routes.map((r) => {
|
|
307
|
+
const parts = r.name.split(".");
|
|
308
|
+
return {
|
|
309
|
+
name: r.name,
|
|
310
|
+
prefix: parts.slice(0, -1).join("."),
|
|
311
|
+
routeName: parts[parts.length - 1] || r.name,
|
|
312
|
+
handler: (r.handler || "typed"),
|
|
313
|
+
inputSource: undefined,
|
|
314
|
+
outputSource: undefined,
|
|
315
|
+
};
|
|
316
|
+
});
|
|
317
|
+
code = generateTypedSvelteKitClient(routeInfos);
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
// Empty routes - generate minimal client
|
|
321
|
+
code = generateTypedSvelteKitClient([]);
|
|
322
|
+
}
|
|
323
|
+
// Ensure output directory exists
|
|
324
|
+
const outputDir = dirname(outputPath);
|
|
325
|
+
await mkdir(outputDir, { recursive: true });
|
|
326
|
+
// Write the generated client
|
|
327
|
+
await writeFile(outputPath, code);
|
|
328
|
+
}
|
|
329
|
+
// Re-export building blocks for advanced usage
|
|
330
|
+
export { generateClientCode, zodToTypeScript, toPascalCase, toCamelCase, } from "@donkeylabs/server/generator";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SvelteKit hooks helper for @donkeylabs/adapter-sveltekit
|
|
3
|
+
*/
|
|
4
|
+
import type { Handle } from "@sveltejs/kit";
|
|
5
|
+
export interface DonkeylabsPlatform {
|
|
6
|
+
donkeylabs?: {
|
|
7
|
+
services: Record<string, any>;
|
|
8
|
+
core: {
|
|
9
|
+
logger: any;
|
|
10
|
+
cache: any;
|
|
11
|
+
events: any;
|
|
12
|
+
cron: any;
|
|
13
|
+
jobs: any;
|
|
14
|
+
sse: any;
|
|
15
|
+
rateLimiter: any;
|
|
16
|
+
db: any;
|
|
17
|
+
};
|
|
18
|
+
/** Direct route handler for SSR (no HTTP!) */
|
|
19
|
+
handleRoute: (routeName: string, input: any) => Promise<any>;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export interface DonkeylabsLocals {
|
|
23
|
+
plugins: Record<string, any>;
|
|
24
|
+
core: {
|
|
25
|
+
logger: any;
|
|
26
|
+
cache: any;
|
|
27
|
+
events: any;
|
|
28
|
+
sse: any;
|
|
29
|
+
};
|
|
30
|
+
db: any;
|
|
31
|
+
ip: string;
|
|
32
|
+
/** Direct route handler for SSR API calls */
|
|
33
|
+
handleRoute?: (routeName: string, input: any) => Promise<any>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Create a SvelteKit handle function that populates event.locals
|
|
37
|
+
* with @donkeylabs/server context.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* // src/hooks.server.ts
|
|
41
|
+
* import { createHandle } from "@donkeylabs/adapter-sveltekit/hooks";
|
|
42
|
+
* export const handle = createHandle();
|
|
43
|
+
*/
|
|
44
|
+
export declare function createHandle(): Handle;
|
|
45
|
+
/**
|
|
46
|
+
* Sequence multiple handle functions together.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* import { sequence, createHandle } from "@donkeylabs/adapter-sveltekit/hooks";
|
|
50
|
+
* export const handle = sequence(createHandle(), myOtherHandle);
|
|
51
|
+
*/
|
|
52
|
+
export declare function sequence(...handlers: Handle[]): Handle;
|
|
53
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAgB,MAAM,eAAe,CAAC;AAyB1D,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE;QACX,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC9B,IAAI,EAAE;YACJ,MAAM,EAAE,GAAG,CAAC;YACZ,KAAK,EAAE,GAAG,CAAC;YACX,MAAM,EAAE,GAAG,CAAC;YACZ,IAAI,EAAE,GAAG,CAAC;YACV,IAAI,EAAE,GAAG,CAAC;YACV,GAAG,EAAE,GAAG,CAAC;YACT,WAAW,EAAE,GAAG,CAAC;YACjB,EAAE,EAAE,GAAG,CAAC;SACT,CAAC;QACF,8CAA8C;QAC9C,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;KAC9D,CAAC;CACH;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,IAAI,EAAE;QACJ,MAAM,EAAE,GAAG,CAAC;QACZ,KAAK,EAAE,GAAG,CAAC;QACX,MAAM,EAAE,GAAG,CAAC;QACZ,GAAG,EAAE,GAAG,CAAC;KACV,CAAC;IACF,EAAE,EAAE,GAAG,CAAC;IACR,EAAE,EAAE,MAAM,CAAC;IACX,6CAA6C;IAC7C,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;CAC/D;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,IAAI,MAAM,CA6CrC;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,GAAG,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAYtD"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SvelteKit hooks helper for @donkeylabs/adapter-sveltekit
|
|
3
|
+
*/
|
|
4
|
+
// Try to import dev server reference (only available in dev mode)
|
|
5
|
+
let getDevServer;
|
|
6
|
+
try {
|
|
7
|
+
// Dynamic import to avoid bundling vite.ts in production
|
|
8
|
+
const viteModule = await import("../vite.js");
|
|
9
|
+
getDevServer = viteModule.getDevServer;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
// Not in dev mode or vite not available
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Safely get client IP address with fallback.
|
|
16
|
+
* SvelteKit's getClientAddress() throws when running with Bun or without proper proxy headers.
|
|
17
|
+
*/
|
|
18
|
+
function safeGetClientAddress(event) {
|
|
19
|
+
try {
|
|
20
|
+
return safeGetClientAddress(event);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// Fallback when address cannot be determined (e.g., Bun runtime, SSR without client)
|
|
24
|
+
return "127.0.0.1";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create a SvelteKit handle function that populates event.locals
|
|
29
|
+
* with @donkeylabs/server context.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* // src/hooks.server.ts
|
|
33
|
+
* import { createHandle } from "@donkeylabs/adapter-sveltekit/hooks";
|
|
34
|
+
* export const handle = createHandle();
|
|
35
|
+
*/
|
|
36
|
+
export function createHandle() {
|
|
37
|
+
return async ({ event, resolve }) => {
|
|
38
|
+
const platform = event.platform;
|
|
39
|
+
if (platform?.donkeylabs) {
|
|
40
|
+
// Production mode: use platform.donkeylabs from adapter
|
|
41
|
+
const { services, core, handleRoute } = platform.donkeylabs;
|
|
42
|
+
// Populate locals with server context
|
|
43
|
+
event.locals.plugins = services;
|
|
44
|
+
event.locals.core = {
|
|
45
|
+
logger: core.logger,
|
|
46
|
+
cache: core.cache,
|
|
47
|
+
events: core.events,
|
|
48
|
+
sse: core.sse,
|
|
49
|
+
};
|
|
50
|
+
event.locals.db = core.db;
|
|
51
|
+
event.locals.ip = safeGetClientAddress(event);
|
|
52
|
+
// Expose the direct route handler for SSR API calls
|
|
53
|
+
event.locals.handleRoute = handleRoute;
|
|
54
|
+
}
|
|
55
|
+
else if (getDevServer) {
|
|
56
|
+
// Dev mode: use global dev server from vite plugin
|
|
57
|
+
const devServer = getDevServer();
|
|
58
|
+
if (devServer) {
|
|
59
|
+
const core = devServer.getCore();
|
|
60
|
+
const plugins = devServer.getServices();
|
|
61
|
+
event.locals.plugins = plugins;
|
|
62
|
+
event.locals.core = {
|
|
63
|
+
logger: core.logger,
|
|
64
|
+
cache: core.cache,
|
|
65
|
+
events: core.events,
|
|
66
|
+
sse: core.sse,
|
|
67
|
+
};
|
|
68
|
+
event.locals.db = core.db;
|
|
69
|
+
event.locals.ip = safeGetClientAddress(event);
|
|
70
|
+
// Direct route handler for SSR
|
|
71
|
+
event.locals.handleRoute = async (routeName, input) => {
|
|
72
|
+
return devServer.callRoute(routeName, input, safeGetClientAddress(event));
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return resolve(event);
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Sequence multiple handle functions together.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* import { sequence, createHandle } from "@donkeylabs/adapter-sveltekit/hooks";
|
|
84
|
+
* export const handle = sequence(createHandle(), myOtherHandle);
|
|
85
|
+
*/
|
|
86
|
+
export function sequence(...handlers) {
|
|
87
|
+
return async ({ event, resolve }) => {
|
|
88
|
+
let resolveChain = resolve;
|
|
89
|
+
for (let i = handlers.length - 1; i >= 0; i--) {
|
|
90
|
+
const handler = handlers[i];
|
|
91
|
+
const next = resolveChain;
|
|
92
|
+
resolveChain = (event) => handler({ event, resolve: next });
|
|
93
|
+
}
|
|
94
|
+
return resolveChain(event);
|
|
95
|
+
};
|
|
96
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
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 type { Adapter } from "@sveltejs/kit";
|
|
10
|
+
export interface AdapterOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Output directory for the built app.
|
|
13
|
+
* @default "build"
|
|
14
|
+
*/
|
|
15
|
+
out?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Path to your @donkeylabs/server entry file.
|
|
18
|
+
* This file should export a configured AppServer instance.
|
|
19
|
+
*
|
|
20
|
+
* @default "./src/server/index.ts"
|
|
21
|
+
*/
|
|
22
|
+
serverEntry?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Whether to precompress static assets with gzip/brotli.
|
|
25
|
+
* @default true
|
|
26
|
+
*/
|
|
27
|
+
precompress?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Environment variable prefix for PORT and HOST.
|
|
30
|
+
* @default ""
|
|
31
|
+
*/
|
|
32
|
+
envPrefix?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Enable development mode features.
|
|
35
|
+
* @default false
|
|
36
|
+
*/
|
|
37
|
+
development?: boolean;
|
|
38
|
+
}
|
|
39
|
+
export default function adapter(options?: AdapterOptions): Adapter;
|
|
40
|
+
export type { AdapterOptions as Options };
|
|
41
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAW,MAAM,eAAe,CAAC;AAQtD,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAgErE;AAmRD,YAAY,EAAE,cAAc,IAAI,OAAO,EAAE,CAAC"}
|