litestar-vite-plugin 0.15.0 → 0.16.1

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.
@@ -0,0 +1,341 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { writeIfChanged } from "./write-if-changed.js";
4
+ function toImportPath(fromDir, targetFile) {
5
+ const relativePath = path.relative(fromDir, targetFile).replace(/\\/g, "/");
6
+ const withoutExtension = relativePath.replace(/\.d\.ts$/, "").replace(/\.ts$/, "");
7
+ if (withoutExtension.startsWith(".")) {
8
+ return withoutExtension;
9
+ }
10
+ return `./${withoutExtension}`;
11
+ }
12
+ function parseHeyApiTypes(content) {
13
+ const dataTypes = /* @__PURE__ */ new Map();
14
+ const responsesTypes = /* @__PURE__ */ new Set();
15
+ const errorsTypes = /* @__PURE__ */ new Set();
16
+ const urlToDataType = /* @__PURE__ */ new Map();
17
+ const typeBlockRegex = /export\s+type\s+(\w+Data)\s*=\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/gs;
18
+ for (const match of content.matchAll(typeBlockRegex)) {
19
+ const typeName = match[1];
20
+ const typeBody = match[2];
21
+ const urlMatch = typeBody.match(/url:\s*['"]([^'"]+)['"]/);
22
+ const url = urlMatch ? urlMatch[1] : null;
23
+ const hasBody = /\bbody\s*:/.test(typeBody) && !/\bbody\s*\?\s*:\s*never/.test(typeBody);
24
+ const hasPath = /\bpath\s*:/.test(typeBody) && !/\bpath\s*\?\s*:\s*never/.test(typeBody);
25
+ const hasQuery = /\bquery\s*:/.test(typeBody) && !/\bquery\s*\?\s*:\s*never/.test(typeBody);
26
+ const info = { typeName, url, hasBody, hasPath, hasQuery };
27
+ dataTypes.set(typeName, info);
28
+ if (url) {
29
+ urlToDataType.set(url, typeName);
30
+ }
31
+ }
32
+ const responsesRegex = /export\s+type\s+(\w+Responses)\s*=/g;
33
+ for (const match of content.matchAll(responsesRegex)) {
34
+ responsesTypes.add(match[1]);
35
+ }
36
+ const errorsRegex = /export\s+type\s+(\w+Errors)\s*=/g;
37
+ for (const match of content.matchAll(errorsRegex)) {
38
+ errorsTypes.add(match[1]);
39
+ }
40
+ return { dataTypes, responsesTypes, errorsTypes, urlToDataType };
41
+ }
42
+ function normalizePath(path2) {
43
+ return path2.replace(/\{([^:}]+):[^}]+\}/g, "{$1}");
44
+ }
45
+ function createOperationMappings(routes, urlToDataType, responsesTypes, errorsTypes) {
46
+ const mappings = [];
47
+ for (const [routeName, route] of Object.entries(routes)) {
48
+ const normalizedPath = normalizePath(route.uri);
49
+ const dataType = urlToDataType.get(normalizedPath) || null;
50
+ let responsesType = null;
51
+ let errorsType = null;
52
+ if (dataType) {
53
+ const baseName = dataType.replace(/Data$/, "");
54
+ const candidateResponses = `${baseName}Responses`;
55
+ const candidateErrors = `${baseName}Errors`;
56
+ if (responsesTypes.has(candidateResponses)) {
57
+ responsesType = candidateResponses;
58
+ }
59
+ if (errorsTypes.has(candidateErrors)) {
60
+ errorsType = candidateErrors;
61
+ }
62
+ }
63
+ mappings.push({
64
+ routeName,
65
+ path: route.uri,
66
+ method: route.method,
67
+ dataType,
68
+ responsesType,
69
+ errorsType
70
+ });
71
+ }
72
+ return mappings.sort((a, b) => a.routeName.localeCompare(b.routeName));
73
+ }
74
+ function generateSchemasTs(mappings, apiTypesImportPath) {
75
+ const validMappings = mappings.filter((m) => m.dataType || m.responsesType);
76
+ if (validMappings.length === 0) {
77
+ return `// AUTO-GENERATED by litestar-vite. Do not edit.
78
+ /* eslint-disable */
79
+
80
+ // Re-export all types from hey-api
81
+ export * from "${apiTypesImportPath}"
82
+
83
+ // No API operations found with matching types.
84
+ // Ensure routes have corresponding hey-api Data/Responses types.
85
+
86
+ /** No operations found */
87
+ export type OperationName = never
88
+
89
+ /** No operation data types */
90
+ export interface OperationDataTypes {}
91
+
92
+ /** No operation response types */
93
+ export interface OperationResponseTypes {}
94
+
95
+ /** Extract request body type - no operations available */
96
+ export type FormInput<T extends OperationName> = never
97
+
98
+ /** Extract response type - no operations available */
99
+ export type FormResponse<T extends OperationName, _Status extends number> = never
100
+
101
+ /** Extract success response - no operations available */
102
+ export type SuccessResponse<T extends OperationName> = never
103
+ `;
104
+ }
105
+ const dataTypeImports = [...new Set(validMappings.map((m) => m.dataType).filter(Boolean))];
106
+ const responsesTypeImports = [...new Set(validMappings.map((m) => m.responsesType).filter(Boolean))];
107
+ const errorsTypeImports = [...new Set(validMappings.map((m) => m.errorsType).filter(Boolean))];
108
+ const allImports = [...dataTypeImports, ...responsesTypeImports, ...errorsTypeImports].sort();
109
+ const operationNames = validMappings.map((m) => ` | '${m.routeName}'`).join("\n");
110
+ const dataTypeEntries = validMappings.filter((m) => m.dataType).map((m) => ` '${m.routeName}': ${m.dataType}`).join("\n");
111
+ const responsesTypeEntries = validMappings.filter((m) => m.responsesType).map((m) => ` '${m.routeName}': ${m.responsesType}`).join("\n");
112
+ const errorsTypeEntries = validMappings.map((m) => ` '${m.routeName}': ${m.errorsType || "never"}`).join("\n");
113
+ return `// AUTO-GENERATED by litestar-vite. Do not edit.
114
+ /* eslint-disable */
115
+
116
+ // Re-export all types from hey-api for direct access
117
+ export * from "${apiTypesImportPath}"
118
+
119
+ // Import specific operation types for mapping
120
+ import type {
121
+ ${allImports.join(",\n ")}
122
+ } from "${apiTypesImportPath}"
123
+
124
+ // ============================================================================
125
+ // Operation Name Registry
126
+ // ============================================================================
127
+
128
+ /**
129
+ * All available API operation names.
130
+ * These match the route names from routes.ts for consistency.
131
+ *
132
+ * @example
133
+ * import type { OperationName } from './generated/schemas'
134
+ * const op: OperationName = 'api:login'
135
+ */
136
+ export type OperationName =
137
+ ${operationNames}
138
+
139
+ /**
140
+ * Mapping of operation names to hey-api Data types.
141
+ * Data types contain body, path, query, and url properties.
142
+ */
143
+ export interface OperationDataTypes {
144
+ ${dataTypeEntries}
145
+ }
146
+
147
+ /**
148
+ * Mapping of operation names to hey-api Responses types.
149
+ * Responses types map status codes to response shapes.
150
+ */
151
+ export interface OperationResponseTypes {
152
+ ${responsesTypeEntries}
153
+ }
154
+
155
+ /**
156
+ * Mapping of operation names to hey-api Error types.
157
+ * Error types represent non-2xx responses.
158
+ */
159
+ export interface OperationErrorTypes {
160
+ ${errorsTypeEntries}
161
+ }
162
+
163
+ // ============================================================================
164
+ // Ergonomic Type Helpers
165
+ // ============================================================================
166
+
167
+ /**
168
+ * Extract request body type for an operation.
169
+ * Use this for form data typing.
170
+ *
171
+ * @example
172
+ * import { useForm } from '@inertiajs/react'
173
+ * import type { FormInput } from './generated/schemas'
174
+ *
175
+ * const form = useForm<FormInput<'api:login'>>({
176
+ * username: '',
177
+ * password: '',
178
+ * })
179
+ */
180
+ export type FormInput<T extends OperationName> =
181
+ T extends keyof OperationDataTypes
182
+ ? OperationDataTypes[T] extends { body: infer B }
183
+ ? B extends never ? never : B
184
+ : never
185
+ : never
186
+
187
+ /**
188
+ * Extract path parameters type for an operation.
189
+ *
190
+ * @example
191
+ * type BookParams = PathParams<'api:book_detail'> // { book_id: number }
192
+ */
193
+ export type PathParams<T extends OperationName> =
194
+ T extends keyof OperationDataTypes
195
+ ? OperationDataTypes[T] extends { path: infer P }
196
+ ? P extends never ? never : P
197
+ : never
198
+ : never
199
+
200
+ /**
201
+ * Extract query parameters type for an operation.
202
+ *
203
+ * @example
204
+ * type SearchQuery = QueryParams<'api:books'> // { q?: string; limit?: number }
205
+ */
206
+ export type QueryParams<T extends OperationName> =
207
+ T extends keyof OperationDataTypes
208
+ ? OperationDataTypes[T] extends { query: infer Q }
209
+ ? Q extends never ? never : Q
210
+ : never
211
+ : never
212
+
213
+ /**
214
+ * Extract response type for a specific HTTP status code.
215
+ *
216
+ * @example
217
+ * type LoginSuccess = FormResponse<'api:login', 201> // { access_token: string; ... }
218
+ * type LoginError = FormResponse<'api:login', 400> // { detail: string; ... }
219
+ */
220
+ export type FormResponse<T extends OperationName, Status extends number> =
221
+ T extends keyof OperationResponseTypes
222
+ ? Status extends keyof OperationResponseTypes[T]
223
+ ? OperationResponseTypes[T][Status]
224
+ : never
225
+ : never
226
+
227
+ /**
228
+ * Extract successful response type (200 or 201).
229
+ * Convenience helper for the most common case.
230
+ *
231
+ * @example
232
+ * type Books = SuccessResponse<'api:books'> // Book[]
233
+ * type NewBook = SuccessResponse<'api:book_create'> // Book
234
+ */
235
+ export type SuccessResponse<T extends OperationName> =
236
+ T extends keyof OperationResponseTypes
237
+ ? OperationResponseTypes[T] extends { 200: infer R }
238
+ ? R
239
+ : OperationResponseTypes[T] extends { 201: infer R }
240
+ ? R
241
+ : OperationResponseTypes[T] extends { 204: infer R }
242
+ ? R
243
+ : never
244
+ : never
245
+
246
+ /**
247
+ * Extract error response type.
248
+ * Returns the union of all error response types for an operation.
249
+ *
250
+ * @example
251
+ * type LoginErrors = ErrorResponse<'api:login'> // { detail: string; ... }
252
+ */
253
+ export type ErrorResponse<T extends OperationName> = OperationErrorTypes[T]
254
+
255
+ /**
256
+ * Extract all possible response types as a union.
257
+ *
258
+ * @example
259
+ * type AllResponses = AnyResponse<'api:login'> // success | error union
260
+ */
261
+ export type AnyResponse<T extends OperationName> =
262
+ T extends keyof OperationResponseTypes
263
+ ? OperationResponseTypes[T][keyof OperationResponseTypes[T]]
264
+ : never
265
+
266
+ // ============================================================================
267
+ // Utility Types for Forms
268
+ // ============================================================================
269
+
270
+ /**
271
+ * Make all form input properties optional.
272
+ * Useful for initializing form state with empty values.
273
+ *
274
+ * @example
275
+ * const initialData: PartialFormInput<'api:login'> = {}
276
+ */
277
+ export type PartialFormInput<T extends OperationName> = Partial<FormInput<T>>
278
+
279
+ /**
280
+ * Inertia-style form state with validation errors.
281
+ *
282
+ * @example
283
+ * const formState: FormState<'api:login'> = {
284
+ * data: { username: '', password: '' },
285
+ * errors: {},
286
+ * processing: false,
287
+ * wasSuccessful: false,
288
+ * recentlySuccessful: false,
289
+ * }
290
+ */
291
+ export interface FormState<T extends OperationName> {
292
+ data: FormInput<T>
293
+ errors: Partial<Record<keyof FormInput<T>, string | string[]>>
294
+ processing: boolean
295
+ wasSuccessful: boolean
296
+ recentlySuccessful: boolean
297
+ }
298
+
299
+ /**
300
+ * Check if an operation has a request body.
301
+ */
302
+ export type HasBody<T extends OperationName> = FormInput<T> extends never ? false : true
303
+
304
+ /**
305
+ * Check if an operation has path parameters.
306
+ */
307
+ export type HasPathParams<T extends OperationName> = PathParams<T> extends never ? false : true
308
+
309
+ /**
310
+ * Check if an operation has query parameters.
311
+ */
312
+ export type HasQueryParams<T extends OperationName> = QueryParams<T> extends never ? false : true
313
+ `;
314
+ }
315
+ async function emitSchemasTypes(routesJsonPath, outputDir, schemasOutputPath) {
316
+ const outDir = path.resolve(process.cwd(), outputDir);
317
+ const outFile = schemasOutputPath ? path.resolve(process.cwd(), schemasOutputPath) : path.join(outDir, "schemas.ts");
318
+ const schemasDir = path.dirname(outFile);
319
+ const apiTypesPath = path.join(outDir, "api", "types.gen.ts");
320
+ const apiTypesImportPath = toImportPath(schemasDir, apiTypesPath);
321
+ if (!fs.existsSync(apiTypesPath)) {
322
+ console.warn("litestar-vite: api/types.gen.ts not found, skipping schemas.ts generation");
323
+ return false;
324
+ }
325
+ if (!fs.existsSync(routesJsonPath)) {
326
+ console.warn("litestar-vite: routes.json not found, skipping schemas.ts generation");
327
+ return false;
328
+ }
329
+ const routesContent = await fs.promises.readFile(routesJsonPath, "utf-8");
330
+ const routesJson = JSON.parse(routesContent);
331
+ const typesContent = await fs.promises.readFile(apiTypesPath, "utf-8");
332
+ const { urlToDataType, responsesTypes, errorsTypes } = parseHeyApiTypes(typesContent);
333
+ const mappings = createOperationMappings(routesJson.routes, urlToDataType, responsesTypes, errorsTypes);
334
+ const content = generateSchemasTs(mappings, apiTypesImportPath);
335
+ await fs.promises.mkdir(schemasDir, { recursive: true });
336
+ const result = await writeIfChanged(outFile, content, { encoding: "utf-8" });
337
+ return result.changed;
338
+ }
339
+ export {
340
+ emitSchemasTypes
341
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Network utilities for URL and host normalization.
3
+ */
4
+ /**
5
+ * Normalizes a host address for URL construction.
6
+ *
7
+ * Handles various host formats to produce browser-compatible URLs:
8
+ * - Converts bind-all addresses (::, 0.0.0.0) to localhost
9
+ * - Converts IPv4/IPv6 localhost addresses (::1, 127.0.0.1) to localhost
10
+ * - Wraps other IPv6 addresses in brackets for URL compatibility
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * normalizeHost("::") // => "localhost"
15
+ * normalizeHost("0.0.0.0") // => "localhost"
16
+ * normalizeHost("::1") // => "localhost"
17
+ * normalizeHost("127.0.0.1") // => "localhost"
18
+ * normalizeHost("fe80::1") // => "[fe80::1]"
19
+ * normalizeHost("[fe80::1]") // => "[fe80::1]" (already bracketed)
20
+ * normalizeHost("192.168.1.1") // => "192.168.1.1"
21
+ * ```
22
+ */
23
+ export declare function normalizeHost(host: string): string;
24
+ /**
25
+ * Resolve the absolute hot file path from bundleDir + hotFile.
26
+ *
27
+ * Python config stores hot_file as a filename (relative to bundle_dir) by default.
28
+ * If hotFile already includes bundleDir, avoid double-prefixing.
29
+ */
30
+ export declare function resolveHotFilePath(bundleDir: string, hotFile: string, rootDir?: string): string;
@@ -0,0 +1,25 @@
1
+ import path from "node:path";
2
+ function normalizeHost(host) {
3
+ if (host === "::" || host === "::1" || host === "0.0.0.0" || host === "127.0.0.1") {
4
+ return "localhost";
5
+ }
6
+ if (host.includes(":") && !host.startsWith("[")) {
7
+ return `[${host}]`;
8
+ }
9
+ return host;
10
+ }
11
+ function resolveHotFilePath(bundleDir, hotFile, rootDir = process.cwd()) {
12
+ if (path.isAbsolute(hotFile)) {
13
+ return hotFile;
14
+ }
15
+ const normalizedHot = hotFile.replace(/^\/+/, "");
16
+ const normalizedBundle = bundleDir.replace(/^\/+/, "").replace(/\/+$/, "");
17
+ if (normalizedBundle && normalizedHot.startsWith(`${normalizedBundle}/`)) {
18
+ return path.resolve(rootDir, normalizedHot);
19
+ }
20
+ return path.resolve(rootDir, normalizedBundle, normalizedHot);
21
+ }
22
+ export {
23
+ normalizeHost,
24
+ resolveHotFilePath
25
+ };
@@ -10,12 +10,18 @@ export interface TypeGenCoreConfig {
10
10
  output: string;
11
11
  /** Path to inertia-pages.json (relative or absolute) */
12
12
  pagePropsPath: string;
13
+ /** Path to routes.json (relative or absolute) */
14
+ routesPath: string;
13
15
  /** Whether to generate SDK client */
14
16
  generateSdk: boolean;
15
17
  /** Whether to generate Zod schemas */
16
18
  generateZod: boolean;
17
19
  /** Whether to generate page props types */
18
20
  generatePageProps: boolean;
21
+ /** Whether to generate schema helper types (schemas.ts) */
22
+ generateSchemas: boolean;
23
+ /** Optional path for schemas.ts output */
24
+ schemasTsPath?: string;
19
25
  /** SDK client plugin (e.g., "@hey-api/client-fetch") */
20
26
  sdkClientPlugin: string;
21
27
  /** JS runtime executor (e.g., "bun", "pnpm") */
@@ -5,6 +5,7 @@ import path from "node:path";
5
5
  import { promisify } from "node:util";
6
6
  import { resolveInstallHint, resolvePackageExecutor } from "../install-hint.js";
7
7
  import { emitPagePropsTypes } from "./emit-page-props-types.js";
8
+ import { emitSchemasTypes } from "./emit-schemas-types.js";
8
9
  const execAsync = promisify(exec);
9
10
  const nodeRequire = createRequire(import.meta.url);
10
11
  function findOpenApiTsConfig(projectRoot) {
@@ -109,6 +110,26 @@ async function runTypeGeneration(config, options = {}) {
109
110
  logger?.error(`Page props generation failed: ${message}`);
110
111
  }
111
112
  }
113
+ const { generateSchemas, routesPath, schemasTsPath } = config;
114
+ if (generateSchemas && routesPath) {
115
+ const absoluteRoutesPath = path.resolve(projectRoot, routesPath);
116
+ if (fs.existsSync(absoluteRoutesPath)) {
117
+ try {
118
+ const changed = await emitSchemasTypes(absoluteRoutesPath, output, schemasTsPath);
119
+ const schemasOutput = schemasTsPath ?? path.join(output, "schemas.ts");
120
+ if (changed) {
121
+ result.generatedFiles.push(schemasOutput);
122
+ result.generated = true;
123
+ } else {
124
+ result.skippedFiles.push(schemasOutput);
125
+ }
126
+ } catch (error) {
127
+ const message = error instanceof Error ? error.message : String(error);
128
+ result.errors.push(`Schema types generation failed: ${message}`);
129
+ logger?.error(`Schema types generation failed: ${message}`);
130
+ }
131
+ }
132
+ }
112
133
  } finally {
113
134
  result.durationMs = Date.now() - startTime;
114
135
  }
@@ -5,10 +5,12 @@ export interface RequiredTypeGenConfig {
5
5
  openapiPath: string;
6
6
  routesPath: string;
7
7
  pagePropsPath: string;
8
+ schemasTsPath?: string;
8
9
  generateZod: boolean;
9
10
  generateSdk: boolean;
10
11
  generateRoutes: boolean;
11
12
  generatePageProps: boolean;
13
+ generateSchemas: boolean;
12
14
  globalRoute: boolean;
13
15
  debounce: number;
14
16
  }
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import colors from "picocolors";
4
4
  import { debounce } from "./debounce.js";
5
5
  import { emitPagePropsTypes } from "./emit-page-props-types.js";
6
+ import { emitSchemasTypes } from "./emit-schemas-types.js";
6
7
  import { formatPath } from "./format-path.js";
7
8
  import { shouldRunOpenApiTs, updateOpenApiTsCache } from "./typegen-cache.js";
8
9
  import { buildHeyApiPlugins, findOpenApiTsConfig, runHeyApiGeneration } from "./typegen-core.js";
@@ -60,11 +61,13 @@ function createLitestarTypeGenPlugin(typesConfig, options) {
60
61
  projectRoot,
61
62
  openapiPath: typesConfig.openapiPath,
62
63
  output: typesConfig.output,
64
+ routesPath: typesConfig.routesPath,
63
65
  pagePropsPath: typesConfig.pagePropsPath,
64
66
  generateSdk: typesConfig.generateSdk,
65
67
  generateZod: typesConfig.generateZod,
66
68
  generatePageProps: false,
67
69
  // Handle separately below
70
+ generateSchemas: typesConfig.generateSchemas,
68
71
  sdkClientPlugin,
69
72
  executor
70
73
  };
@@ -97,6 +100,20 @@ function createLitestarTypeGenPlugin(typesConfig, options) {
97
100
  logger.error(`Page props generation failed: ${message}`);
98
101
  }
99
102
  }
103
+ const absoluteRoutesPath = path.resolve(projectRoot, typesConfig.routesPath);
104
+ if (typesConfig.generateSchemas && fs.existsSync(absoluteRoutesPath)) {
105
+ try {
106
+ const changed = await emitSchemasTypes(absoluteRoutesPath, typesConfig.output, typesConfig.schemasTsPath);
107
+ if (changed) {
108
+ generated = true;
109
+ } else {
110
+ resolvedConfig?.logger.info(`${colors.cyan("\u2022")} Schema types ${colors.dim("(unchanged)")}`);
111
+ }
112
+ } catch (error) {
113
+ const message = error instanceof Error ? error.message : String(error);
114
+ logger.error(`Schema types generation failed: ${message}`);
115
+ }
116
+ }
100
117
  if (generated && resolvedConfig) {
101
118
  const duration = Date.now() - startTime;
102
119
  resolvedConfig.logger.info(`${colors.green("\u2713")} TypeScript artifacts updated ${colors.dim(`(${duration}ms)`)}`);
@@ -18,10 +18,7 @@
18
18
  * plugins: [
19
19
  * litestarSvelteKit({
20
20
  * apiProxy: 'http://localhost:8000',
21
- * types: {
22
- * enabled: true,
23
- * output: 'src/lib/generated',
24
- * },
21
+ * types: true,
25
22
  * }),
26
23
  * sveltekit(), // SvelteKit plugin comes after
27
24
  * ],
@@ -51,19 +48,25 @@ export interface SvelteKitTypesConfig {
51
48
  /**
52
49
  * Path where the OpenAPI schema is exported by Litestar.
53
50
  *
54
- * @default 'openapi.json'
51
+ * @default `${output}/openapi.json`
55
52
  */
56
53
  openapiPath?: string;
57
54
  /**
58
55
  * Path where route metadata is exported by Litestar.
59
56
  *
60
- * @default 'routes.json'
57
+ * @default `${output}/routes.json`
61
58
  */
62
59
  routesPath?: string;
60
+ /**
61
+ * Optional path for the generated schemas.ts helper file.
62
+ *
63
+ * @default `${output}/schemas.ts`
64
+ */
65
+ schemasTsPath?: string;
63
66
  /**
64
67
  * Path where Inertia page props metadata is exported by Litestar.
65
68
  *
66
- * @default 'inertia-pages.json'
69
+ * @default `${output}/inertia-pages.json`
67
70
  */
68
71
  pagePropsPath?: string;
69
72
  /**
@@ -90,6 +93,12 @@ export interface SvelteKitTypesConfig {
90
93
  * @default true
91
94
  */
92
95
  generatePageProps?: boolean;
96
+ /**
97
+ * Generate schemas.ts with ergonomic form/response type helpers.
98
+ *
99
+ * @default true
100
+ */
101
+ generateSchemas?: boolean;
93
102
  /**
94
103
  * Register route() globally on window object.
95
104
  *
@@ -2,6 +2,8 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import colors from "picocolors";
4
4
  import { readBridgeConfig } from "./shared/bridge-schema.js";
5
+ import { DEBOUNCE_MS } from "./shared/constants.js";
6
+ import { normalizeHost, resolveHotFilePath } from "./shared/network.js";
5
7
  import { createLitestarTypeGenPlugin } from "./shared/typegen-plugin.js";
6
8
  function resolveConfig(config = {}) {
7
9
  let hotFile;
@@ -21,7 +23,7 @@ function resolveConfig(config = {}) {
21
23
  if (runtime) {
22
24
  hasPythonConfig = true;
23
25
  const hot = runtime.hotFile;
24
- hotFile = path.isAbsolute(hot) ? hot : path.resolve(process.cwd(), runtime.bundleDir, hot);
26
+ hotFile = resolveHotFilePath(runtime.bundleDir, hot);
25
27
  proxyMode = runtime.proxyMode;
26
28
  port = runtime.port;
27
29
  pythonExecutor = runtime.executor;
@@ -30,47 +32,71 @@ function resolveConfig(config = {}) {
30
32
  }
31
33
  }
32
34
  let typesConfig = false;
35
+ const defaultTypesOutput = "src/lib/generated";
36
+ const buildTypeDefaults = (output) => ({
37
+ openapiPath: path.join(output, "openapi.json"),
38
+ routesPath: path.join(output, "routes.json"),
39
+ pagePropsPath: path.join(output, "inertia-pages.json"),
40
+ schemasTsPath: path.join(output, "schemas.ts")
41
+ });
33
42
  if (config.types === true) {
43
+ const output = pythonTypesConfig?.output ?? defaultTypesOutput;
44
+ const defaults = buildTypeDefaults(output);
34
45
  typesConfig = {
35
46
  enabled: true,
36
- output: pythonTypesConfig?.output ?? "src/lib/generated",
37
- openapiPath: pythonTypesConfig?.openapiPath ?? "openapi.json",
38
- routesPath: pythonTypesConfig?.routesPath ?? "routes.json",
39
- pagePropsPath: pythonTypesConfig?.pagePropsPath ?? "inertia-pages.json",
47
+ output,
48
+ openapiPath: pythonTypesConfig?.openapiPath ?? defaults.openapiPath,
49
+ routesPath: pythonTypesConfig?.routesPath ?? defaults.routesPath,
50
+ pagePropsPath: pythonTypesConfig?.pagePropsPath ?? defaults.pagePropsPath,
51
+ schemasTsPath: pythonTypesConfig?.schemasTsPath ?? defaults.schemasTsPath,
40
52
  generateZod: pythonTypesConfig?.generateZod ?? false,
41
53
  generateSdk: pythonTypesConfig?.generateSdk ?? true,
42
54
  generateRoutes: pythonTypesConfig?.generateRoutes ?? true,
43
55
  generatePageProps: pythonTypesConfig?.generatePageProps ?? true,
56
+ generateSchemas: pythonTypesConfig?.generateSchemas ?? true,
44
57
  globalRoute: pythonTypesConfig?.globalRoute ?? false,
45
- debounce: 300
58
+ debounce: DEBOUNCE_MS
46
59
  };
47
60
  } else if (typeof config.types === "object" && config.types !== null) {
61
+ const userProvidedOutput = Object.hasOwn(config.types, "output");
62
+ const output = config.types.output ?? pythonTypesConfig?.output ?? defaultTypesOutput;
63
+ const defaults = buildTypeDefaults(output);
64
+ const openapiFallback = userProvidedOutput ? defaults.openapiPath : pythonTypesConfig?.openapiPath ?? defaults.openapiPath;
65
+ const routesFallback = userProvidedOutput ? defaults.routesPath : pythonTypesConfig?.routesPath ?? defaults.routesPath;
66
+ const pagePropsFallback = userProvidedOutput ? defaults.pagePropsPath : pythonTypesConfig?.pagePropsPath ?? defaults.pagePropsPath;
67
+ const schemasFallback = userProvidedOutput ? defaults.schemasTsPath : pythonTypesConfig?.schemasTsPath ?? defaults.schemasTsPath;
48
68
  typesConfig = {
49
69
  enabled: config.types.enabled ?? true,
50
- output: config.types.output ?? pythonTypesConfig?.output ?? "src/lib/generated",
51
- openapiPath: config.types.openapiPath ?? pythonTypesConfig?.openapiPath ?? "openapi.json",
52
- routesPath: config.types.routesPath ?? pythonTypesConfig?.routesPath ?? "routes.json",
53
- pagePropsPath: config.types.pagePropsPath ?? pythonTypesConfig?.pagePropsPath ?? "inertia-pages.json",
70
+ output,
71
+ openapiPath: config.types.openapiPath ?? openapiFallback,
72
+ routesPath: config.types.routesPath ?? routesFallback,
73
+ pagePropsPath: config.types.pagePropsPath ?? pagePropsFallback,
74
+ schemasTsPath: config.types.schemasTsPath ?? schemasFallback,
54
75
  generateZod: config.types.generateZod ?? pythonTypesConfig?.generateZod ?? false,
55
76
  generateSdk: config.types.generateSdk ?? pythonTypesConfig?.generateSdk ?? true,
56
77
  generateRoutes: config.types.generateRoutes ?? pythonTypesConfig?.generateRoutes ?? true,
57
78
  generatePageProps: config.types.generatePageProps ?? pythonTypesConfig?.generatePageProps ?? true,
79
+ generateSchemas: config.types.generateSchemas ?? pythonTypesConfig?.generateSchemas ?? true,
58
80
  globalRoute: config.types.globalRoute ?? pythonTypesConfig?.globalRoute ?? false,
59
- debounce: config.types.debounce ?? 300
81
+ debounce: config.types.debounce ?? DEBOUNCE_MS
60
82
  };
61
83
  } else if (config.types !== false && pythonTypesConfig?.enabled) {
84
+ const output = pythonTypesConfig.output ?? defaultTypesOutput;
85
+ const defaults = buildTypeDefaults(output);
62
86
  typesConfig = {
63
87
  enabled: true,
64
- output: pythonTypesConfig.output ?? "src/lib/generated",
65
- openapiPath: pythonTypesConfig.openapiPath ?? "openapi.json",
66
- routesPath: pythonTypesConfig.routesPath ?? "routes.json",
67
- pagePropsPath: pythonTypesConfig.pagePropsPath ?? "inertia-pages.json",
88
+ output,
89
+ openapiPath: pythonTypesConfig.openapiPath ?? defaults.openapiPath,
90
+ routesPath: pythonTypesConfig.routesPath ?? defaults.routesPath,
91
+ pagePropsPath: pythonTypesConfig.pagePropsPath ?? defaults.pagePropsPath,
92
+ schemasTsPath: pythonTypesConfig.schemasTsPath ?? defaults.schemasTsPath,
68
93
  generateZod: pythonTypesConfig.generateZod ?? false,
69
94
  generateSdk: pythonTypesConfig.generateSdk ?? true,
70
95
  generateRoutes: pythonTypesConfig.generateRoutes ?? true,
71
96
  generatePageProps: pythonTypesConfig.generatePageProps ?? true,
97
+ generateSchemas: pythonTypesConfig.generateSchemas ?? true,
72
98
  globalRoute: pythonTypesConfig.globalRoute ?? false,
73
- debounce: 300
99
+ debounce: DEBOUNCE_MS
74
100
  };
75
101
  }
76
102
  return {
@@ -127,12 +153,7 @@ function litestarSvelteKit(userConfig = {}) {
127
153
  server.httpServer?.once("listening", () => {
128
154
  const address = server.httpServer?.address();
129
155
  if (address && typeof address === "object" && "port" in address) {
130
- let host = address.address;
131
- if (host === "::" || host === "::1") {
132
- host = "localhost";
133
- } else if (host.includes(":")) {
134
- host = `[${host}]`;
135
- }
156
+ const host = normalizeHost(address.address);
136
157
  const url = `http://${host}:${address.port}`;
137
158
  fs.mkdirSync(path.dirname(hotFile), { recursive: true });
138
159
  fs.writeFileSync(hotFile, url);