litestar-vite-plugin 0.26.1 → 0.28.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/README.md CHANGED
@@ -20,19 +20,10 @@ npm install litestar-vite-plugin
20
20
  ## Quick Start
21
21
 
22
22
  ```python
23
- import os
24
- from pathlib import Path
25
23
  from litestar import Litestar
26
- from litestar_vite import PathConfig, ViteConfig, VitePlugin
24
+ from litestar_vite import VitePlugin
27
25
 
28
- DEV_MODE = os.getenv("VITE_DEV_MODE", "true").lower() in ("true", "1", "yes")
29
-
30
- app = Litestar(
31
- plugins=[VitePlugin(config=ViteConfig(
32
- dev_mode=DEV_MODE,
33
- paths=PathConfig(root=Path(__file__).parent),
34
- ))]
35
- )
26
+ app = Litestar(plugins=[VitePlugin()])
36
27
  ```
37
28
 
38
29
  ```bash
@@ -41,14 +32,42 @@ litestar assets install
41
32
  litestar run --reload
42
33
  ```
43
34
 
44
- ## Documentation
35
+ `VitePlugin()` detects development and production behavior without manual `VITE_DEV_MODE` parsing. The effective
36
+ `litestar run --host/--port` values are also passed to the frontend environment and `.litestar.json` bridge.
37
+
38
+ ### Optional Granian native static serving
39
+
40
+ Granian 0.16+ can serve eligible production bundle files before the request enters Python:
41
+
42
+ ```python
43
+ from litestar import Litestar
44
+ from litestar_granian import GranianPlugin
45
+ from litestar_vite import VitePlugin
46
+
47
+ app = Litestar(
48
+ plugins=[
49
+ VitePlugin(),
50
+ GranianPlugin(static="auto"),
51
+ ]
52
+ )
53
+ ```
54
+
55
+ This is an optimization, not a separate application configuration. The Litestar static route stays registered, so
56
+ Uvicorn and other ASGI servers serve the same files. Granian automatically falls back to Litestar when assets are
57
+ protected, customized, missing, or otherwise unsafe to intercept.
45
58
 
46
- - **[Usage Guide](https://litestar-org.github.io/litestar-vite/usage/index.html)**: Core concepts, configuration, and type generation.
47
- - **[Inertia](https://litestar-org.github.io/litestar-vite/frameworks/inertia/index.html)**: Fullstack protocols and SSR.
48
- - **[Frameworks](https://litestar-org.github.io/litestar-vite/frameworks/index.html)**: Guides for React, Vue, Svelte, Angular, Astro, and Nuxt.
49
- - **[Reference](https://litestar-org.github.io/litestar-vite/reference/index.html)**: API and CLI documentation.
59
+ Native hits bypass ASGI middleware, guards, compression, custom headers, and Python access logging. Keep protected or
60
+ customized assets on the Litestar fallback path. See the
61
+ [production guide](https://litestar-org.github.io/litestar-vite/latest/usage/production.html) for the server matrix and
62
+ advanced configuration.
63
+
64
+ ## Documentation
50
65
 
51
- For a full list of changes, see the [Changelog](https://litestar-org.github.io/litestar-vite/changelog.html).
66
+ - Get started: <https://litestar-org.github.io/litestar-vite/latest/usage/index.html>
67
+ - Framework guides: <https://litestar-org.github.io/litestar-vite/latest/frameworks/index.html>
68
+ - Inertia: <https://litestar-org.github.io/litestar-vite/latest/frameworks/inertia/index.html>
69
+ - API reference: <https://litestar-org.github.io/litestar-vite/latest/reference/index.html>
70
+ - Changelog: <https://litestar-org.github.io/litestar-vite/latest/changelog.html>
52
71
 
53
72
  ## Common Commands
54
73
 
@@ -76,7 +95,7 @@ litestar-vite now defaults Inertia apps to script-element bootstrap. Inertia v3
76
95
 
77
96
  ## Links
78
97
 
79
- - Docs: <https://litestar-org.github.io/litestar-vite/>
98
+ - Docs: <https://litestar-org.github.io/litestar-vite/latest/>
80
99
  - Examples: `examples/` (React, Vue, Svelte, HTMX, Inertia, Astro, Nuxt, SvelteKit, Angular)
81
100
  - Real-world example: [litestar-fullstack](https://github.com/litestar-org/litestar-fullstack)
82
101
  - Issues: <https://github.com/litestar-org/litestar-vite/issues/>
package/dist/js/astro.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { readBridgeConfig } from "./shared/bridge-schema.js";
4
+ import { installManagedShutdown } from "./shared/managed-shutdown.js";
4
5
  import { normalizeHost, resolveHotFilePath, resolveLitestarPort } from "./shared/network.js";
5
6
  import { createLitestarTypeGenPlugin, resolveTypesConfig } from "./shared/typegen-plugin.js";
6
7
  import { hmrServerConfig } from "./shared/vite-compat.js";
@@ -91,6 +92,9 @@ function createProxyPlugin(config) {
91
92
  }
92
93
  }
93
94
  };
95
+ },
96
+ configureServer(server) {
97
+ installManagedShutdown(server);
94
98
  }
95
99
  };
96
100
  }
@@ -7,8 +7,8 @@
7
7
  <link rel="preconnect" href="https://fonts.googleapis.com" />
8
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
9
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" />
10
- <style rel="stylesheet" crossorigin>/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
11
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-duration:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0}}}@layer theme{:root,:host{--font-sans:"Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--font-mono:"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;--spacing:.25rem;--container-lg:32rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--font-weight-light:300;--font-weight-medium:500;--font-weight-semibold:600;--tracking-widest:.1em;--leading-relaxed:1.625;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-navy:#202235;--color-navy-surface:#2a2d40;--color-navy-surface-variant:#1a1c2e;--color-gold:#edb641;--color-gold-light:#ffd480;--color-on-surface:#f8fafc;--color-on-surface-muted:#cbd5e1;--color-on-surface-subtle:#94a3b8;--color-outline:#334155;--color-success:#4ade80;--radius-brand:8px;--radius-brand-lg:12px;--shadow-text-glow-dark:0 0 24px #edb64138}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.absolute{position:absolute}.relative{position:relative}.mt-8{margin-top:calc(var(--spacing) * 8)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-10{margin-bottom:calc(var(--spacing) * 10)}.mb-12{margin-bottom:calc(var(--spacing) * 12)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.inline{display:inline}.inline-flex{display:inline-flex}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-12{height:calc(var(--spacing) * 12)}.h-14{height:calc(var(--spacing) * 14)}.h-full{height:100%}.min-h-screen{min-height:100vh}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-auto{width:auto}.w-full{width:100%}.max-w-lg{max-width:var(--container-lg)}.min-w-0{min-width:0}.flex-1{flex:1}.flex-none{flex:none}.animate-pulse-dot{animation:2s ease-in-out infinite pulse-dot}.items-center{align-items:center}.justify-center{justify-content:center}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-6{gap:calc(var(--spacing) * 6)}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)))}.rounded-brand{border-radius:var(--radius-brand)}.rounded-brand-lg{border-radius:var(--radius-brand-lg)}.rounded-full{border-radius:3.40282e38px}.border{border-style:var(--tw-border-style);border-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-\[3px\]{border-left-style:var(--tw-border-style);border-left-width:3px}.border-outline{border-color:var(--color-outline)}.border-l-gold{border-left-color:var(--color-gold)}.bg-navy-surface{background-color:var(--color-navy-surface)}.bg-navy-surface-variant{background-color:var(--color-navy-surface-variant)}.bg-success{background-color:var(--color-success)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.px-8{padding-inline:calc(var(--spacing) * 8)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-16{padding-block:calc(var(--spacing) * 16)}.text-center{text-align:center}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-all{word-break:break-all}.text-gold{color:var(--color-gold)}.text-on-surface{color:var(--color-on-surface)}.text-on-surface-muted{color:var(--color-on-surface-muted)}.text-on-surface-subtle{color:var(--color-on-surface-subtle)}.uppercase{text-transform:uppercase}.opacity-60{opacity:.6}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.select-all{-webkit-user-select:all;user-select:all}.text-brand-glow{text-shadow:var(--shadow-text-glow-dark)}@media (hover:hover){.group-hover\:translate-x-0\.5:is(:where(.group):hover *){--tw-translate-x:calc(var(--spacing) * .5);translate:var(--tw-translate-x) var(--tw-translate-y)}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}.hover\:bg-navy:hover{background-color:var(--color-navy)}.hover\:text-gold:hover{color:var(--color-gold)}.hover\:text-gold-light:hover{color:var(--color-gold-light)}.hover\:opacity-80:hover{opacity:.8}}@media (width>=40rem){.sm\:px-10{padding-inline:calc(var(--spacing) * 10)}.sm\:py-10{padding-block:calc(var(--spacing) * 10)}.sm\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}}}html{font-family:var(--font-sans);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{background-color:var(--color-navy);color:var(--color-on-surface-muted);background-image:radial-gradient(#f8fafc0a 1px,#0000 1px);background-size:24px 24px}@keyframes pulse-dot{0%,to{opacity:1;transform:scale(1)}50%{opacity:.55;transform:scale(.92)}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}
10
+ <style rel="stylesheet" crossorigin>/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
11
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-duration:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0}}}@layer theme{:root,:host{--font-sans:"Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--font-mono:"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;--spacing:.25rem;--container-lg:32rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--font-weight-light:300;--font-weight-medium:500;--font-weight-semibold:600;--tracking-widest:.1em;--leading-relaxed:1.625;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-navy:#202235;--color-navy-surface:#2a2d40;--color-navy-surface-variant:#1a1c2e;--color-gold:#edb641;--color-gold-light:#ffd480;--color-on-surface:#f8fafc;--color-on-surface-muted:#cbd5e1;--color-on-surface-subtle:#94a3b8;--color-outline:#334155;--color-success:#4ade80;--radius-brand:8px;--radius-brand-lg:12px;--shadow-text-glow-dark:0 0 24px #edb64138}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.absolute{position:absolute}.relative{position:relative}.mt-8{margin-top:calc(var(--spacing) * 8)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-10{margin-bottom:calc(var(--spacing) * 10)}.mb-12{margin-bottom:calc(var(--spacing) * 12)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.inline{display:inline}.inline-flex{display:inline-flex}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-12{height:calc(var(--spacing) * 12)}.h-14{height:calc(var(--spacing) * 14)}.h-full{height:100%}.min-h-screen{min-height:100vh}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-auto{width:auto}.w-full{width:100%}.max-w-lg{max-width:var(--container-lg)}.min-w-0{min-width:0}.flex-1{flex:1}.flex-none{flex:none}.animate-pulse-dot{animation:2s ease-in-out infinite pulse-dot}.items-center{align-items:center}.justify-center{justify-content:center}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-6{gap:calc(var(--spacing) * 6)}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)))}.rounded-brand{border-radius:var(--radius-brand)}.rounded-brand-lg{border-radius:var(--radius-brand-lg)}.rounded-full{border-radius:3.40282e38px}.border{border-style:var(--tw-border-style);border-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-\[3px\]{border-left-style:var(--tw-border-style);border-left-width:3px}.border-outline{border-color:var(--color-outline)}.border-l-gold{border-left-color:var(--color-gold)}.bg-navy-surface{background-color:var(--color-navy-surface)}.bg-navy-surface-variant{background-color:var(--color-navy-surface-variant)}.bg-success{background-color:var(--color-success)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.px-8{padding-inline:calc(var(--spacing) * 8)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-16{padding-block:calc(var(--spacing) * 16)}.text-center{text-align:center}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-all{word-break:break-all}.text-gold{color:var(--color-gold)}.text-on-surface{color:var(--color-on-surface)}.text-on-surface-muted{color:var(--color-on-surface-muted)}.text-on-surface-subtle{color:var(--color-on-surface-subtle)}.uppercase{text-transform:uppercase}.opacity-60{opacity:.6}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.select-all{-webkit-user-select:all;user-select:all}.text-brand-glow{text-shadow:var(--shadow-text-glow-dark)}@media (hover:hover){.group-hover\:translate-x-0\.5:is(:where(.group):hover *){--tw-translate-x:calc(var(--spacing) * .5);translate:var(--tw-translate-x) var(--tw-translate-y)}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}.hover\:bg-navy:hover{background-color:var(--color-navy)}.hover\:text-gold:hover{color:var(--color-gold)}.hover\:text-gold-light:hover{color:var(--color-gold-light)}.hover\:opacity-80:hover{opacity:.8}}@media (width>=40rem){.sm\:px-10{padding-inline:calc(var(--spacing) * 10)}.sm\:py-10{padding-block:calc(var(--spacing) * 10)}.sm\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}}}html{font-family:var(--font-sans);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{background-color:var(--color-navy);color:var(--color-on-surface-muted);background-image:radial-gradient(#f8fafc0a 1px,#0000 1px);background-size:24px 24px}@keyframes pulse-dot{0%,to{opacity:1;transform:scale(1)}50%{opacity:.55;transform:scale(.92)}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}
12
12
  /*$vite$:1*/</style>
13
13
  </head>
14
14
  <body class="flex min-h-screen items-center justify-center px-6 py-16">
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Browser helper for WebSocket routes generated by Litestar's ChannelsPlugin.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { createChannelsStream } from "litestar-vite-plugin/helpers"
7
+ *
8
+ * const stream = createChannelsStream({
9
+ * basePath: "/ws",
10
+ * channel: "notifications",
11
+ * onEvent: (event) => console.log(event),
12
+ * })
13
+ * stream.connect()
14
+ * ```
15
+ *
16
+ * @module
17
+ */
18
+ import { type EventStream, type EventStreamConfig } from "./stream.js";
19
+ export type ChannelName = string | (() => string);
20
+ export interface ChannelsStreamOptions<TFrame = unknown> extends Omit<EventStreamConfig<TFrame>, "EventSourceCtor" | "sseEvents" | "transport"> {
21
+ channel: ChannelName;
22
+ basePath?: string;
23
+ transformUrl?: (url: URL) => string | URL;
24
+ }
25
+ /**
26
+ * Create a stream for a WebSocket route generated by ChannelsPlugin.
27
+ *
28
+ * @param options - Channel name, server route prefix, and stream callbacks.
29
+ * @returns A disposable stream that connects only when `connect()` is called.
30
+ */
31
+ export declare function createChannelsStream<TFrame = unknown>(options: ChannelsStreamOptions<TFrame>): EventStream;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Browser helper for WebSocket routes generated by Litestar's ChannelsPlugin.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { createChannelsStream } from "litestar-vite-plugin/helpers"
7
+ *
8
+ * const stream = createChannelsStream({
9
+ * basePath: "/ws",
10
+ * channel: "notifications",
11
+ * onEvent: (event) => console.log(event),
12
+ * })
13
+ * stream.connect()
14
+ * ```
15
+ *
16
+ * @module
17
+ */
18
+ import { createEventStream, resolveStreamUrl } from "./stream.js";
19
+ /**
20
+ * Create a stream for a WebSocket route generated by ChannelsPlugin.
21
+ *
22
+ * @param options - Channel name, server route prefix, and stream callbacks.
23
+ * @returns A disposable stream that connects only when `connect()` is called.
24
+ */
25
+ export function createChannelsStream(options) {
26
+ const { basePath = "/", channel, transformUrl, ...streamOptions } = options;
27
+ return createEventStream({
28
+ ...streamOptions,
29
+ buildUrl: () => {
30
+ const channelName = typeof channel === "function" ? channel() : channel;
31
+ const prefix = basePath.endsWith("/") ? basePath : `${basePath}/`;
32
+ const url = new URL(resolveStreamUrl(`${prefix}${encodeURIComponent(channelName)}`, "websocket"));
33
+ return transformUrl?.(url) ?? url;
34
+ },
35
+ transport: "websocket",
36
+ });
37
+ }
@@ -39,5 +39,9 @@
39
39
  * @module
40
40
  */
41
41
  export { csrfFetch, csrfHeaders, getCsrfToken } from "./csrf.js";
42
+ export { createChannelsStream, type ChannelName, type ChannelsStreamOptions } from "./channels.js";
42
43
  export { addDirective, registerHtmxExtension, setDebug as setHtmxDebug, swapJson } from "./htmx.js";
44
+ export { createQueueEventStream, QUEUE_SSE_EVENTS, type QueueEventStreamOptions, type QueueStreamTarget, type QueueStreamValue } from "./queues.js";
43
45
  export { createRouteHelpers, currentRoute, isCurrentRoute, isRoute, type RouteDefinition, type RouteDefinitions, type RouteHelpers, toRoute } from "./routes.js";
46
+ export { createEventStream, type EventStream, type EventStreamOptions, type StreamGap } from "./stream.js";
47
+ export { defineStreamElement, LitestarStreamElement, type StreamElementOptions } from "./stream-element.js";
@@ -40,7 +40,15 @@
40
40
  */
41
41
  // CSRF utilities
42
42
  export { csrfFetch, csrfHeaders, getCsrfToken } from "./csrf.js";
43
+ // Litestar Channels utilities
44
+ export { createChannelsStream } from "./channels.js";
43
45
  // HTMX utilities
44
46
  export { addDirective, registerHtmxExtension, setDebug as setHtmxDebug, swapJson } from "./htmx.js";
47
+ // Litestar Queues utilities
48
+ export { createQueueEventStream, QUEUE_SSE_EVENTS } from "./queues.js";
45
49
  // Route matching utilities
46
50
  export { createRouteHelpers, currentRoute, isCurrentRoute, isRoute, toRoute } from "./routes.js";
51
+ // Realtime stream utilities
52
+ export { createEventStream } from "./stream.js";
53
+ // Declarative realtime stream element
54
+ export { defineStreamElement, LitestarStreamElement } from "./stream-element.js";
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Browser helper for routes generated by Litestar Queues EventStreamConfig.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { createQueueEventStream } from "litestar-vite-plugin/helpers"
7
+ *
8
+ * const stream = createQueueEventStream({
9
+ * scope: "task",
10
+ * taskId: "01J...",
11
+ * onEvent: (event) => console.log(event),
12
+ * })
13
+ * stream.connect()
14
+ * ```
15
+ *
16
+ * @module
17
+ */
18
+ import { type EventStream, type EventStreamConfig, type StreamUrl } from "./stream.js";
19
+ export declare const QUEUE_SSE_EVENTS: readonly ["task.started", "task.progress", "task.log", "task.event", "task.completed", "task.failed", "task.cancelled", "task.claim_lost", "task.stale_failed", "worker.heartbeat", "worker.stale_recovery"];
20
+ export type QueueStreamValue = string | (() => string);
21
+ export type QueueStreamTarget = {
22
+ scope: "task";
23
+ taskId: QueueStreamValue;
24
+ } | {
25
+ scope: "queue";
26
+ queue: QueueStreamValue;
27
+ } | {
28
+ scope: "worker";
29
+ workerId: QueueStreamValue;
30
+ } | {
31
+ scope: "global";
32
+ } | {
33
+ scope: "custom";
34
+ scopeKey: QueueStreamValue;
35
+ };
36
+ type QueueRouteStreamOptions = QueueStreamTarget & {
37
+ url?: never;
38
+ buildUrl?: never;
39
+ basePath?: string;
40
+ };
41
+ type QueueDirectStreamOptions = {
42
+ url: StreamUrl;
43
+ buildUrl?: never;
44
+ basePath?: never;
45
+ } | {
46
+ url?: never;
47
+ buildUrl: () => string | URL;
48
+ basePath?: never;
49
+ };
50
+ export type QueueEventStreamOptions<TFrame = unknown> = EventStreamConfig<TFrame> & (QueueRouteStreamOptions | QueueDirectStreamOptions) & {
51
+ basePath?: string;
52
+ transformUrl?: (url: URL) => string | URL;
53
+ };
54
+ /**
55
+ * Create a stream for routes generated by QueuePlugin EventStreamConfig.
56
+ *
57
+ * @param options - Queue target, transport, and stream callbacks.
58
+ * @returns A disposable stream that connects only when `connect()` is called.
59
+ */
60
+ export declare function createQueueEventStream<TFrame = unknown>(options: QueueEventStreamOptions<TFrame>): EventStream;
61
+ export {};
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Browser helper for routes generated by Litestar Queues EventStreamConfig.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { createQueueEventStream } from "litestar-vite-plugin/helpers"
7
+ *
8
+ * const stream = createQueueEventStream({
9
+ * scope: "task",
10
+ * taskId: "01J...",
11
+ * onEvent: (event) => console.log(event),
12
+ * })
13
+ * stream.connect()
14
+ * ```
15
+ *
16
+ * @module
17
+ */
18
+ import { createEventStream, resolveStreamUrl } from "./stream.js";
19
+ export const QUEUE_SSE_EVENTS = [
20
+ "task.started",
21
+ "task.progress",
22
+ "task.log",
23
+ "task.event",
24
+ "task.completed",
25
+ "task.failed",
26
+ "task.cancelled",
27
+ "task.claim_lost",
28
+ "task.stale_failed",
29
+ "worker.heartbeat",
30
+ "worker.stale_recovery",
31
+ ];
32
+ function asRecord(value) {
33
+ return typeof value === "object" && value !== null ? value : undefined;
34
+ }
35
+ function defaultQueueIsHeartbeat(frame) {
36
+ return asRecord(frame)?.type === "ping";
37
+ }
38
+ function defaultQueueGetEventKey(frame) {
39
+ const record = asRecord(frame);
40
+ const key = record?.eventKey ?? record?.id;
41
+ return typeof key === "string" ? key : undefined;
42
+ }
43
+ function defaultQueueGetSequence(frame) {
44
+ const record = asRecord(frame);
45
+ const sequence = record?.sequence;
46
+ const taskId = record?.taskId;
47
+ const attempt = record?.attempt;
48
+ if (typeof sequence !== "number" || !Number.isFinite(sequence) || typeof taskId !== "string" || (typeof attempt !== "string" && typeof attempt !== "number")) {
49
+ return undefined;
50
+ }
51
+ return { stream: `${taskId}:${attempt}`, value: sequence };
52
+ }
53
+ function resolveValue(value) {
54
+ return typeof value === "function" ? value() : value;
55
+ }
56
+ function queueRouteSuffix(target) {
57
+ switch (target.scope) {
58
+ case "task":
59
+ return `tasks/${encodeURIComponent(resolveValue(target.taskId))}`;
60
+ case "queue":
61
+ return `queues/${encodeURIComponent(resolveValue(target.queue))}`;
62
+ case "worker":
63
+ return `workers/${encodeURIComponent(resolveValue(target.workerId))}`;
64
+ case "global":
65
+ return "global";
66
+ case "custom":
67
+ return `custom/${encodeURIComponent(resolveValue(target.scopeKey))}`;
68
+ }
69
+ }
70
+ function queueStreamUrl(target, basePath, transport) {
71
+ const root = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
72
+ const transportPath = transport === "sse" ? "sse/" : "";
73
+ return new URL(resolveStreamUrl(`${root}/${transportPath}${queueRouteSuffix(target)}`, transport));
74
+ }
75
+ /**
76
+ * Create a stream for routes generated by QueuePlugin EventStreamConfig.
77
+ *
78
+ * @param options - Queue target, transport, and stream callbacks.
79
+ * @returns A disposable stream that connects only when `connect()` is called.
80
+ */
81
+ export function createQueueEventStream(options) {
82
+ const { baseDelayMs, basePath = "/queues/events", dedupWindow, EventSourceCtor, getEventKey = defaultQueueGetEventKey, getSequence = defaultQueueGetSequence, isHeartbeat = defaultQueueIsHeartbeat, maxDelayMs, onEvent, onClose, onGap, onHealthChange, onOpen, onReconnect, parseFrame, shouldReconnect, sseEvents = QUEUE_SSE_EVENTS, transformUrl, transport = "websocket", WebSocketCtor, } = options;
83
+ return createEventStream({
84
+ baseDelayMs,
85
+ buildUrl: () => {
86
+ let endpoint;
87
+ if ("buildUrl" in options && options.buildUrl !== undefined) {
88
+ endpoint = options.buildUrl();
89
+ }
90
+ else if ("url" in options && options.url !== undefined) {
91
+ endpoint = typeof options.url === "function" ? options.url() : options.url;
92
+ }
93
+ else {
94
+ endpoint = queueStreamUrl(options, basePath, transport);
95
+ }
96
+ const url = new URL(resolveStreamUrl(endpoint, transport));
97
+ return transformUrl?.(url) ?? url;
98
+ },
99
+ dedupWindow,
100
+ EventSourceCtor,
101
+ getEventKey,
102
+ getSequence,
103
+ isHeartbeat,
104
+ maxDelayMs,
105
+ onEvent,
106
+ onClose,
107
+ onGap,
108
+ onHealthChange,
109
+ onOpen,
110
+ onReconnect,
111
+ parseFrame,
112
+ shouldReconnect,
113
+ sseEvents,
114
+ transport,
115
+ WebSocketCtor,
116
+ });
117
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Declarative lifecycle binding for JSON WebSocket and SSE streams.
3
+ *
4
+ * This element replaces `htmx-ext-ws` and `htmx-ext-sse` for JSON streams.
5
+ * Do not layer both reconnect implementations on the same endpoint.
6
+ *
7
+ * @example
8
+ * ```html
9
+ * <litestar-stream url="/events" transport="sse" swap="json">
10
+ * <template ls-for="item in $data.items"><p>${item}</p></template>
11
+ * </litestar-stream>
12
+ * ```
13
+ *
14
+ * @module
15
+ */
16
+ import { type EventStreamConfig } from "./stream.js";
17
+ type StreamCallbackConfig = Pick<EventStreamConfig, "EventSourceCtor" | "WebSocketCtor" | "getEventKey" | "getSequence" | "isHeartbeat" | "onEvent" | "onGap" | "onHealthChange" | "onReconnect" | "parseFrame" | "shouldReconnect">;
18
+ type HTMLElementConstructor = new (...args: never[]) => HTMLElement;
19
+ declare const HTMLElementBase: HTMLElementConstructor;
20
+ export interface StreamElementOptions {
21
+ tagName?: string;
22
+ }
23
+ export declare class LitestarStreamElement extends HTMLElementBase {
24
+ static readonly observedAttributes: string[];
25
+ private stream;
26
+ private _buildUrl;
27
+ private _onEvent;
28
+ private _onGap;
29
+ private _onHealthChange;
30
+ private _onReconnect;
31
+ private _shouldReconnect;
32
+ private _isHeartbeat;
33
+ private _getEventKey;
34
+ private _getSequence;
35
+ private _parseFrame;
36
+ private _WebSocketCtor;
37
+ private _EventSourceCtor;
38
+ constructor();
39
+ get buildUrl(): (() => string | URL) | undefined;
40
+ set buildUrl(value: (() => string | URL) | undefined);
41
+ get onEvent(): StreamCallbackConfig["onEvent"] | undefined;
42
+ set onEvent(value: StreamCallbackConfig["onEvent"] | undefined);
43
+ get onGap(): StreamCallbackConfig["onGap"];
44
+ set onGap(value: StreamCallbackConfig["onGap"]);
45
+ get onHealthChange(): StreamCallbackConfig["onHealthChange"];
46
+ set onHealthChange(value: StreamCallbackConfig["onHealthChange"]);
47
+ get onReconnect(): StreamCallbackConfig["onReconnect"];
48
+ set onReconnect(value: StreamCallbackConfig["onReconnect"]);
49
+ get shouldReconnect(): StreamCallbackConfig["shouldReconnect"];
50
+ set shouldReconnect(value: StreamCallbackConfig["shouldReconnect"]);
51
+ get isHeartbeat(): StreamCallbackConfig["isHeartbeat"];
52
+ set isHeartbeat(value: StreamCallbackConfig["isHeartbeat"]);
53
+ get getEventKey(): StreamCallbackConfig["getEventKey"];
54
+ set getEventKey(value: StreamCallbackConfig["getEventKey"]);
55
+ get getSequence(): StreamCallbackConfig["getSequence"];
56
+ set getSequence(value: StreamCallbackConfig["getSequence"]);
57
+ get parseFrame(): StreamCallbackConfig["parseFrame"];
58
+ set parseFrame(value: StreamCallbackConfig["parseFrame"]);
59
+ get WebSocketCtor(): StreamCallbackConfig["WebSocketCtor"];
60
+ set WebSocketCtor(value: StreamCallbackConfig["WebSocketCtor"]);
61
+ get EventSourceCtor(): StreamCallbackConfig["EventSourceCtor"];
62
+ set EventSourceCtor(value: StreamCallbackConfig["EventSourceCtor"]);
63
+ get healthy(): boolean;
64
+ connectedCallback(): void;
65
+ disconnectedCallback(): void;
66
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
67
+ private upgradeProperty;
68
+ private start;
69
+ private stop;
70
+ private restart;
71
+ private dispatchStreamEvent;
72
+ }
73
+ /**
74
+ * Register the declarative stream element.
75
+ *
76
+ * @param options - Optional custom tag name.
77
+ */
78
+ export declare function defineStreamElement(options?: StreamElementOptions): void;
79
+ export {};