litestar-vite-plugin 0.15.0 → 0.16.0
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/astro.d.ts +17 -8
- package/dist/js/astro.js +43 -18
- package/dist/js/dev-server-index.html +115 -163
- package/dist/js/index.d.ts +19 -5
- package/dist/js/index.js +41 -18
- package/dist/js/nuxt.d.ts +16 -7
- package/dist/js/nuxt.js +42 -25
- package/dist/js/shared/bridge-schema.d.ts +3 -0
- package/dist/js/shared/bridge-schema.js +22 -0
- package/dist/js/shared/constants.d.ts +33 -0
- package/dist/js/shared/constants.js +10 -0
- package/dist/js/shared/emit-schemas-types.d.ts +8 -0
- package/dist/js/shared/emit-schemas-types.js +341 -0
- package/dist/js/shared/network.d.ts +30 -0
- package/dist/js/shared/network.js +25 -0
- package/dist/js/shared/typegen-core.d.ts +6 -0
- package/dist/js/shared/typegen-core.js +21 -0
- package/dist/js/shared/typegen-plugin.d.ts +2 -0
- package/dist/js/shared/typegen-plugin.js +17 -0
- package/dist/js/sveltekit.d.ts +16 -7
- package/dist/js/sveltekit.js +43 -22
- package/dist/js/typegen-cli.js +6 -2
- package/package.json +9 -4
|
@@ -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)`)}`);
|
package/dist/js/sveltekit.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
*
|
package/dist/js/sveltekit.js
CHANGED
|
@@ -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 =
|
|
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
|
|
37
|
-
openapiPath: pythonTypesConfig?.openapiPath ??
|
|
38
|
-
routesPath: pythonTypesConfig?.routesPath ??
|
|
39
|
-
pagePropsPath: pythonTypesConfig?.pagePropsPath ??
|
|
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:
|
|
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
|
|
51
|
-
openapiPath: config.types.openapiPath ??
|
|
52
|
-
routesPath: config.types.routesPath ??
|
|
53
|
-
pagePropsPath: config.types.pagePropsPath ??
|
|
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 ??
|
|
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
|
|
65
|
-
openapiPath: pythonTypesConfig.openapiPath ??
|
|
66
|
-
routesPath: pythonTypesConfig.routesPath ??
|
|
67
|
-
pagePropsPath: pythonTypesConfig.pagePropsPath ??
|
|
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:
|
|
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
|
-
|
|
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);
|