litestar-vite 0.1.1__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 (154) hide show
  1. litestar_vite/__init__.py +54 -4
  2. litestar_vite/__metadata__.py +12 -7
  3. litestar_vite/_codegen/__init__.py +26 -0
  4. litestar_vite/_codegen/inertia.py +407 -0
  5. litestar_vite/_codegen/openapi.py +233 -0
  6. litestar_vite/_codegen/routes.py +653 -0
  7. litestar_vite/_codegen/ts.py +235 -0
  8. litestar_vite/_handler/__init__.py +8 -0
  9. litestar_vite/_handler/app.py +524 -0
  10. litestar_vite/_handler/routing.py +130 -0
  11. litestar_vite/cli.py +1147 -10
  12. litestar_vite/codegen.py +39 -0
  13. litestar_vite/commands.py +79 -0
  14. litestar_vite/config.py +1594 -70
  15. litestar_vite/deploy.py +355 -0
  16. litestar_vite/doctor.py +1179 -0
  17. litestar_vite/exceptions.py +78 -0
  18. litestar_vite/executor.py +316 -0
  19. litestar_vite/handler.py +9 -0
  20. litestar_vite/html_transform.py +426 -0
  21. litestar_vite/inertia/__init__.py +53 -0
  22. litestar_vite/inertia/_utils.py +114 -0
  23. litestar_vite/inertia/exception_handler.py +172 -0
  24. litestar_vite/inertia/helpers.py +1043 -0
  25. litestar_vite/inertia/middleware.py +54 -0
  26. litestar_vite/inertia/plugin.py +133 -0
  27. litestar_vite/inertia/request.py +286 -0
  28. litestar_vite/inertia/response.py +706 -0
  29. litestar_vite/inertia/types.py +316 -0
  30. litestar_vite/loader.py +462 -121
  31. litestar_vite/plugin.py +2160 -21
  32. litestar_vite/py.typed +0 -0
  33. litestar_vite/scaffolding/__init__.py +20 -0
  34. litestar_vite/scaffolding/generator.py +270 -0
  35. litestar_vite/scaffolding/templates.py +437 -0
  36. litestar_vite/templates/__init__.py +0 -0
  37. litestar_vite/templates/addons/tailwindcss/tailwind.css.j2 +1 -0
  38. litestar_vite/templates/angular/index.html.j2 +12 -0
  39. litestar_vite/templates/angular/openapi-ts.config.ts.j2 +18 -0
  40. litestar_vite/templates/angular/package.json.j2 +35 -0
  41. litestar_vite/templates/angular/src/app/app.component.css.j2 +3 -0
  42. litestar_vite/templates/angular/src/app/app.component.html.j2 +1 -0
  43. litestar_vite/templates/angular/src/app/app.component.ts.j2 +9 -0
  44. litestar_vite/templates/angular/src/app/app.config.ts.j2 +5 -0
  45. litestar_vite/templates/angular/src/main.ts.j2 +9 -0
  46. litestar_vite/templates/angular/src/styles.css.j2 +9 -0
  47. litestar_vite/templates/angular/tsconfig.app.json.j2 +34 -0
  48. litestar_vite/templates/angular/tsconfig.json.j2 +20 -0
  49. litestar_vite/templates/angular/vite.config.ts.j2 +21 -0
  50. litestar_vite/templates/angular-cli/.postcssrc.json.j2 +5 -0
  51. litestar_vite/templates/angular-cli/angular.json.j2 +36 -0
  52. litestar_vite/templates/angular-cli/openapi-ts.config.ts.j2 +18 -0
  53. litestar_vite/templates/angular-cli/package.json.j2 +27 -0
  54. litestar_vite/templates/angular-cli/proxy.conf.json.j2 +18 -0
  55. litestar_vite/templates/angular-cli/src/app/app.component.css.j2 +3 -0
  56. litestar_vite/templates/angular-cli/src/app/app.component.html.j2 +1 -0
  57. litestar_vite/templates/angular-cli/src/app/app.component.ts.j2 +9 -0
  58. litestar_vite/templates/angular-cli/src/app/app.config.ts.j2 +5 -0
  59. litestar_vite/templates/angular-cli/src/index.html.j2 +13 -0
  60. litestar_vite/templates/angular-cli/src/main.ts.j2 +6 -0
  61. litestar_vite/templates/angular-cli/src/styles.css.j2 +10 -0
  62. litestar_vite/templates/angular-cli/tailwind.config.js.j2 +4 -0
  63. litestar_vite/templates/angular-cli/tsconfig.app.json.j2 +16 -0
  64. litestar_vite/templates/angular-cli/tsconfig.json.j2 +26 -0
  65. litestar_vite/templates/angular-cli/tsconfig.spec.json.j2 +9 -0
  66. litestar_vite/templates/astro/astro.config.mjs.j2 +28 -0
  67. litestar_vite/templates/astro/openapi-ts.config.ts.j2 +15 -0
  68. litestar_vite/templates/astro/src/layouts/Layout.astro.j2 +63 -0
  69. litestar_vite/templates/astro/src/pages/index.astro.j2 +36 -0
  70. litestar_vite/templates/astro/src/styles/global.css.j2 +1 -0
  71. litestar_vite/templates/base/.gitignore.j2 +42 -0
  72. litestar_vite/templates/base/openapi-ts.config.ts.j2 +15 -0
  73. litestar_vite/templates/base/package.json.j2 +38 -0
  74. litestar_vite/templates/base/resources/vite-env.d.ts.j2 +1 -0
  75. litestar_vite/templates/base/tsconfig.json.j2 +37 -0
  76. litestar_vite/templates/htmx/src/main.js.j2 +8 -0
  77. litestar_vite/templates/htmx/templates/base.html.j2.j2 +56 -0
  78. litestar_vite/templates/htmx/templates/index.html.j2.j2 +13 -0
  79. litestar_vite/templates/htmx/vite.config.ts.j2 +40 -0
  80. litestar_vite/templates/nuxt/app.vue.j2 +29 -0
  81. litestar_vite/templates/nuxt/composables/useApi.ts.j2 +33 -0
  82. litestar_vite/templates/nuxt/nuxt.config.ts.j2 +31 -0
  83. litestar_vite/templates/nuxt/openapi-ts.config.ts.j2 +15 -0
  84. litestar_vite/templates/nuxt/pages/index.vue.j2 +54 -0
  85. litestar_vite/templates/react/index.html.j2 +13 -0
  86. litestar_vite/templates/react/src/App.css.j2 +56 -0
  87. litestar_vite/templates/react/src/App.tsx.j2 +19 -0
  88. litestar_vite/templates/react/src/main.tsx.j2 +10 -0
  89. litestar_vite/templates/react/vite.config.ts.j2 +39 -0
  90. litestar_vite/templates/react-inertia/index.html.j2 +14 -0
  91. litestar_vite/templates/react-inertia/package.json.j2 +46 -0
  92. litestar_vite/templates/react-inertia/resources/App.css.j2 +68 -0
  93. litestar_vite/templates/react-inertia/resources/main.tsx.j2 +17 -0
  94. litestar_vite/templates/react-inertia/resources/pages/Home.tsx.j2 +18 -0
  95. litestar_vite/templates/react-inertia/resources/ssr.tsx.j2 +19 -0
  96. litestar_vite/templates/react-inertia/vite.config.ts.j2 +59 -0
  97. litestar_vite/templates/react-router/index.html.j2 +12 -0
  98. litestar_vite/templates/react-router/src/App.css.j2 +17 -0
  99. litestar_vite/templates/react-router/src/App.tsx.j2 +7 -0
  100. litestar_vite/templates/react-router/src/main.tsx.j2 +10 -0
  101. litestar_vite/templates/react-router/vite.config.ts.j2 +39 -0
  102. litestar_vite/templates/react-tanstack/index.html.j2 +12 -0
  103. litestar_vite/templates/react-tanstack/openapi-ts.config.ts.j2 +18 -0
  104. litestar_vite/templates/react-tanstack/src/App.css.j2 +17 -0
  105. litestar_vite/templates/react-tanstack/src/main.tsx.j2 +21 -0
  106. litestar_vite/templates/react-tanstack/src/routeTree.gen.ts.j2 +7 -0
  107. litestar_vite/templates/react-tanstack/src/routes/__root.tsx.j2 +9 -0
  108. litestar_vite/templates/react-tanstack/src/routes/books.tsx.j2 +9 -0
  109. litestar_vite/templates/react-tanstack/src/routes/index.tsx.j2 +9 -0
  110. litestar_vite/templates/react-tanstack/vite.config.ts.j2 +39 -0
  111. litestar_vite/templates/svelte/index.html.j2 +13 -0
  112. litestar_vite/templates/svelte/src/App.svelte.j2 +30 -0
  113. litestar_vite/templates/svelte/src/app.css.j2 +45 -0
  114. litestar_vite/templates/svelte/src/main.ts.j2 +8 -0
  115. litestar_vite/templates/svelte/src/vite-env.d.ts.j2 +2 -0
  116. litestar_vite/templates/svelte/svelte.config.js.j2 +5 -0
  117. litestar_vite/templates/svelte/vite.config.ts.j2 +39 -0
  118. litestar_vite/templates/svelte-inertia/index.html.j2 +14 -0
  119. litestar_vite/templates/svelte-inertia/resources/app.css.j2 +21 -0
  120. litestar_vite/templates/svelte-inertia/resources/main.ts.j2 +11 -0
  121. litestar_vite/templates/svelte-inertia/resources/pages/Home.svelte.j2 +43 -0
  122. litestar_vite/templates/svelte-inertia/resources/vite-env.d.ts.j2 +2 -0
  123. litestar_vite/templates/svelte-inertia/svelte.config.js.j2 +5 -0
  124. litestar_vite/templates/svelte-inertia/vite.config.ts.j2 +37 -0
  125. litestar_vite/templates/sveltekit/openapi-ts.config.ts.j2 +15 -0
  126. litestar_vite/templates/sveltekit/src/app.css.j2 +40 -0
  127. litestar_vite/templates/sveltekit/src/app.html.j2 +12 -0
  128. litestar_vite/templates/sveltekit/src/hooks.server.ts.j2 +55 -0
  129. litestar_vite/templates/sveltekit/src/routes/+layout.svelte.j2 +12 -0
  130. litestar_vite/templates/sveltekit/src/routes/+page.svelte.j2 +34 -0
  131. litestar_vite/templates/sveltekit/svelte.config.js.j2 +12 -0
  132. litestar_vite/templates/sveltekit/tsconfig.json.j2 +14 -0
  133. litestar_vite/templates/sveltekit/vite.config.ts.j2 +31 -0
  134. litestar_vite/templates/vue/env.d.ts.j2 +7 -0
  135. litestar_vite/templates/vue/index.html.j2 +13 -0
  136. litestar_vite/templates/vue/src/App.vue.j2 +28 -0
  137. litestar_vite/templates/vue/src/main.ts.j2 +5 -0
  138. litestar_vite/templates/vue/src/style.css.j2 +45 -0
  139. litestar_vite/templates/vue/vite.config.ts.j2 +39 -0
  140. litestar_vite/templates/vue-inertia/env.d.ts.j2 +7 -0
  141. litestar_vite/templates/vue-inertia/index.html.j2 +14 -0
  142. litestar_vite/templates/vue-inertia/package.json.j2 +49 -0
  143. litestar_vite/templates/vue-inertia/resources/main.ts.j2 +18 -0
  144. litestar_vite/templates/vue-inertia/resources/pages/Home.vue.j2 +22 -0
  145. litestar_vite/templates/vue-inertia/resources/ssr.ts.j2 +21 -0
  146. litestar_vite/templates/vue-inertia/resources/style.css.j2 +21 -0
  147. litestar_vite/templates/vue-inertia/vite.config.ts.j2 +59 -0
  148. litestar_vite-0.15.0rc2.dist-info/METADATA +230 -0
  149. litestar_vite-0.15.0rc2.dist-info/RECORD +151 -0
  150. {litestar_vite-0.1.1.dist-info → litestar_vite-0.15.0rc2.dist-info}/WHEEL +1 -1
  151. litestar_vite/template_engine.py +0 -103
  152. litestar_vite-0.1.1.dist-info/METADATA +0 -68
  153. litestar_vite-0.1.1.dist-info/RECORD +0 -11
  154. {litestar_vite-0.1.1.dist-info → litestar_vite-0.15.0rc2.dist-info}/licenses/LICENSE +0 -0
litestar_vite/__init__.py CHANGED
@@ -1,8 +1,58 @@
1
- from __future__ import annotations
1
+ """Litestar-Vite: Seamless integration between Litestar and Vite.
2
2
 
3
- from litestar_vite.config import ViteConfig, ViteTemplateConfig
3
+ This package provides integration between the Litestar web framework and
4
+ Vite, the next-generation frontend build tool.
5
+
6
+ Basic usage:
7
+ from litestar import Litestar
8
+ from litestar_vite import VitePlugin, ViteConfig
9
+
10
+ app = Litestar(
11
+ plugins=[VitePlugin(config=ViteConfig(dev_mode=True))],
12
+ )
13
+
14
+ For more advanced configuration:
15
+ from litestar_vite import VitePlugin, ViteConfig, PathConfig, RuntimeConfig
16
+
17
+ app = Litestar(
18
+ plugins=[
19
+ VitePlugin(
20
+ config=ViteConfig(
21
+ mode="spa",
22
+ dev_mode=True,
23
+ paths=PathConfig(bundle_dir=Path("dist")),
24
+ runtime=RuntimeConfig(executor="bun"),
25
+ types=True,
26
+ )
27
+ )
28
+ ],
29
+ )
30
+ """
31
+
32
+ from litestar_vite import inertia
33
+ from litestar_vite.config import (
34
+ DeployConfig,
35
+ ExternalDevServer,
36
+ InertiaConfig,
37
+ InertiaSSRConfig,
38
+ PathConfig,
39
+ RuntimeConfig,
40
+ TypeGenConfig,
41
+ ViteConfig,
42
+ )
4
43
  from litestar_vite.loader import ViteAssetLoader
5
44
  from litestar_vite.plugin import VitePlugin
6
- from litestar_vite.template_engine import ViteTemplateEngine
7
45
 
8
- __all__ = ("ViteConfig", "ViteTemplateConfig", "VitePlugin", "ViteAssetLoader", "ViteTemplateEngine")
46
+ __all__ = (
47
+ "DeployConfig",
48
+ "ExternalDevServer",
49
+ "InertiaConfig",
50
+ "InertiaSSRConfig",
51
+ "PathConfig",
52
+ "RuntimeConfig",
53
+ "TypeGenConfig",
54
+ "ViteAssetLoader",
55
+ "ViteConfig",
56
+ "VitePlugin",
57
+ "inertia",
58
+ )
@@ -1,11 +1,16 @@
1
1
  """Metadata for the Project."""
2
- from __future__ import annotations
3
2
 
4
- import importlib.metadata
3
+ from importlib.metadata import PackageNotFoundError, metadata, version
5
4
 
6
- __all__ = ["__version__", "__project__"]
5
+ __all__ = ("__project__", "__version__")
7
6
 
8
- __version__ = importlib.metadata.version("litestar_vite")
9
- """Version of the project."""
10
- __project__ = importlib.metadata.metadata("litestar_vite")["Name"]
11
- """Name of the project."""
7
+ try:
8
+ __version__ = version("litestar_vite")
9
+ """Version of the project."""
10
+ __project__ = metadata("litestar_vite")["Name"]
11
+ """Name of the project."""
12
+ except PackageNotFoundError: # pragma: no cover
13
+ __version__ = "0.0.0"
14
+ __project__ = "Litestar Vite"
15
+ finally:
16
+ del version, PackageNotFoundError, metadata
@@ -0,0 +1,26 @@
1
+ """Internal code generation implementation.
2
+
3
+ This package contains the implementation behind the public functions exposed via
4
+ ``litestar_vite.codegen``.
5
+
6
+ We keep Litestar private OpenAPI integration and TypeScript conversion details
7
+ isolated here to make the public module easier to navigate and maintain.
8
+ """
9
+
10
+ from litestar_vite._codegen.inertia import InertiaPageMetadata, extract_inertia_pages, generate_inertia_pages_json
11
+ from litestar_vite._codegen.routes import (
12
+ RouteMetadata,
13
+ extract_route_metadata,
14
+ generate_routes_json,
15
+ generate_routes_ts,
16
+ )
17
+
18
+ __all__ = (
19
+ "InertiaPageMetadata",
20
+ "RouteMetadata",
21
+ "extract_inertia_pages",
22
+ "extract_route_metadata",
23
+ "generate_inertia_pages_json",
24
+ "generate_routes_json",
25
+ "generate_routes_ts",
26
+ )
@@ -0,0 +1,407 @@
1
+ """Inertia page-props metadata extraction and export."""
2
+
3
+ import datetime
4
+ from contextlib import suppress
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+ from typing import TYPE_CHECKING, Any, cast
8
+
9
+ from litestar._openapi.datastructures import _get_normalized_schema_key # pyright: ignore[reportPrivateUsage]
10
+ from litestar.handlers import HTTPRouteHandler
11
+ from litestar.openapi.spec import Reference, Schema
12
+ from litestar.response.base import ASGIResponse
13
+ from litestar.routes import HTTPRoute
14
+ from litestar.types.builtin_types import NoneType
15
+ from litestar.typing import FieldDefinition
16
+
17
+ from litestar_vite._codegen.openapi import (
18
+ OpenAPISupport,
19
+ build_schema_name_map,
20
+ merge_generated_components_into_openapi,
21
+ openapi_components_schemas,
22
+ resolve_page_props_field_definition,
23
+ schema_name_from_ref,
24
+ )
25
+ from litestar_vite._codegen.ts import collect_ref_names, normalize_path, python_type_to_typescript, ts_type_from_openapi
26
+
27
+ if TYPE_CHECKING:
28
+ from litestar import Litestar
29
+
30
+ from litestar_vite.config import InertiaConfig, TypeGenConfig
31
+
32
+
33
+ def _str_list_factory() -> list[str]:
34
+ """Return an empty ``list[str]`` (typed for pyright).
35
+
36
+ Returns:
37
+ An empty list.
38
+ """
39
+ return []
40
+
41
+
42
+ @dataclass
43
+ class InertiaPageMetadata:
44
+ """Metadata for a single Inertia page component."""
45
+
46
+ component: str
47
+ route_path: str
48
+ props_type: str | None = None
49
+ schema_ref: str | None = None
50
+ handler_name: str | None = None
51
+ ts_type: str | None = None
52
+ custom_types: list[str] = field(default_factory=_str_list_factory)
53
+
54
+
55
+ def _get_return_type_name(handler: HTTPRouteHandler) -> "str | None":
56
+ field_definition = handler.parsed_fn_signature.return_type
57
+ excluded_types: tuple[type[Any], ...] = (NoneType, ASGIResponse)
58
+ if field_definition.is_subclass_of(excluded_types):
59
+ return None
60
+
61
+ fn = handler.fn
62
+ with suppress(AttributeError):
63
+ return_annotation = fn.__annotations__.get("return")
64
+ if isinstance(return_annotation, str) and return_annotation:
65
+ return return_annotation
66
+
67
+ raw = field_definition.raw
68
+ if isinstance(raw, str):
69
+ return raw
70
+ if isinstance(raw, type):
71
+ return raw.__name__
72
+ origin: Any = None
73
+ with suppress(AttributeError):
74
+ origin = field_definition.origin
75
+ if isinstance(origin, type):
76
+ return origin.__name__
77
+ return str(raw)
78
+
79
+
80
+ def _get_openapi_schema_ref(
81
+ handler: HTTPRouteHandler, openapi_schema: dict[str, Any] | None, route_path: str, method: str = "GET"
82
+ ) -> "str | None":
83
+ if not openapi_schema:
84
+ return None
85
+
86
+ paths = openapi_schema.get("paths", {})
87
+ path_item = paths.get(route_path, {})
88
+ operation = path_item.get(method.lower(), {})
89
+
90
+ responses = operation.get("responses", {})
91
+ success_response = responses.get("200", responses.get("2XX", {}))
92
+ content = success_response.get("content", {})
93
+
94
+ json_content = content.get("application/json", {})
95
+ schema = json_content.get("schema", {})
96
+
97
+ ref = schema.get("$ref")
98
+ return cast("str | None", ref) if ref else None
99
+
100
+
101
+ def _extract_inertia_component(handler: HTTPRouteHandler) -> str | None:
102
+ opt = handler.opt or {}
103
+ component = opt.get("component") or opt.get("page")
104
+ return component if isinstance(component, str) and component else None
105
+
106
+
107
+ def _infer_inertia_props_type(
108
+ component: str,
109
+ handler: HTTPRouteHandler,
110
+ schema_creator: Any,
111
+ page_schema_keys: dict[str, tuple[str, ...]],
112
+ page_schema_dicts: dict[str, dict[str, Any]],
113
+ *,
114
+ fallback_type: str,
115
+ ) -> str | None:
116
+ if schema_creator is not None:
117
+ field_def, schema_result = resolve_page_props_field_definition(handler, schema_creator)
118
+ if field_def is not None and isinstance(schema_result, Reference):
119
+ page_schema_keys[component] = _get_normalized_schema_key(field_def)
120
+ return None
121
+ if isinstance(schema_result, Schema):
122
+ schema_dict = schema_result.to_schema()
123
+ page_schema_dicts[component] = schema_dict
124
+ return ts_type_from_openapi(schema_dict)
125
+ return None
126
+
127
+ raw_type = _get_return_type_name(handler)
128
+ if not raw_type:
129
+ return None
130
+ props_type, _ = python_type_to_typescript(raw_type, fallback=fallback_type)
131
+ return props_type
132
+
133
+
134
+ def _finalize_inertia_pages(
135
+ pages: list[InertiaPageMetadata],
136
+ *,
137
+ openapi_support: OpenAPISupport,
138
+ page_schema_keys: dict[str, tuple[str, ...]],
139
+ page_schema_dicts: dict[str, dict[str, Any]],
140
+ ) -> None:
141
+ context = openapi_support.context
142
+ if context is None:
143
+ return
144
+
145
+ generated_components = context.schema_registry.generate_components_schemas()
146
+ name_map = build_schema_name_map(context.schema_registry)
147
+ openapi_components = openapi_components_schemas(openapi_support.openapi_schema)
148
+
149
+ if openapi_support.openapi_schema is not None:
150
+ merge_generated_components_into_openapi(openapi_support.openapi_schema, generated_components)
151
+
152
+ for page in pages:
153
+ schema_key = page_schema_keys.get(page.component)
154
+
155
+ schema_name: str | None = None
156
+ if page.schema_ref:
157
+ schema_name = schema_name_from_ref(page.schema_ref)
158
+ elif schema_key:
159
+ schema_name = name_map.get(schema_key)
160
+
161
+ if schema_name:
162
+ page.ts_type = schema_name
163
+ page.props_type = schema_name
164
+
165
+ custom_types: set[str] = set()
166
+ if page.ts_type:
167
+ custom_types.add(page.ts_type)
168
+
169
+ if page.schema_ref:
170
+ openapi_schema_dict = openapi_components.get(page.ts_type or "")
171
+ if isinstance(openapi_schema_dict, dict):
172
+ custom_types.update(collect_ref_names(openapi_schema_dict))
173
+ else:
174
+ page_schema_dict = page_schema_dicts.get(page.component)
175
+ if isinstance(page_schema_dict, dict):
176
+ custom_types.update(collect_ref_names(page_schema_dict))
177
+ elif schema_key:
178
+ registered = context.schema_registry._schema_key_map.get( # pyright: ignore[reportPrivateUsage]
179
+ schema_key
180
+ )
181
+ if registered:
182
+ custom_types.update(collect_ref_names(registered.schema.to_schema()))
183
+
184
+ page.custom_types = sorted(custom_types)
185
+
186
+
187
+ def extract_inertia_pages(
188
+ app: "Litestar", *, openapi_schema: dict[str, Any] | None = None, fallback_type: "str" = "unknown"
189
+ ) -> list[InertiaPageMetadata]:
190
+ pages: list[InertiaPageMetadata] = []
191
+
192
+ openapi_support = OpenAPISupport.from_app(app, openapi_schema)
193
+
194
+ page_schema_keys: dict[str, tuple[str, ...]] = {}
195
+ page_schema_dicts: dict[str, dict[str, Any]] = {}
196
+
197
+ for http_route, route_handler in _iter_route_handlers(app):
198
+ component = _extract_inertia_component(route_handler)
199
+ if not component:
200
+ continue
201
+
202
+ normalized_path = normalize_path(str(http_route.path))
203
+ handler_name = route_handler.handler_name or route_handler.name
204
+
205
+ props_type = _infer_inertia_props_type(
206
+ component,
207
+ route_handler,
208
+ openapi_support.schema_creator,
209
+ page_schema_keys,
210
+ page_schema_dicts,
211
+ fallback_type=fallback_type,
212
+ )
213
+
214
+ method = next(iter(route_handler.http_methods), "GET") if route_handler.http_methods else "GET"
215
+ schema_ref = _get_openapi_schema_ref(route_handler, openapi_schema, normalized_path, method=str(method))
216
+
217
+ pages.append(
218
+ InertiaPageMetadata(
219
+ component=component,
220
+ route_path=normalized_path,
221
+ props_type=props_type,
222
+ schema_ref=schema_ref,
223
+ handler_name=handler_name,
224
+ )
225
+ )
226
+
227
+ if openapi_support.enabled:
228
+ _finalize_inertia_pages(
229
+ pages,
230
+ openapi_support=openapi_support,
231
+ page_schema_keys=page_schema_keys,
232
+ page_schema_dicts=page_schema_dicts,
233
+ )
234
+
235
+ return pages
236
+
237
+
238
+ def _iter_route_handlers(app: "Litestar") -> "list[tuple[HTTPRoute, HTTPRouteHandler]]":
239
+ """Iterate over HTTP route handlers in an app.
240
+
241
+ Returns:
242
+ A list of (http_route, route_handler) tuples.
243
+ """
244
+ handlers: list[tuple[HTTPRoute, HTTPRouteHandler]] = []
245
+ for route in app.routes:
246
+ if isinstance(route, HTTPRoute):
247
+ handlers.extend((route, route_handler) for route_handler in route.route_handlers)
248
+ return handlers
249
+
250
+
251
+ def _fallback_ts_type(types_config: "TypeGenConfig | None") -> str:
252
+ fallback_type = types_config.fallback_type if types_config is not None else "unknown"
253
+ return "any" if fallback_type == "any" else "unknown"
254
+
255
+
256
+ def _ts_type_from_value(value: Any, *, fallback_ts_type: str) -> str:
257
+ ts_type = fallback_ts_type
258
+ if value is None:
259
+ ts_type = "null"
260
+ elif isinstance(value, bool):
261
+ ts_type = "boolean"
262
+ elif isinstance(value, str):
263
+ ts_type = "string"
264
+ elif isinstance(value, (int, float)):
265
+ ts_type = "number"
266
+ elif isinstance(value, (bytes, bytearray, Path)):
267
+ ts_type = "string"
268
+ elif isinstance(value, (list, tuple, set, frozenset)):
269
+ ts_type = f"{fallback_ts_type}[]"
270
+ elif isinstance(value, dict):
271
+ ts_type = f"Record<string, {fallback_ts_type}>"
272
+ return ts_type
273
+
274
+
275
+ def _should_register_value_schema(value: Any) -> bool:
276
+ if value is None:
277
+ return False
278
+ return not isinstance(value, (bool, str, int, float, bytes, bytearray, Path, list, tuple, set, frozenset, dict))
279
+
280
+
281
+ def _build_inertia_shared_props(
282
+ app: "Litestar",
283
+ *,
284
+ openapi_schema: dict[str, Any] | None,
285
+ include_default_auth: bool,
286
+ include_default_flash: bool,
287
+ inertia_config: "InertiaConfig | None",
288
+ types_config: "TypeGenConfig | None",
289
+ ) -> dict[str, dict[str, Any]]:
290
+ """Build shared props metadata (built-ins + configured props).
291
+
292
+ Returns:
293
+ Mapping of shared prop name to metadata payload.
294
+ """
295
+ fallback_ts_type = _fallback_ts_type(types_config)
296
+
297
+ shared_props: dict[str, dict[str, Any]] = {
298
+ "errors": {"type": "Record<string, string[]>", "optional": True},
299
+ "csrf_token": {"type": "string", "optional": True},
300
+ }
301
+
302
+ if include_default_auth or include_default_flash:
303
+ shared_props["auth"] = {"type": "AuthData", "optional": True}
304
+ shared_props["flash"] = {"type": "FlashMessages", "optional": True}
305
+
306
+ if inertia_config is None:
307
+ return shared_props
308
+
309
+ openapi_support = OpenAPISupport.from_app(app, openapi_schema)
310
+ shared_schema_keys: dict[str, tuple[str, ...]] = {}
311
+
312
+ for key, value in inertia_config.extra_static_page_props.items():
313
+ if not key:
314
+ continue
315
+
316
+ shared_props[key] = {"type": _ts_type_from_value(value, fallback_ts_type=fallback_ts_type), "optional": True}
317
+
318
+ if openapi_support.enabled and isinstance(openapi_schema, dict) and _should_register_value_schema(value):
319
+ try:
320
+ field_def = FieldDefinition.from_annotation(value.__class__)
321
+ schema_result = openapi_support.schema_creator.for_field_definition(field_def) # type: ignore[union-attr]
322
+ if isinstance(schema_result, Reference):
323
+ shared_schema_keys[key] = _get_normalized_schema_key(field_def)
324
+ except (AttributeError, TypeError, ValueError): # pragma: no cover - defensive
325
+ pass
326
+
327
+ for key in inertia_config.extra_session_page_props:
328
+ if not key:
329
+ continue
330
+ shared_props.setdefault(key, {"type": fallback_ts_type, "optional": True})
331
+
332
+ if not (
333
+ openapi_support.context
334
+ and openapi_support.schema_creator
335
+ and isinstance(openapi_schema, dict)
336
+ and shared_schema_keys
337
+ ):
338
+ return shared_props
339
+
340
+ generated_components = openapi_support.context.schema_registry.generate_components_schemas()
341
+ name_map = build_schema_name_map(openapi_support.context.schema_registry)
342
+ merge_generated_components_into_openapi(openapi_schema, generated_components)
343
+
344
+ for prop_name, schema_key in shared_schema_keys.items():
345
+ type_name = name_map.get(schema_key)
346
+ if type_name:
347
+ shared_props[prop_name]["type"] = type_name
348
+
349
+ return shared_props
350
+
351
+
352
+ def generate_inertia_pages_json(
353
+ app: "Litestar",
354
+ *,
355
+ openapi_schema: dict[str, Any] | None = None,
356
+ include_default_auth: bool = True,
357
+ include_default_flash: bool = True,
358
+ inertia_config: "InertiaConfig | None" = None,
359
+ types_config: "TypeGenConfig | None" = None,
360
+ ) -> dict[str, Any]:
361
+ """Generate Inertia pages metadata JSON.
362
+
363
+ Returns:
364
+ An Inertia pages metadata payload as a dictionary.
365
+ """
366
+ pages_metadata = extract_inertia_pages(
367
+ app,
368
+ openapi_schema=openapi_schema,
369
+ fallback_type=types_config.fallback_type if types_config is not None else "unknown",
370
+ )
371
+
372
+ pages_dict: dict[str, dict[str, Any]] = {}
373
+ for page in pages_metadata:
374
+ page_data: dict[str, Any] = {"route": page.route_path}
375
+ if page.props_type:
376
+ page_data["propsType"] = page.props_type
377
+ if page.ts_type:
378
+ page_data["tsType"] = page.ts_type
379
+ if page.custom_types:
380
+ page_data["customTypes"] = page.custom_types
381
+ if page.schema_ref:
382
+ page_data["schemaRef"] = page.schema_ref
383
+ if page.handler_name:
384
+ page_data["handler"] = page.handler_name
385
+ pages_dict[page.component] = page_data
386
+
387
+ shared_props = _build_inertia_shared_props(
388
+ app,
389
+ openapi_schema=openapi_schema,
390
+ include_default_auth=include_default_auth,
391
+ include_default_flash=include_default_flash,
392
+ inertia_config=inertia_config,
393
+ types_config=types_config,
394
+ )
395
+
396
+ root: dict[str, Any] = {
397
+ "pages": pages_dict,
398
+ "sharedProps": shared_props,
399
+ "typeGenConfig": {"includeDefaultAuth": include_default_auth, "includeDefaultFlash": include_default_flash},
400
+ "generatedAt": datetime.datetime.now(tz=datetime.timezone.utc).isoformat(),
401
+ }
402
+
403
+ if types_config is not None:
404
+ root["typeImportPaths"] = types_config.type_import_paths
405
+ root["fallbackType"] = types_config.fallback_type
406
+
407
+ return root