litestar-vite-plugin 0.15.0-alpha.3 → 0.15.0-alpha.5

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/README.md CHANGED
@@ -10,66 +10,83 @@ Litestar Vite connects the Litestar backend to a Vite toolchain. It supports SPA
10
10
  - Type-safe frontends: optional OpenAPI/routes export + `@hey-api/openapi-ts` via the Vite plugin.
11
11
  - Inertia support: v2 protocol with session middleware and optional SPA mode.
12
12
 
13
- ## Quick start (React TanStack SPA)
13
+ ## Quick Start (SPA)
14
14
 
15
15
  ```bash
16
16
  pip install litestar-vite
17
- litestar assets init --template react-tanstack
18
- litestar assets install # installs frontend deps via configured executor
19
17
  ```
20
18
 
21
19
  ```python
20
+ import os
21
+ from pathlib import Path
22
22
  from litestar import Litestar
23
- from litestar_vite import VitePlugin, ViteConfig
23
+ from litestar_vite import VitePlugin, ViteConfig, PathConfig
24
+
25
+ DEV_MODE = os.getenv("VITE_DEV_MODE", "true").lower() in ("true", "1", "yes")
24
26
 
25
- app = Litestar(plugins=[VitePlugin(config=ViteConfig(dev_mode=True))]) # SPA mode by default
27
+ app = Litestar(
28
+ plugins=[VitePlugin(config=ViteConfig(
29
+ dev_mode=DEV_MODE,
30
+ paths=PathConfig(root=Path(__file__).parent),
31
+ ))]
32
+ )
26
33
  ```
27
34
 
28
35
  ```bash
29
- litestar run --reload # starts Litestar; Vite dev is proxied automatically
36
+ litestar run --reload # Vite dev server is proxied automatically
30
37
  ```
31
38
 
32
- Other templates: `react`, `vue`, `svelte`, `htmx`, `react-inertia`, `vue-inertia`, `svelte-inertia`, `angular`, `angular-cli`, `astro`, `nuxt`, `sveltekit` (see `litestar assets init --help`).
39
+ Scaffold a frontend: `litestar assets init --template vue` (or `react`, `svelte`, `htmx`, `react-inertia`, `vue-inertia`, `angular`, `astro`, `nuxt`, `sveltekit`).
33
40
 
34
41
  ## Template / HTMX
35
42
 
36
43
  ```python
44
+ from pathlib import Path
37
45
  from litestar import Litestar
38
46
  from litestar.contrib.jinja import JinjaTemplateEngine
39
- from litestar.template.config import TemplateConfig
40
- from litestar_vite import VitePlugin, ViteConfig
47
+ from litestar.template import TemplateConfig
48
+ from litestar_vite import VitePlugin, ViteConfig, PathConfig
49
+
50
+ here = Path(__file__).parent
41
51
 
42
52
  app = Litestar(
43
- template_config=TemplateConfig(engine=JinjaTemplateEngine(directory="templates")),
44
- plugins=[VitePlugin(config=ViteConfig(mode="template", dev_mode=True))],
53
+ template_config=TemplateConfig(directory=here / "templates", engine=JinjaTemplateEngine),
54
+ plugins=[VitePlugin(config=ViteConfig(
55
+ dev_mode=True,
56
+ paths=PathConfig(root=here),
57
+ ))],
45
58
  )
46
59
  ```
47
60
 
48
61
  ## Inertia (v2)
49
62
 
50
- Requires session middleware.
63
+ Requires session middleware (32-char secret).
51
64
 
52
65
  ```python
66
+ import os
67
+ from pathlib import Path
53
68
  from litestar import Litestar
54
69
  from litestar.middleware.session.client_side import CookieBackendConfig
55
- from litestar_vite import VitePlugin, ViteConfig
56
- from litestar_vite.inertia import InertiaPlugin
57
- from litestar_vite.inertia.config import InertiaConfig
70
+ from litestar_vite import VitePlugin, ViteConfig, PathConfig
71
+ from litestar_vite.inertia import InertiaConfig
58
72
 
59
- session_backend = CookieBackendConfig(secret="dev-secret")
73
+ here = Path(__file__).parent
74
+ SECRET_KEY = os.environ.get("SECRET_KEY", "development-only-secret-32-chars")
75
+ session = CookieBackendConfig(secret=SECRET_KEY.encode("utf-8"))
60
76
 
61
77
  app = Litestar(
62
- middleware=[session_backend.middleware],
63
- plugins=[
64
- VitePlugin(config=ViteConfig(mode="template", inertia=True, dev_mode=True)),
65
- InertiaPlugin(InertiaConfig()),
66
- ],
78
+ middleware=[session.middleware],
79
+ plugins=[VitePlugin(config=ViteConfig(
80
+ dev_mode=True,
81
+ paths=PathConfig(root=here),
82
+ inertia=InertiaConfig(root_template="index.html"),
83
+ ))],
67
84
  )
68
85
  ```
69
86
 
70
- ## SSR Frameworks (Astro, Nuxt, SvelteKit)
87
+ ## Meta-frameworks (Astro, Nuxt, SvelteKit)
71
88
 
72
- For SSR frameworks that handle their own routing, use `proxy_mode="ssr"`:
89
+ Use `proxy_mode="ssr"` to proxy non-API routes to the framework's dev server:
73
90
 
74
91
  ```python
75
92
  import os
@@ -77,17 +94,15 @@ from pathlib import Path
77
94
  from litestar import Litestar
78
95
  from litestar_vite import VitePlugin, ViteConfig, PathConfig, RuntimeConfig
79
96
 
97
+ here = Path(__file__).parent
80
98
  DEV_MODE = os.getenv("VITE_DEV_MODE", "true").lower() in ("true", "1", "yes")
81
99
 
82
100
  app = Litestar(
83
101
  plugins=[
84
102
  VitePlugin(config=ViteConfig(
85
103
  dev_mode=DEV_MODE,
86
- paths=PathConfig(root=Path(__file__).parent),
87
- runtime=RuntimeConfig(
88
- proxy_mode="ssr" if DEV_MODE else None, # Proxy in dev, static in prod
89
- spa_handler=not DEV_MODE, # Serve built files in production
90
- ),
104
+ paths=PathConfig(root=here),
105
+ runtime=RuntimeConfig(proxy_mode="ssr"),
91
106
  ))
92
107
  ],
93
108
  )
@@ -99,29 +114,28 @@ app = Litestar(
99
114
  |------|-------|----------|
100
115
  | `vite` | - | SPAs - proxies Vite assets only (default) |
101
116
  | `direct` | - | Two-port dev - expose Vite port directly |
102
- | `proxy` | `ssr` | SSR frameworks - proxies everything except API routes |
117
+ | `proxy` | `ssr` | Meta-frameworks - proxies everything except API routes |
103
118
 
104
119
  ### Production Deployment
105
120
 
106
- **Static Build (recommended):** Build your SSR framework to static files, then serve with Litestar:
121
+ **Astro (static):** Astro generates static HTML by default. Build and serve with Litestar:
107
122
 
108
123
  ```bash
109
- # Build frontend
110
- cd examples/astro && npm run build
111
-
112
- # Run in production mode
124
+ litestar --app-dir examples/astro assets install
125
+ litestar --app-dir examples/astro assets build
113
126
  VITE_DEV_MODE=false litestar --app-dir examples/astro run
114
127
  ```
115
128
 
116
- Configure `bundle_dir` to match your framework's build output:
129
+ **Nuxt/SvelteKit (SSR):** These run their own Node servers. Deploy as two services:
117
130
 
118
- | Framework | Default Output | PathConfig |
119
- |-----------|---------------|------------|
120
- | Astro | `dist/` | `bundle_dir=Path("dist")` |
121
- | Nuxt | `.output/public/` | `bundle_dir=Path(".output/public")` |
122
- | SvelteKit | `build/` | `bundle_dir=Path("build")` |
131
+ ```bash
132
+ # Terminal 1: SSR server
133
+ litestar --app-dir examples/nuxt assets build
134
+ litestar --app-dir examples/nuxt assets serve
123
135
 
124
- **Two-Service:** For dynamic SSR, run the Node server alongside Litestar behind a reverse proxy.
136
+ # Terminal 2: Litestar API
137
+ VITE_DEV_MODE=false litestar --app-dir examples/nuxt run --port 8001
138
+ ```
125
139
 
126
140
  ## Type generation
127
141
 
@@ -151,5 +165,5 @@ litestar assets generate-types # one-off or CI
151
165
  ## Links
152
166
 
153
167
  - Docs: <https://litestar-org.github.io/litestar-vite/>
154
- - Examples: `examples/` (basic, inertia, react)
168
+ - Examples: `examples/` (react, vue, svelte, react-inertia, vue-inertia, astro, nuxt, sveltekit, htmx)
155
169
  - Issues: <https://github.com/litestar-org/litestar-vite/issues/>
@@ -125,6 +125,12 @@ export interface AstroTypesConfig {
125
125
  * @default false
126
126
  */
127
127
  generateZod?: boolean;
128
+ /**
129
+ * Generate SDK client functions for API calls.
130
+ *
131
+ * @default true
132
+ */
133
+ generateSdk?: boolean;
128
134
  /**
129
135
  * Debounce time in milliseconds for type regeneration.
130
136
  *
package/dist/js/astro.js CHANGED
@@ -40,6 +40,7 @@ function resolveConfig(config = {}) {
40
40
  openapiPath: "openapi.json",
41
41
  routesPath: "routes.json",
42
42
  generateZod: false,
43
+ generateSdk: true,
43
44
  debounce: 300
44
45
  };
45
46
  } else if (typeof config.types === "object" && config.types !== null) {
@@ -49,6 +50,7 @@ function resolveConfig(config = {}) {
49
50
  openapiPath: config.types.openapiPath ?? "openapi.json",
50
51
  routesPath: config.types.routesPath ?? "routes.json",
51
52
  generateZod: config.types.generateZod ?? false,
53
+ generateSdk: config.types.generateSdk ?? true,
52
54
  debounce: config.types.debounce ?? 300
53
55
  };
54
56
  }
@@ -68,6 +70,9 @@ function createProxyPlugin(config) {
68
70
  config() {
69
71
  return {
70
72
  server: {
73
+ // Force IPv4 binding for consistency with Python proxy configuration
74
+ // Without this, Astro might bind to IPv6 localhost which the proxy can't reach
75
+ host: "127.0.0.1",
71
76
  // Set the port from Python config/env to ensure Astro uses the expected port
72
77
  // strictPort: true prevents Astro from auto-incrementing to a different port
73
78
  ...config.port !== void 0 ? {
@@ -227,12 +232,28 @@ function createTypeGenerationPlugin(typesConfig) {
227
232
  return false;
228
233
  }
229
234
  console.log(colors.cyan("[litestar-astro]"), colors.dim("Generating TypeScript types..."));
230
- const args = ["@hey-api/openapi-ts", "-i", typesConfig.openapiPath, "-o", typesConfig.output];
231
- if (typesConfig.generateZod) {
232
- args.push("--plugins", "zod", "@hey-api/typescript");
235
+ const projectRoot = process.cwd();
236
+ const candidates = [path.resolve(projectRoot, "openapi-ts.config.ts"), path.resolve(projectRoot, "hey-api.config.ts"), path.resolve(projectRoot, ".hey-api.config.ts")];
237
+ const configPath = candidates.find((p) => fs.existsSync(p)) || null;
238
+ let args;
239
+ if (configPath) {
240
+ console.log(colors.cyan("[litestar-astro]"), colors.dim("Using config:"), configPath);
241
+ args = ["@hey-api/openapi-ts", "--file", configPath];
242
+ } else {
243
+ args = ["@hey-api/openapi-ts", "-i", typesConfig.openapiPath, "-o", typesConfig.output];
244
+ const plugins = ["@hey-api/typescript", "@hey-api/schemas"];
245
+ if (typesConfig.generateSdk) {
246
+ plugins.push("@hey-api/sdk", "@hey-api/client-fetch");
247
+ }
248
+ if (typesConfig.generateZod) {
249
+ plugins.push("zod");
250
+ }
251
+ if (plugins.length) {
252
+ args.push("--plugins", ...plugins);
253
+ }
233
254
  }
234
255
  await execAsync(`npx ${args.join(" ")}`, {
235
- cwd: process.cwd()
256
+ cwd: projectRoot
236
257
  });
237
258
  const routesPath = path.resolve(process.cwd(), typesConfig.routesPath);
238
259
  if (fs.existsSync(routesPath)) {
@@ -271,6 +292,14 @@ function createTypeGenerationPlugin(typesConfig) {
271
292
  server = devServer;
272
293
  console.log(colors.cyan("[litestar-astro]"), colors.dim("Watching for schema changes:"), colors.yellow(typesConfig.openapiPath));
273
294
  },
295
+ async buildStart() {
296
+ if (typesConfig.enabled) {
297
+ const openapiPath = path.resolve(process.cwd(), typesConfig.openapiPath);
298
+ if (fs.existsSync(openapiPath)) {
299
+ await runTypeGeneration();
300
+ }
301
+ }
302
+ },
274
303
  handleHotUpdate({ file }) {
275
304
  if (!typesConfig.enabled) {
276
305
  return;
@@ -311,12 +340,18 @@ function litestarAstro(userConfig = {}) {
311
340
  plugins
312
341
  }
313
342
  };
314
- if (command === "dev" && config.port !== void 0) {
343
+ if (command === "dev") {
315
344
  configUpdate.server = {
316
- port: config.port
345
+ // Force IPv4 binding for consistency with Python proxy configuration
346
+ host: "127.0.0.1",
347
+ // Set port from Python config/env if provided
348
+ ...config.port !== void 0 ? { port: config.port } : {}
317
349
  };
318
350
  if (config.verbose) {
319
- logger.info(`Setting Astro server port to ${config.port}`);
351
+ logger.info("Setting Astro server host to 127.0.0.1");
352
+ if (config.port !== void 0) {
353
+ logger.info(`Setting Astro server port to ${config.port}`);
354
+ }
320
355
  }
321
356
  }
322
357
  updateConfig(configUpdate);
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Litestar HTMX Extension
3
+ *
4
+ * Lightweight JSON templating for HTMX with CSRF support.
5
+ *
6
+ * Features:
7
+ * - `hx-swap="json"` - Client-side JSON templating
8
+ * - Automatic CSRF token injection
9
+ * - Template syntax: `ls-for`, `ls-if`, `:attr`, `@event`, `${expr}`
10
+ *
11
+ * For typed routes, import from your generated routes file:
12
+ * ```ts
13
+ * import { route } from '@/generated/routes'
14
+ * ```
15
+ *
16
+ * @example
17
+ * ```html
18
+ * <div hx-get="/api/books" hx-swap="json" hx-ext="litestar">
19
+ * <template ls-for="book in $data">
20
+ * <article :id="`book-${book.id}`">
21
+ * <h3>${book.title}</h3>
22
+ * <p>${book.author} • ${book.year}</p>
23
+ * </article>
24
+ * </template>
25
+ * </div>
26
+ * ```
27
+ *
28
+ * @module
29
+ */
30
+ /** Type for route function - matches generated routes.ts */
31
+ type RouteFn = (name: string, params?: Record<string, string | number>) => string;
32
+ declare global {
33
+ interface Window {
34
+ htmx?: HtmxApi;
35
+ }
36
+ }
37
+ interface HtmxApi {
38
+ defineExtension: (name: string, ext: HtmxExtension) => void;
39
+ process: (elt: Element) => void;
40
+ }
41
+ interface HtmxExtension {
42
+ init?: () => void;
43
+ onEvent?: (name: string, evt: CustomEvent) => boolean | void;
44
+ transformResponse?: (text: string, xhr: XMLHttpRequest, elt: Element) => string;
45
+ isInlineSwap?: (swapStyle: string) => boolean;
46
+ handleSwap?: (swapStyle: string, target: Element, fragment: DocumentFragment | Element) => Element[];
47
+ }
48
+ /** Template context - inherits from data via prototype for direct access */
49
+ interface Ctx {
50
+ $data: unknown;
51
+ $parent?: Ctx;
52
+ $index?: number;
53
+ $key?: string;
54
+ $event?: Event;
55
+ route?: RouteFn;
56
+ navigate?: (name: string, params?: Record<string, string | number>) => void;
57
+ [key: string]: unknown;
58
+ }
59
+ export declare function registerHtmxExtension(): void;
60
+ export declare function swapJson(el: Element, data: unknown): void;
61
+ type Handler = (ctx: Ctx, el: Element) => Ctx | false | void;
62
+ interface Dir {
63
+ match: (a: Attr) => boolean;
64
+ create: (el: Element, a: Attr) => Handler | null;
65
+ }
66
+ export declare function setDebug(_on: boolean): void;
67
+ export declare function addDirective(dir: Dir): void;
68
+ export {};