litestar-vite 0.15.0__py3-none-any.whl → 0.15.0rc2__py3-none-any.whl

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.
Files changed (55) hide show
  1. litestar_vite/_codegen/__init__.py +26 -0
  2. litestar_vite/_codegen/inertia.py +407 -0
  3. litestar_vite/{codegen/_openapi.py → _codegen/openapi.py} +11 -58
  4. litestar_vite/{codegen/_routes.py → _codegen/routes.py} +43 -110
  5. litestar_vite/{codegen/_ts.py → _codegen/ts.py} +19 -19
  6. litestar_vite/_handler/__init__.py +8 -0
  7. litestar_vite/{handler/_app.py → _handler/app.py} +29 -117
  8. litestar_vite/cli.py +254 -155
  9. litestar_vite/codegen.py +39 -0
  10. litestar_vite/commands.py +6 -0
  11. litestar_vite/{config/__init__.py → config.py} +726 -99
  12. litestar_vite/deploy.py +3 -14
  13. litestar_vite/doctor.py +6 -8
  14. litestar_vite/executor.py +1 -45
  15. litestar_vite/handler.py +9 -0
  16. litestar_vite/html_transform.py +5 -148
  17. litestar_vite/inertia/__init__.py +0 -24
  18. litestar_vite/inertia/_utils.py +0 -5
  19. litestar_vite/inertia/exception_handler.py +16 -22
  20. litestar_vite/inertia/helpers.py +18 -546
  21. litestar_vite/inertia/plugin.py +11 -77
  22. litestar_vite/inertia/request.py +0 -48
  23. litestar_vite/inertia/response.py +17 -113
  24. litestar_vite/inertia/types.py +0 -19
  25. litestar_vite/loader.py +7 -7
  26. litestar_vite/plugin.py +2184 -0
  27. litestar_vite/templates/angular/package.json.j2 +1 -2
  28. litestar_vite/templates/angular-cli/package.json.j2 +1 -2
  29. litestar_vite/templates/base/package.json.j2 +1 -2
  30. litestar_vite/templates/react-inertia/package.json.j2 +1 -2
  31. litestar_vite/templates/vue-inertia/package.json.j2 +1 -2
  32. {litestar_vite-0.15.0.dist-info → litestar_vite-0.15.0rc2.dist-info}/METADATA +5 -5
  33. {litestar_vite-0.15.0.dist-info → litestar_vite-0.15.0rc2.dist-info}/RECORD +36 -49
  34. litestar_vite/codegen/__init__.py +0 -48
  35. litestar_vite/codegen/_export.py +0 -229
  36. litestar_vite/codegen/_inertia.py +0 -619
  37. litestar_vite/codegen/_utils.py +0 -141
  38. litestar_vite/config/_constants.py +0 -97
  39. litestar_vite/config/_deploy.py +0 -70
  40. litestar_vite/config/_inertia.py +0 -241
  41. litestar_vite/config/_paths.py +0 -63
  42. litestar_vite/config/_runtime.py +0 -235
  43. litestar_vite/config/_spa.py +0 -93
  44. litestar_vite/config/_types.py +0 -94
  45. litestar_vite/handler/__init__.py +0 -9
  46. litestar_vite/inertia/precognition.py +0 -274
  47. litestar_vite/plugin/__init__.py +0 -687
  48. litestar_vite/plugin/_process.py +0 -185
  49. litestar_vite/plugin/_proxy.py +0 -689
  50. litestar_vite/plugin/_proxy_headers.py +0 -244
  51. litestar_vite/plugin/_static.py +0 -37
  52. litestar_vite/plugin/_utils.py +0 -489
  53. /litestar_vite/{handler/_routing.py → _handler/routing.py} +0 -0
  54. {litestar_vite-0.15.0.dist-info → litestar_vite-0.15.0rc2.dist-info}/WHEEL +0 -0
  55. {litestar_vite-0.15.0.dist-info → litestar_vite-0.15.0rc2.dist-info}/licenses/LICENSE +0 -0
@@ -14,36 +14,10 @@ from litestar._openapi.parameters import ( # pyright: ignore[reportPrivateUsage
14
14
  from litestar.handlers import HTTPRouteHandler
15
15
  from litestar.routes import HTTPRoute
16
16
 
17
- from litestar_vite.codegen._ts import normalize_path, ts_type_from_openapi
17
+ from litestar_vite._codegen.ts import normalize_path, ts_type_from_openapi
18
18
 
19
19
  _PATH_PARAM_EXTRACT_PATTERN = re.compile(r"\{([^:}]+)(?::([^}]+))?\}")
20
20
 
21
- # HTTP methods in priority order for Inertia router integration
22
- _HTTP_METHOD_PRIORITY = ["GET", "POST", "PUT", "PATCH", "DELETE"]
23
-
24
-
25
- def pick_primary_method(methods: list[str]) -> str:
26
- """Pick the primary HTTP method for Inertia router integration.
27
-
28
- When a route supports multiple HTTP methods, this picks the most
29
- appropriate one for use with Inertia's router.visit() and form.submit().
30
-
31
- Args:
32
- methods: List of HTTP methods (e.g., ["GET", "HEAD", "OPTIONS"]).
33
-
34
- Returns:
35
- The primary method in lowercase (e.g., "get", "post").
36
- """
37
- for preferred in _HTTP_METHOD_PRIORITY:
38
- if preferred in methods:
39
- return preferred.lower()
40
- # Fallback to first non-HEAD/OPTIONS method, or "get" if none
41
- for method in methods:
42
- if method not in {"HEAD", "OPTIONS"}:
43
- return method.lower()
44
- return "get"
45
-
46
-
47
21
  _TS_SEMANTIC_ALIASES: dict[str, tuple[str, str]] = {
48
22
  "UUID": ("UUID v4 string", "string"),
49
23
  "DateTime": ("RFC 3339 date-time string", "string"),
@@ -57,7 +31,7 @@ _TS_SEMANTIC_ALIASES: dict[str, tuple[str, str]] = {
57
31
  }
58
32
 
59
33
 
60
- def str_dict_factory() -> dict[str, str]:
34
+ def _str_dict_factory() -> dict[str, str]:
61
35
  """Return an empty ``dict[str, str]`` (typed for pyright).
62
36
 
63
37
  Returns:
@@ -73,13 +47,12 @@ class RouteMetadata:
73
47
  name: str
74
48
  path: str
75
49
  methods: list[str]
76
- method: str # Primary method for Inertia router (lowercase)
77
- params: dict[str, str] = field(default_factory=str_dict_factory)
78
- query_params: dict[str, str] = field(default_factory=str_dict_factory)
50
+ params: dict[str, str] = field(default_factory=_str_dict_factory)
51
+ query_params: dict[str, str] = field(default_factory=_str_dict_factory)
79
52
  component: "str | None" = None
80
53
 
81
54
 
82
- def extract_path_params(path: str) -> dict[str, str]:
55
+ def _extract_path_params(path: str) -> dict[str, str]:
83
56
  """Extract path parameters and their types from a route.
84
57
 
85
58
  Args:
@@ -91,28 +64,22 @@ def extract_path_params(path: str) -> dict[str, str]:
91
64
  return {match.group(1): "string" for match in _PATH_PARAM_EXTRACT_PATTERN.finditer(path)}
92
65
 
93
66
 
94
- def iter_route_handlers(app: Litestar) -> Generator[tuple["HTTPRoute", HTTPRouteHandler], None, None]:
67
+ def _iter_route_handlers(app: Litestar) -> Generator[tuple["HTTPRoute", HTTPRouteHandler], None, None]:
95
68
  """Iterate over HTTP route handlers in an app.
96
69
 
97
- Returns handlers in deterministic order, sorted by (route_path, handler_name)
98
- to ensure consistent output across multiple runs.
99
-
100
70
  Args:
101
71
  app: The Litestar application.
102
72
 
103
73
  Yields:
104
- Tuples of (HTTPRoute, HTTPRouteHandler), sorted for determinism.
74
+ Tuples of (HTTPRoute, HTTPRouteHandler).
105
75
  """
106
- handlers: list[tuple[HTTPRoute, HTTPRouteHandler]] = []
107
76
  for route in app.routes:
108
77
  if isinstance(route, HTTPRoute):
109
- handlers.extend((route, route_handler) for route_handler in route.route_handlers)
110
- # Sort by route path, then handler name for deterministic ordering
111
- handlers.sort(key=lambda x: (str(x[0].path), x[1].handler_name or x[1].name or ""))
112
- yield from handlers
78
+ for route_handler in route.route_handlers:
79
+ yield route, route_handler
113
80
 
114
81
 
115
- def extract_params_from_litestar(
82
+ def _extract_params_from_litestar(
116
83
  handler: HTTPRouteHandler, http_route: "HTTPRoute", openapi_context: OpenAPIContext | None
117
84
  ) -> tuple[dict[str, str], dict[str, str]]:
118
85
  """Extract path and query parameters using Litestar's native OpenAPI generation.
@@ -159,7 +126,7 @@ def extract_params_from_litestar(
159
126
  return path_params, query_params
160
127
 
161
128
 
162
- def make_unique_name(base_name: str, used_names: set[str], path: str, methods: list[str]) -> str:
129
+ def _make_unique_name(base_name: str, used_names: set[str], path: str, methods: list[str]) -> str:
163
130
  """Generate a unique route name, avoiding collisions.
164
131
 
165
132
  Returns:
@@ -210,7 +177,7 @@ def extract_route_metadata(
210
177
  with contextlib.suppress(AttributeError, TypeError, ValueError):
211
178
  openapi_context = OpenAPIContext(openapi_config=app.openapi_config, plugins=app.plugins.openapi)
212
179
 
213
- for http_route, route_handler in iter_route_handlers(app):
180
+ for http_route, route_handler in _iter_route_handlers(app):
214
181
  base_name = route_handler.name or route_handler.handler_name or str(route_handler)
215
182
  methods = [method.upper() for method in route_handler.http_methods]
216
183
 
@@ -231,7 +198,7 @@ def extract_route_metadata(
231
198
  else:
232
199
  continue
233
200
 
234
- route_name = make_unique_name(base_name, used_names, full_path, methods)
201
+ route_name = _make_unique_name(base_name, used_names, full_path, methods)
235
202
  used_names.add(route_name)
236
203
 
237
204
  if only and not any(pattern in route_name or pattern in full_path for pattern in only):
@@ -239,10 +206,10 @@ def extract_route_metadata(
239
206
  if exclude and any(pattern in route_name or pattern in full_path for pattern in exclude):
240
207
  continue
241
208
 
242
- params, query_params = extract_params_from_litestar(route_handler, http_route, openapi_context)
209
+ params, query_params = _extract_params_from_litestar(route_handler, http_route, openapi_context)
243
210
 
244
211
  if not params:
245
- params = extract_path_params(full_path)
212
+ params = _extract_path_params(full_path)
246
213
 
247
214
  normalized_path = normalize_path(full_path)
248
215
 
@@ -254,7 +221,6 @@ def extract_route_metadata(
254
221
  name=route_name,
255
222
  path=normalized_path,
256
223
  methods=methods,
257
- method=pick_primary_method(methods),
258
224
  params=params,
259
225
  query_params=query_params,
260
226
  component=cast("str | None", component),
@@ -274,31 +240,22 @@ def generate_routes_json(
274
240
  ) -> dict[str, Any]:
275
241
  """Generate Ziggy-compatible routes JSON.
276
242
 
277
- The output is deterministic: routes are sorted by name to produce
278
- byte-identical output for the same input data.
279
-
280
243
  Returns:
281
- A Ziggy-compatible routes payload as a dictionary with sorted keys.
244
+ A Ziggy-compatible routes payload as a dictionary.
282
245
  """
283
246
  routes_metadata = extract_route_metadata(app, only=only, exclude=exclude, openapi_schema=openapi_schema)
284
247
 
285
- # Sort routes by name for deterministic output
286
- sorted_routes = sorted(routes_metadata, key=lambda r: r.name)
287
-
288
248
  routes_dict: dict[str, Any] = {}
289
249
 
290
- for route in sorted_routes:
291
- route_data: dict[str, Any] = {"uri": route.path, "methods": route.methods, "method": route.method}
250
+ for route in routes_metadata:
251
+ route_data: dict[str, Any] = {"uri": route.path, "methods": route.methods}
292
252
 
293
253
  if route.params:
294
- # Sort params dict for deterministic output
295
- sorted_params = dict(sorted(route.params.items()))
296
- route_data["parameters"] = list(sorted_params.keys())
297
- route_data["parameterTypes"] = sorted_params
254
+ route_data["parameters"] = list(route.params.keys())
255
+ route_data["parameterTypes"] = route.params
298
256
 
299
257
  if route.query_params:
300
- # Sort query params for deterministic output
301
- route_data["queryParameters"] = dict(sorted(route.query_params.items()))
258
+ route_data["queryParameters"] = route.query_params
302
259
 
303
260
  if include_components and route.component:
304
261
  route_data["component"] = route.component
@@ -330,7 +287,7 @@ _TS_TYPE_MAP: dict[str, str] = {
330
287
  }
331
288
 
332
289
 
333
- def ts_type_for_param(param_type: str) -> str:
290
+ def _ts_type_for_param(param_type: str) -> str:
334
291
  """Map a parameter type string to TypeScript type.
335
292
 
336
293
  Returns:
@@ -346,7 +303,7 @@ def ts_type_for_param(param_type: str) -> str:
346
303
  return ts_type
347
304
 
348
305
 
349
- def is_type_required(param_type: str) -> bool:
306
+ def _is_type_required(param_type: str) -> bool:
350
307
  """Check if a parameter type indicates a required field.
351
308
 
352
309
  Returns:
@@ -355,7 +312,7 @@ def is_type_required(param_type: str) -> bool:
355
312
  return "undefined" not in param_type and not param_type.endswith("?")
356
313
 
357
314
 
358
- def escape_ts_string(s: str) -> str:
315
+ def _escape_ts_string(s: str) -> str:
359
316
  """Escape a string for use in TypeScript string literals.
360
317
 
361
318
  Returns:
@@ -370,53 +327,42 @@ def generate_routes_ts(
370
327
  only: "list[str] | None" = None,
371
328
  exclude: "list[str] | None" = None,
372
329
  openapi_schema: dict[str, Any] | None = None,
373
- global_route: bool = False,
374
330
  ) -> str:
375
331
  """Generate typed routes TypeScript file (Ziggy-style).
376
332
 
377
- The output is deterministic: routes are sorted by name to produce
378
- byte-identical output for the same input data.
379
-
380
333
  Returns:
381
334
  The generated TypeScript source.
382
335
  """
383
336
  routes_metadata = extract_route_metadata(app, only=only, exclude=exclude, openapi_schema=openapi_schema)
384
337
 
385
- # Sort routes by name for deterministic output
386
- sorted_routes = sorted(routes_metadata, key=lambda r: r.name)
387
-
388
338
  route_names: list[str] = []
389
339
  path_params_entries: list[str] = []
390
340
  query_params_entries: list[str] = []
391
341
  routes_entries: list[str] = []
392
342
  used_aliases: set[str] = set()
393
343
 
394
- for route in sorted_routes:
344
+ for route in routes_metadata:
395
345
  route_name = route.name
396
346
  route_names.append(route_name)
397
347
 
398
- # Sort params for deterministic output
399
- sorted_params = dict(sorted(route.params.items())) if route.params else {}
400
- sorted_query_params = dict(sorted(route.query_params.items())) if route.query_params else {}
401
-
402
- if sorted_params:
348
+ if route.params:
403
349
  param_fields: list[str] = []
404
- for param_name, param_type in sorted_params.items():
405
- ts_type = ts_type_for_param(param_type)
350
+ for param_name, param_type in route.params.items():
351
+ ts_type = _ts_type_for_param(param_type)
406
352
  ts_type_clean = ts_type.replace(" | undefined", "")
407
- used_aliases.update(collect_semantic_aliases(ts_type_clean))
353
+ used_aliases.update(_collect_semantic_aliases(ts_type_clean))
408
354
  param_fields.append(f" {param_name}: {ts_type_clean};")
409
355
  path_params_entries.append(f" '{route_name}': {{\n" + "\n".join(param_fields) + "\n };")
410
356
  else:
411
357
  path_params_entries.append(f" '{route_name}': Record<string, never>;")
412
358
 
413
- if sorted_query_params:
359
+ if route.query_params:
414
360
  query_param_fields: list[str] = []
415
- for param_name, param_type in sorted_query_params.items():
416
- ts_type = ts_type_for_param(param_type)
417
- is_required = is_type_required(param_type)
361
+ for param_name, param_type in route.query_params.items():
362
+ ts_type = _ts_type_for_param(param_type)
363
+ is_required = _is_type_required(param_type)
418
364
  ts_type_clean = ts_type.replace(" | undefined", "")
419
- used_aliases.update(collect_semantic_aliases(ts_type_clean))
365
+ used_aliases.update(_collect_semantic_aliases(ts_type_clean))
420
366
  if is_required:
421
367
  query_param_fields.append(f" {param_name}: {ts_type_clean};")
422
368
  else:
@@ -425,37 +371,27 @@ def generate_routes_ts(
425
371
  else:
426
372
  query_params_entries.append(f" '{route_name}': Record<string, never>;")
427
373
 
428
- methods_str = ", ".join(f"'{m}'" for m in sorted(route.methods))
374
+ methods_str = ", ".join(f"'{m}'" for m in route.methods)
429
375
  route_entry_lines = [
430
376
  f" '{route_name}': {{",
431
- f" path: '{escape_ts_string(route.path)}',",
377
+ f" path: '{_escape_ts_string(route.path)}',",
432
378
  f" methods: [{methods_str}] as const,",
433
- f" method: '{route.method}',",
434
379
  ]
435
- param_names_str = ", ".join(f"'{p}'" for p in sorted_params) if sorted_params else ""
380
+ param_names_str = ", ".join(f"'{p}'" for p in route.params) if route.params else ""
436
381
  route_entry_lines.append(f" pathParams: [{param_names_str}] as const,")
437
382
 
438
- query_names_str = ", ".join(f"'{p}'" for p in sorted_query_params) if sorted_query_params else ""
383
+ query_names_str = ", ".join(f"'{p}'" for p in route.query_params) if route.query_params else ""
439
384
  route_entry_lines.append(f" queryParams: [{query_names_str}] as const,")
440
385
  if route.component:
441
- route_entry_lines.append(f" component: '{escape_ts_string(route.component)}',")
386
+ route_entry_lines.append(f" component: '{_escape_ts_string(route.component)}',")
442
387
  route_entry_lines.append(" },")
443
388
  routes_entries.append("\n".join(route_entry_lines))
444
389
 
445
390
  route_names_union = "\n | ".join(f"'{name}'" for name in route_names) if route_names else "never"
446
391
 
447
- alias_block = render_semantic_aliases(used_aliases)
392
+ alias_block = _render_semantic_aliases(used_aliases)
448
393
  alias_preamble = f"{alias_block}\n\n" if alias_block else ""
449
394
 
450
- global_route_snippet = ""
451
- if global_route:
452
- global_route_snippet = (
453
- "\n\n// Optionally register route() on window for global access\n"
454
- "if (typeof window !== 'undefined') {\n"
455
- " (window as any).route = route;\n"
456
- "}\n"
457
- )
458
-
459
395
  return f"""// AUTO-GENERATED by litestar-vite. Do not edit.
460
396
  /* eslint-disable */
461
397
 
@@ -521,9 +457,6 @@ type RoutesWithoutRequiredParams = Exclude<RouteName, RoutesWithRequiredParams>;
521
457
  * route('books') // '/api/books'
522
458
  * route('book_detail', {{ book_id: 123 }}) // '/api/books/123'
523
459
  * route('search', {{ q: 'test', limit: 5 }}) // '/api/search?q=test&limit=5'
524
- *
525
- * // Access HTTP method from route definition when needed:
526
- * routeDefinitions.login.method // 'post'
527
460
  */
528
461
  export function route<T extends RoutesWithoutRequiredParams>(name: T): string;
529
462
  export function route<T extends RoutesWithoutRequiredParams>(
@@ -702,14 +635,14 @@ export function isCurrentRoute(pattern: string): boolean {{
702
635
  const regex = new RegExp(`^${{escaped.replace(/\\*/g, '.*')}}$`);
703
636
  return regex.test(current);
704
637
  }}
705
- {global_route_snippet}"""
638
+ """
706
639
 
707
640
 
708
- def collect_semantic_aliases(type_expr: str) -> set[str]:
641
+ def _collect_semantic_aliases(type_expr: str) -> set[str]:
709
642
  return {alias for alias in _TS_SEMANTIC_ALIASES if alias in type_expr}
710
643
 
711
644
 
712
- def render_semantic_aliases(aliases: set[str]) -> str:
645
+ def _render_semantic_aliases(aliases: set[str]) -> str:
713
646
  if not aliases:
714
647
  return ""
715
648
 
@@ -56,31 +56,31 @@ def ts_type_from_openapi(schema_dict: dict[str, Any]) -> str:
56
56
 
57
57
  if "anyOf" in schema_dict and isinstance(schema_dict["anyOf"], list) and schema_dict["anyOf"]:
58
58
  schemas = cast("list[Any]", schema_dict["anyOf"])
59
- union = {ts_type_from_subschema(s) for s in schemas}
60
- return join_union(union)
59
+ union = {_ts_type_from_subschema(s) for s in schemas}
60
+ return _join_union(union)
61
61
 
62
62
  result = "any"
63
63
  match schema_dict:
64
64
  case {"const": const} if const is not None:
65
- result = "any" if const is False else ts_literal(const)
65
+ result = "any" if const is False else _ts_literal(const)
66
66
  case {"enum": enum} if isinstance(enum, list) and enum:
67
67
  enum_values = cast("list[Any]", enum)
68
- result = " | ".join(ts_literal(v) for v in enum_values)
68
+ result = " | ".join(_ts_literal(v) for v in enum_values)
69
69
  case {"oneOf": one_of} if isinstance(one_of, list) and one_of:
70
70
  schemas = cast("list[Any]", one_of)
71
- union = {ts_type_from_subschema(s) for s in schemas}
72
- result = join_union(union)
71
+ union = {_ts_type_from_subschema(s) for s in schemas}
72
+ result = _join_union(union)
73
73
  case {"allOf": all_of} if isinstance(all_of, list) and all_of:
74
74
  schemas = cast("list[Any]", all_of)
75
- parts = [wrap_union_for_intersection(ts_type_from_subschema(s)) for s in schemas]
75
+ parts = [_wrap_union_for_intersection(_ts_type_from_subschema(s)) for s in schemas]
76
76
  parts = [p for p in parts if p and p != "any"]
77
77
  result = " & ".join(parts) if parts else "any"
78
78
  case {"type": list()}:
79
79
  type_entries_list: list[Any] = schema_dict["type"]
80
- parts = [ts_type_from_openapi_type_entry(t, schema_dict) for t in type_entries_list if isinstance(t, str)]
81
- result = join_union(set(parts)) if parts else "any"
80
+ parts = [_ts_type_from_openapi_type_entry(t, schema_dict) for t in type_entries_list if isinstance(t, str)]
81
+ result = _join_union(set(parts)) if parts else "any"
82
82
  case {"type": str() as schema_type}:
83
- result = ts_type_from_openapi_type_entry(schema_type, schema_dict)
83
+ result = _ts_type_from_openapi_type_entry(schema_type, schema_dict)
84
84
  case _:
85
85
  pass
86
86
 
@@ -146,13 +146,13 @@ def collect_ref_names(schema_dict: Any) -> set[str]:
146
146
  return refs
147
147
 
148
148
 
149
- def ts_type_from_subschema(schema: Any) -> str:
149
+ def _ts_type_from_subschema(schema: Any) -> str:
150
150
  if isinstance(schema, dict):
151
151
  return ts_type_from_openapi(cast("dict[str, Any]", schema))
152
152
  return "any"
153
153
 
154
154
 
155
- def ts_type_from_openapi_type_entry(type_name: str, schema_dict: dict[str, Any]) -> str:
155
+ def _ts_type_from_openapi_type_entry(type_name: str, schema_dict: dict[str, Any]) -> str:
156
156
  primitive_types: dict[str, str] = {
157
157
  "string": "string",
158
158
  "integer": "number",
@@ -168,8 +168,8 @@ def ts_type_from_openapi_type_entry(type_name: str, schema_dict: dict[str, Any])
168
168
  result = _OPENAPI_STRING_FORMAT_TO_TS_ALIAS.get(fmt, result)
169
169
  if type_name == "array":
170
170
  items = schema_dict.get("items")
171
- item_type = ts_type_from_subschema(items) if isinstance(items, dict) else "unknown"
172
- result = f"{wrap_for_array(item_type)}[]"
171
+ item_type = _ts_type_from_subschema(items) if isinstance(items, dict) else "unknown"
172
+ result = f"{_wrap_for_array(item_type)}[]"
173
173
  elif type_name == "object":
174
174
  properties = schema_dict.get("properties")
175
175
  if not isinstance(properties, dict) or not properties:
@@ -182,7 +182,7 @@ def ts_type_from_openapi_type_entry(type_name: str, schema_dict: dict[str, Any])
182
182
 
183
183
  lines: list[str] = ["{"]
184
184
  for name, prop_schema in cast("dict[str, Any]", properties).items():
185
- ts_type = ts_type_from_subschema(prop_schema)
185
+ ts_type = _ts_type_from_subschema(prop_schema)
186
186
  optional = "" if name in required else "?"
187
187
  lines.append(f" {name}{optional}: {ts_type};")
188
188
  lines.append("}")
@@ -191,7 +191,7 @@ def ts_type_from_openapi_type_entry(type_name: str, schema_dict: dict[str, Any])
191
191
  return result
192
192
 
193
193
 
194
- def wrap_for_array(type_expr: str) -> str:
194
+ def _wrap_for_array(type_expr: str) -> str:
195
195
  expr = type_expr.strip()
196
196
  if not expr:
197
197
  return "unknown"
@@ -203,7 +203,7 @@ def wrap_for_array(type_expr: str) -> str:
203
203
  return expr
204
204
 
205
205
 
206
- def wrap_union_for_intersection(type_expr: str) -> str:
206
+ def _wrap_union_for_intersection(type_expr: str) -> str:
207
207
  expr = type_expr.strip()
208
208
  if not expr:
209
209
  return "any"
@@ -214,7 +214,7 @@ def wrap_union_for_intersection(type_expr: str) -> str:
214
214
  return expr
215
215
 
216
216
 
217
- def join_union(types: set[str]) -> str:
217
+ def _join_union(types: set[str]) -> str:
218
218
  if not types:
219
219
  return "any"
220
220
  if len(types) == 1:
@@ -222,7 +222,7 @@ def join_union(types: set[str]) -> str:
222
222
  return " | ".join(sorted(types))
223
223
 
224
224
 
225
- def ts_literal(value: Any) -> str:
225
+ def _ts_literal(value: Any) -> str:
226
226
  if value is None:
227
227
  return "null"
228
228
  if isinstance(value, bool):
@@ -0,0 +1,8 @@
1
+ """Internal SPA handler implementation.
2
+
3
+ The public API is exposed via :mod:`litestar_vite.handler`.
4
+ """
5
+
6
+ from litestar_vite._handler.app import AppHandler
7
+
8
+ __all__ = ("AppHandler",)