litestar-vite-plugin 0.15.0-beta.1 → 0.15.0-beta.3

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/js/nuxt.js CHANGED
@@ -1,11 +1,7 @@
1
- import { exec } from "node:child_process";
2
1
  import fs from "node:fs";
3
2
  import path from "node:path";
4
- import { promisify } from "node:util";
5
3
  import colors from "picocolors";
6
- import { resolveInstallHint, resolvePackageExecutor } from "./install-hint.js";
7
- import { debounce } from "./shared/debounce.js";
8
- const execAsync = promisify(exec);
4
+ import { createTypeGenerationPlugin } from "./shared/create-type-gen-plugin.js";
9
5
  function normalizeHost(host) {
10
6
  if (host === "::" || host === "::1" || host === "0.0.0.0") {
11
7
  return "localhost";
@@ -151,234 +147,18 @@ function createProxyPlugin(config) {
151
147
  }
152
148
  };
153
149
  }
154
- async function emitRouteTypes(routesPath, outputDir) {
155
- const contents = await fs.promises.readFile(routesPath, "utf-8");
156
- const json = JSON.parse(contents);
157
- const outDir = path.resolve(process.cwd(), outputDir);
158
- await fs.promises.mkdir(outDir, { recursive: true });
159
- const outFile = path.join(outDir, "routes.ts");
160
- const banner = `// AUTO-GENERATED by litestar-vite. Do not edit.
161
- /* eslint-disable */
162
-
163
- `;
164
- const routesData = json.routes || json;
165
- const routeNames = Object.keys(routesData);
166
- const routeNameType = routeNames.length > 0 ? routeNames.map((n) => `"${n}"`).join(" | ") : "never";
167
- const routeParamTypes = [];
168
- for (const [name, data] of Object.entries(routesData)) {
169
- const routeData = data;
170
- if (routeData.parameters && routeData.parameters.length > 0) {
171
- const params = routeData.parameters.map((p) => `${p}: string | number`).join("; ");
172
- routeParamTypes.push(` "${name}": { ${params} }`);
173
- } else {
174
- routeParamTypes.push(` "${name}": Record<string, never>`);
175
- }
176
- }
177
- const body = `/**
178
- * AUTO-GENERATED by litestar-vite.
179
- *
180
- * Exports:
181
- * - routesMeta: full route metadata
182
- * - routes: name -> uri map
183
- * - serverRoutes: alias of routes for clarity in apps
184
- * - route(): type-safe URL generator
185
- * - hasRoute(): type guard
186
- * - csrf helpers re-exported from litestar-vite-plugin/helpers
187
- *
188
- * @see https://litestar-vite.litestar.dev/
189
- */
190
- export const routesMeta = ${JSON.stringify(json, null, 2)} as const
191
-
192
- /**
193
- * Route name to URI mapping.
194
- */
195
- export const routes = ${JSON.stringify(Object.fromEntries(Object.entries(routesData).map(([name, data]) => [name, data.uri])), null, 2)} as const
196
-
197
- /**
198
- * Alias for server-injected route map (more descriptive for consumers).
199
- */
200
- export const serverRoutes = routes
201
-
202
- /**
203
- * All available route names.
204
- */
205
- export type RouteName = ${routeNameType}
206
-
207
- /**
208
- * Parameter types for each route.
209
- */
210
- export interface RouteParams {
211
- ${routeParamTypes.join("\n")}
212
- }
213
-
214
- /**
215
- * Generate a URL for a named route with type-safe parameters.
216
- *
217
- * @param name - The route name
218
- * @param params - Route parameters (required if route has path parameters)
219
- * @returns The generated URL
220
- *
221
- * @example
222
- * \`\`\`ts
223
- * import { route } from '@/generated/routes'
224
- *
225
- * // Route without parameters
226
- * route('home') // "/"
227
- *
228
- * // Route with parameters
229
- * route('user:detail', { user_id: 123 }) // "/users/123"
230
- * \`\`\`
231
- */
232
- export function route<T extends RouteName>(
233
- name: T,
234
- ...args: RouteParams[T] extends Record<string, never> ? [] : [params: RouteParams[T]]
235
- ): string {
236
- let uri = routes[name] as string
237
- const params = args[0] as Record<string, string | number> | undefined
238
-
239
- if (params) {
240
- for (const [key, value] of Object.entries(params)) {
241
- // Handle both {param} and {param:type} syntax
242
- uri = uri.replace(new RegExp(\`\\\\{\${key}(?::[^}]+)?\\\\}\`, "g"), String(value))
243
- }
244
- }
245
-
246
- return uri
247
- }
248
-
249
- /**
250
- * Check if a route name exists.
251
- */
252
- export function hasRoute(name: string): name is RouteName {
253
- return name in routes
254
- }
255
-
256
- declare global {
257
- interface Window {
258
- /**
259
- * Fully-typed route metadata injected by Litestar.
260
- */
261
- __LITESTAR_ROUTES__?: typeof routesMeta
262
- /**
263
- * Simple route map (name -> uri) for legacy consumers.
264
- */
265
- routes?: typeof routes
266
- serverRoutes?: typeof serverRoutes
267
- }
268
- // eslint-disable-next-line no-var
269
- var routes: typeof routes | undefined
270
- var serverRoutes: typeof serverRoutes | undefined
271
- }
272
-
273
- // Re-export helper functions from litestar-vite-plugin
274
- // These work with the routes defined above
275
- export { getCsrfToken, csrfHeaders, csrfFetch } from "litestar-vite-plugin/helpers"
276
- `;
277
- await fs.promises.writeFile(outFile, `${banner}${body}`, "utf-8");
278
- }
279
- function createTypeGenerationPlugin(typesConfig, executor) {
280
- let server = null;
281
- let isGenerating = false;
282
- async function runTypeGeneration() {
283
- if (isGenerating) {
284
- return false;
285
- }
286
- isGenerating = true;
287
- const startTime = Date.now();
288
- try {
289
- const openapiPath = path.resolve(process.cwd(), typesConfig.openapiPath);
290
- if (!fs.existsSync(openapiPath)) {
291
- console.log(colors.cyan("[litestar-nuxt]"), colors.yellow("OpenAPI schema not found:"), typesConfig.openapiPath);
292
- return false;
293
- }
294
- console.log(colors.cyan("[litestar-nuxt]"), colors.dim("Generating TypeScript types..."));
295
- const projectRoot = process.cwd();
296
- const candidates = [path.resolve(projectRoot, "openapi-ts.config.ts"), path.resolve(projectRoot, "hey-api.config.ts"), path.resolve(projectRoot, ".hey-api.config.ts")];
297
- const configPath = candidates.find((p) => fs.existsSync(p)) || null;
298
- let args;
299
- if (configPath) {
300
- console.log(colors.cyan("[litestar-nuxt]"), colors.dim("Using config:"), configPath);
301
- args = ["@hey-api/openapi-ts", "--file", configPath];
302
- } else {
303
- args = ["@hey-api/openapi-ts", "-i", typesConfig.openapiPath, "-o", typesConfig.output];
304
- const plugins = ["@hey-api/typescript", "@hey-api/schemas"];
305
- if (typesConfig.generateSdk) {
306
- plugins.push("@hey-api/sdk", "@hey-api/client-nuxt");
307
- }
308
- if (typesConfig.generateZod) {
309
- plugins.push("zod");
310
- }
311
- if (plugins.length) {
312
- args.push("--plugins", ...plugins);
313
- }
314
- }
315
- await execAsync(resolvePackageExecutor(args.join(" "), executor), {
316
- cwd: projectRoot
317
- });
318
- const routesPath = path.resolve(process.cwd(), typesConfig.routesPath);
319
- if (fs.existsSync(routesPath)) {
320
- await emitRouteTypes(routesPath, typesConfig.output);
321
- }
322
- const duration = Date.now() - startTime;
323
- console.log(colors.cyan("[litestar-nuxt]"), colors.green("Types generated"), colors.dim(`in ${duration}ms`));
324
- if (server) {
325
- server.ws.send({
326
- type: "custom",
327
- event: "litestar:types-updated",
328
- data: {
329
- output: typesConfig.output,
330
- timestamp: Date.now()
331
- }
332
- });
333
- }
334
- return true;
335
- } catch (error) {
336
- const message = error instanceof Error ? error.message : String(error);
337
- if (message.includes("not found") || message.includes("ENOENT")) {
338
- console.log(colors.cyan("[litestar-nuxt]"), colors.yellow("@hey-api/openapi-ts not installed"), "- run:", resolveInstallHint());
339
- } else {
340
- console.error(colors.cyan("[litestar-nuxt]"), colors.red("Type generation failed:"), message);
341
- }
342
- return false;
343
- } finally {
344
- isGenerating = false;
345
- }
346
- }
347
- const debouncedRunTypeGeneration = debounce(runTypeGeneration, typesConfig.debounce);
348
- return {
349
- name: "litestar-nuxt-types",
350
- enforce: "pre",
351
- configureServer(devServer) {
352
- server = devServer;
353
- console.log(colors.cyan("[litestar-nuxt]"), colors.dim("Watching for schema changes:"), colors.yellow(typesConfig.openapiPath));
354
- },
355
- async buildStart() {
356
- if (typesConfig.enabled) {
357
- const openapiPath = path.resolve(process.cwd(), typesConfig.openapiPath);
358
- if (fs.existsSync(openapiPath)) {
359
- await runTypeGeneration();
360
- }
361
- }
362
- },
363
- handleHotUpdate({ file }) {
364
- if (!typesConfig.enabled) {
365
- return;
366
- }
367
- const relativePath = path.relative(process.cwd(), file);
368
- const openapiPath = typesConfig.openapiPath.replace(/^\.\//, "");
369
- const routesPath = typesConfig.routesPath.replace(/^\.\//, "");
370
- if (relativePath === openapiPath || relativePath === routesPath || file.endsWith(openapiPath) || file.endsWith(routesPath)) {
371
- console.log(colors.cyan("[litestar-nuxt]"), colors.dim("Schema changed:"), colors.yellow(relativePath));
372
- debouncedRunTypeGeneration();
373
- }
374
- }
375
- };
376
- }
377
150
  function litestarPlugins(userConfig = {}) {
378
151
  const config = resolveConfig(userConfig);
379
152
  const plugins = [createProxyPlugin(config)];
380
153
  if (config.types !== false && config.types.enabled) {
381
- plugins.push(createTypeGenerationPlugin(config.types, config.executor));
154
+ plugins.push(
155
+ createTypeGenerationPlugin(config.types, {
156
+ frameworkName: "litestar-nuxt",
157
+ pluginName: "litestar-nuxt-types",
158
+ clientPlugin: "@hey-api/client-nuxt",
159
+ executor: config.executor
160
+ })
161
+ );
382
162
  }
383
163
  return plugins;
384
164
  }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Shared type generation Vite plugin.
3
+ *
4
+ * Creates a Vite plugin that watches for OpenAPI schema and route metadata changes
5
+ * and regenerates TypeScript types using @hey-api/openapi-ts.
6
+ * Used by Astro, Nuxt, and SvelteKit integrations.
7
+ *
8
+ * @module
9
+ */
10
+ import type { Plugin } from "vite";
11
+ /**
12
+ * Base configuration for type generation.
13
+ */
14
+ export interface BaseTypesConfig {
15
+ /**
16
+ * Enable type generation.
17
+ * @default false
18
+ */
19
+ enabled?: boolean;
20
+ /**
21
+ * Path to output generated TypeScript types.
22
+ */
23
+ output?: string;
24
+ /**
25
+ * Path where the OpenAPI schema is exported by Litestar.
26
+ * @default 'openapi.json'
27
+ */
28
+ openapiPath?: string;
29
+ /**
30
+ * Path where route metadata is exported by Litestar.
31
+ * @default 'routes.json'
32
+ */
33
+ routesPath?: string;
34
+ /**
35
+ * Generate Zod schemas in addition to TypeScript types.
36
+ * @default false
37
+ */
38
+ generateZod?: boolean;
39
+ /**
40
+ * Generate SDK client functions for API calls.
41
+ * @default true
42
+ */
43
+ generateSdk?: boolean;
44
+ /**
45
+ * Debounce time in milliseconds for type regeneration.
46
+ * @default 300
47
+ */
48
+ debounce?: number;
49
+ }
50
+ /**
51
+ * Required version of types config (all fields defined).
52
+ */
53
+ export interface RequiredTypesConfig {
54
+ enabled: boolean;
55
+ output: string;
56
+ openapiPath: string;
57
+ routesPath: string;
58
+ generateZod: boolean;
59
+ generateSdk: boolean;
60
+ debounce: number;
61
+ }
62
+ /**
63
+ * Options for creating the type generation plugin.
64
+ */
65
+ export interface TypeGenPluginOptions {
66
+ /**
67
+ * Framework name for logging (e.g., "litestar-astro", "litestar-nuxt").
68
+ */
69
+ frameworkName: string;
70
+ /**
71
+ * Vite plugin name.
72
+ */
73
+ pluginName: string;
74
+ /**
75
+ * The @hey-api client plugin to use when generating SDK.
76
+ * @default '@hey-api/client-fetch'
77
+ */
78
+ clientPlugin?: string;
79
+ /**
80
+ * Optional executor for running npx/bunx/pnpm dlx commands.
81
+ */
82
+ executor?: string;
83
+ }
84
+ /**
85
+ * Create a Vite plugin for type generation from OpenAPI schemas.
86
+ *
87
+ * @param typesConfig - The type generation configuration
88
+ * @param options - Plugin creation options
89
+ * @returns A Vite plugin that watches for schema changes and regenerates types
90
+ *
91
+ * @example
92
+ * ```typescript
93
+ * const plugin = createTypeGenerationPlugin(
94
+ * { enabled: true, output: 'src/generated' },
95
+ * { frameworkName: 'litestar-astro', pluginName: 'litestar-astro-types' }
96
+ * )
97
+ * ```
98
+ */
99
+ export declare function createTypeGenerationPlugin(typesConfig: RequiredTypesConfig, options: TypeGenPluginOptions): Plugin;
@@ -0,0 +1,110 @@
1
+ import { exec } from "node:child_process";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { promisify } from "node:util";
5
+ import colors from "picocolors";
6
+ import { resolveInstallHint, resolvePackageExecutor } from "../install-hint.js";
7
+ import { debounce } from "./debounce.js";
8
+ import { emitRouteTypes } from "./emit-route-types.js";
9
+ const execAsync = promisify(exec);
10
+ function createTypeGenerationPlugin(typesConfig, options) {
11
+ const { frameworkName, pluginName, clientPlugin = "@hey-api/client-fetch", executor } = options;
12
+ let server = null;
13
+ let isGenerating = false;
14
+ async function runTypeGeneration() {
15
+ if (isGenerating) {
16
+ return false;
17
+ }
18
+ isGenerating = true;
19
+ const startTime = Date.now();
20
+ try {
21
+ const openapiPath = path.resolve(process.cwd(), typesConfig.openapiPath);
22
+ if (!fs.existsSync(openapiPath)) {
23
+ console.log(colors.cyan(`[${frameworkName}]`), colors.yellow("OpenAPI schema not found:"), typesConfig.openapiPath);
24
+ return false;
25
+ }
26
+ console.log(colors.cyan(`[${frameworkName}]`), colors.dim("Generating TypeScript types..."));
27
+ const projectRoot = process.cwd();
28
+ const candidates = [path.resolve(projectRoot, "openapi-ts.config.ts"), path.resolve(projectRoot, "hey-api.config.ts"), path.resolve(projectRoot, ".hey-api.config.ts")];
29
+ const configPath = candidates.find((p) => fs.existsSync(p)) || null;
30
+ let args;
31
+ if (configPath) {
32
+ console.log(colors.cyan(`[${frameworkName}]`), colors.dim("Using config:"), configPath);
33
+ args = ["@hey-api/openapi-ts", "--file", configPath];
34
+ } else {
35
+ args = ["@hey-api/openapi-ts", "-i", typesConfig.openapiPath, "-o", typesConfig.output];
36
+ const plugins = ["@hey-api/typescript", "@hey-api/schemas"];
37
+ if (typesConfig.generateSdk) {
38
+ plugins.push("@hey-api/sdk", clientPlugin);
39
+ }
40
+ if (typesConfig.generateZod) {
41
+ plugins.push("zod");
42
+ }
43
+ if (plugins.length) {
44
+ args.push("--plugins", ...plugins);
45
+ }
46
+ }
47
+ const command = executor ? resolvePackageExecutor(args.join(" "), executor) : `npx ${args.join(" ")}`;
48
+ await execAsync(command, { cwd: projectRoot });
49
+ const routesPath = path.resolve(process.cwd(), typesConfig.routesPath);
50
+ if (fs.existsSync(routesPath)) {
51
+ await emitRouteTypes(routesPath, typesConfig.output, { declareGlobalVars: true });
52
+ }
53
+ const duration = Date.now() - startTime;
54
+ console.log(colors.cyan(`[${frameworkName}]`), colors.green("Types generated"), colors.dim(`in ${duration}ms`));
55
+ if (server) {
56
+ server.ws.send({
57
+ type: "custom",
58
+ event: "litestar:types-updated",
59
+ data: {
60
+ output: typesConfig.output,
61
+ timestamp: Date.now()
62
+ }
63
+ });
64
+ }
65
+ return true;
66
+ } catch (error) {
67
+ const message = error instanceof Error ? error.message : String(error);
68
+ if (message.includes("not found") || message.includes("ENOENT")) {
69
+ console.log(colors.cyan(`[${frameworkName}]`), colors.yellow("@hey-api/openapi-ts not installed"), "- run:", resolveInstallHint());
70
+ } else {
71
+ console.error(colors.cyan(`[${frameworkName}]`), colors.red("Type generation failed:"), message);
72
+ }
73
+ return false;
74
+ } finally {
75
+ isGenerating = false;
76
+ }
77
+ }
78
+ const debouncedRunTypeGeneration = debounce(runTypeGeneration, typesConfig.debounce);
79
+ return {
80
+ name: pluginName,
81
+ enforce: "pre",
82
+ configureServer(devServer) {
83
+ server = devServer;
84
+ console.log(colors.cyan(`[${frameworkName}]`), colors.dim("Watching for schema changes:"), colors.yellow(typesConfig.openapiPath));
85
+ },
86
+ async buildStart() {
87
+ if (typesConfig.enabled) {
88
+ const openapiPath = path.resolve(process.cwd(), typesConfig.openapiPath);
89
+ if (fs.existsSync(openapiPath)) {
90
+ await runTypeGeneration();
91
+ }
92
+ }
93
+ },
94
+ handleHotUpdate({ file }) {
95
+ if (!typesConfig.enabled) {
96
+ return;
97
+ }
98
+ const relativePath = path.relative(process.cwd(), file);
99
+ const openapiPath = typesConfig.openapiPath.replace(/^\.\//, "");
100
+ const routesPath = typesConfig.routesPath.replace(/^\.\//, "");
101
+ if (relativePath === openapiPath || relativePath === routesPath || file.endsWith(openapiPath) || file.endsWith(routesPath)) {
102
+ console.log(colors.cyan(`[${frameworkName}]`), colors.dim("Schema changed:"), colors.yellow(relativePath));
103
+ debouncedRunTypeGeneration();
104
+ }
105
+ }
106
+ };
107
+ }
108
+ export {
109
+ createTypeGenerationPlugin
110
+ };
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Shared route type generation utility.
3
+ *
4
+ * Generates TypeScript types from routes.json metadata for type-safe routing.
5
+ * Used by the main litestar plugin and framework integrations (Astro, Nuxt, SvelteKit).
6
+ *
7
+ * @module
8
+ */
9
+ /**
10
+ * Options for emitRouteTypes.
11
+ */
12
+ export interface EmitRouteTypesOptions {
13
+ /**
14
+ * Whether to register route() on window for global access.
15
+ * Only used by the main plugin.
16
+ * @default false
17
+ */
18
+ globalRoute?: boolean;
19
+ /**
20
+ * Whether to declare global `var routes` and `var serverRoutes`.
21
+ * Used by framework integrations for SSR compatibility.
22
+ * @default false
23
+ */
24
+ declareGlobalVars?: boolean;
25
+ }
26
+ /**
27
+ * Generate TypeScript route types from routes.json metadata.
28
+ *
29
+ * Creates a routes.ts file with:
30
+ * - routesMeta: full route metadata
31
+ * - routes: name -> uri map
32
+ * - serverRoutes: alias of routes
33
+ * - route(): type-safe URL generator
34
+ * - hasRoute(): type guard
35
+ * - CSRF helpers re-exported from litestar-vite-plugin/helpers
36
+ *
37
+ * @param routesPath - Path to routes.json file
38
+ * @param outputDir - Output directory for routes.ts
39
+ * @param options - Generation options
40
+ */
41
+ export declare function emitRouteTypes(routesPath: string, outputDir: string, options?: EmitRouteTypesOptions): Promise<void>;
@@ -0,0 +1,151 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ async function emitRouteTypes(routesPath, outputDir, options = {}) {
4
+ const { globalRoute = false, declareGlobalVars = false } = options;
5
+ const contents = await fs.promises.readFile(routesPath, "utf-8");
6
+ const json = JSON.parse(contents);
7
+ const outDir = path.resolve(process.cwd(), outputDir);
8
+ await fs.promises.mkdir(outDir, { recursive: true });
9
+ const outFile = path.join(outDir, "routes.ts");
10
+ const banner = `// AUTO-GENERATED by litestar-vite. Do not edit.
11
+ /* eslint-disable */
12
+
13
+ `;
14
+ const routesData = json.routes || json;
15
+ const routeNames = Object.keys(routesData);
16
+ const routeNameType = routeNames.length > 0 ? routeNames.map((n) => `"${n}"`).join(" | ") : "never";
17
+ const routeParamTypes = [];
18
+ for (const [name, data] of Object.entries(routesData)) {
19
+ if (data.parameters && data.parameters.length > 0) {
20
+ const params = data.parameters.map((p) => `${p}: string | number`).join("; ");
21
+ routeParamTypes.push(` "${name}": { ${params} }`);
22
+ } else {
23
+ routeParamTypes.push(` "${name}": Record<string, never>`);
24
+ }
25
+ }
26
+ let globalDeclarations = `declare global {
27
+ interface Window {
28
+ /**
29
+ * Fully-typed route metadata injected by Litestar.
30
+ */
31
+ __LITESTAR_ROUTES__?: typeof routesMeta
32
+ /**
33
+ * Simple route map (name -> uri) for legacy consumers.
34
+ */
35
+ routes?: ${declareGlobalVars ? "typeof routes" : "Record<string, string>"}
36
+ serverRoutes?: ${declareGlobalVars ? "typeof serverRoutes" : "Record<string, string>"}`;
37
+ if (globalRoute) {
38
+ globalDeclarations += `
39
+ /**
40
+ * Global route helper (available when globalRoute=true in TypeGenConfig).
41
+ * @see route
42
+ */
43
+ route?: typeof route`;
44
+ }
45
+ globalDeclarations += `
46
+ }`;
47
+ if (declareGlobalVars) {
48
+ globalDeclarations += `
49
+ // eslint-disable-next-line no-var
50
+ var routes: typeof routes | undefined
51
+ var serverRoutes: typeof serverRoutes | undefined`;
52
+ }
53
+ globalDeclarations += `
54
+ }`;
55
+ const globalRouteRegistration = globalRoute ? `
56
+
57
+ // Register route() globally on window for Laravel/Ziggy-style usage
58
+ if (typeof window !== "undefined") {
59
+ window.route = route
60
+ }
61
+ ` : "";
62
+ const body = `/**
63
+ * AUTO-GENERATED by litestar-vite.
64
+ *
65
+ * Exports:
66
+ * - routesMeta: full route metadata
67
+ * - routes: name -> uri map
68
+ * - serverRoutes: alias of routes for clarity in apps
69
+ * - route(): type-safe URL generator
70
+ * - hasRoute(): type guard
71
+ * - csrf helpers re-exported from litestar-vite-plugin/helpers
72
+ *
73
+ * @see https://litestar-vite.litestar.dev/
74
+ */
75
+ export const routesMeta = ${JSON.stringify(json, null, 2)} as const
76
+
77
+ /**
78
+ * Route name to URI mapping.
79
+ */
80
+ export const routes = ${JSON.stringify(Object.fromEntries(Object.entries(routesData).map(([name, data]) => [name, data.uri])), null, 2)} as const
81
+
82
+ /**
83
+ * Alias for server-injected route map (more descriptive for consumers).
84
+ */
85
+ export const serverRoutes = routes
86
+
87
+ /**
88
+ * All available route names.
89
+ */
90
+ export type RouteName = ${routeNameType}
91
+
92
+ /**
93
+ * Parameter types for each route.
94
+ */
95
+ export interface RouteParams {
96
+ ${routeParamTypes.join("\n")}
97
+ }
98
+
99
+ /**
100
+ * Generate a URL for a named route with type-safe parameters.
101
+ *
102
+ * @param name - The route name
103
+ * @param params - Route parameters (required if route has path parameters)
104
+ * @returns The generated URL
105
+ *
106
+ * @example
107
+ * \`\`\`ts
108
+ * import { route } from '@/generated/routes'
109
+ *
110
+ * // Route without parameters
111
+ * route('home') // "/"
112
+ *
113
+ * // Route with parameters
114
+ * route('user:detail', { user_id: 123 }) // "/users/123"
115
+ * \`\`\`
116
+ */
117
+ export function route<T extends RouteName>(
118
+ name: T,
119
+ ...args: RouteParams[T] extends Record<string, never> ? [] : [params: RouteParams[T]]
120
+ ): string {
121
+ let uri = routes[name] as string
122
+ const params = args[0] as Record<string, string | number> | undefined
123
+
124
+ if (params) {
125
+ for (const [key, value] of Object.entries(params)) {
126
+ // Handle both {param} and {param:type} syntax
127
+ uri = uri.replace(new RegExp(\`\\\\{\${key}(?::[^}]+)?\\\\}\`, "g"), String(value))
128
+ }
129
+ }
130
+
131
+ return uri
132
+ }
133
+
134
+ /**
135
+ * Check if a route name exists.
136
+ */
137
+ export function hasRoute(name: string): name is RouteName {
138
+ return name in routes
139
+ }
140
+
141
+ ${globalDeclarations}
142
+
143
+ // Re-export helper functions from litestar-vite-plugin
144
+ // These work with the routes defined above
145
+ export { getCsrfToken, csrfHeaders, csrfFetch } from "litestar-vite-plugin/helpers"
146
+ ${globalRouteRegistration}`;
147
+ await fs.promises.writeFile(outFile, `${banner}${body}`, "utf-8");
148
+ }
149
+ export {
150
+ emitRouteTypes
151
+ };