litestar-vite-plugin 0.27.0 → 0.29.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.
Files changed (49) hide show
  1. package/README.md +37 -18
  2. package/dist/js/astro.d.ts +12 -121
  3. package/dist/js/astro.js +8 -55
  4. package/dist/js/helpers/channels.d.ts +31 -0
  5. package/dist/js/helpers/channels.js +37 -0
  6. package/dist/js/helpers/csrf.d.ts +3 -1
  7. package/dist/js/helpers/csrf.js +26 -12
  8. package/dist/js/helpers/expression.d.ts +3 -0
  9. package/dist/js/helpers/expression.js +524 -0
  10. package/dist/js/helpers/htmx.d.ts +1 -1
  11. package/dist/js/helpers/htmx.js +33 -140
  12. package/dist/js/helpers/index.d.ts +5 -1
  13. package/dist/js/helpers/index.js +9 -1
  14. package/dist/js/helpers/queues.d.ts +61 -0
  15. package/dist/js/helpers/queues.js +117 -0
  16. package/dist/js/helpers/stream-element.d.ts +79 -0
  17. package/dist/js/helpers/stream-element.js +229 -0
  18. package/dist/js/helpers/stream.d.ts +76 -0
  19. package/dist/js/helpers/stream.js +242 -0
  20. package/dist/js/index.d.ts +4 -106
  21. package/dist/js/index.js +4 -0
  22. package/dist/js/install-hint.js +8 -15
  23. package/dist/js/nuxt.d.ts +12 -128
  24. package/dist/js/nuxt.js +17 -74
  25. package/dist/js/react/index.d.ts +28 -0
  26. package/dist/js/react/index.js +54 -0
  27. package/dist/js/shared/bridge-schema.d.ts +10 -7
  28. package/dist/js/shared/bridge-schema.js +6 -0
  29. package/dist/js/shared/emit-page-props-types.js +22 -6
  30. package/dist/js/shared/emit-static-props-types.js +6 -2
  31. package/dist/js/shared/integration-config.d.ts +75 -0
  32. package/dist/js/shared/integration-config.js +59 -0
  33. package/dist/js/shared/logger.d.ts +0 -4
  34. package/dist/js/shared/logger.js +1 -2
  35. package/dist/js/shared/managed-shutdown.d.ts +8 -0
  36. package/dist/js/shared/managed-shutdown.js +26 -0
  37. package/dist/js/shared/network.d.ts +4 -3
  38. package/dist/js/shared/typegen-core.d.ts +2 -9
  39. package/dist/js/shared/typegen-core.js +0 -1
  40. package/dist/js/shared/typegen-plugin.d.ts +84 -0
  41. package/dist/js/shared/vite-compat.d.ts +0 -4
  42. package/dist/js/shared/vite-compat.js +1 -3
  43. package/dist/js/svelte/index.d.ts +27 -0
  44. package/dist/js/svelte/index.js +51 -0
  45. package/dist/js/sveltekit.d.ts +12 -128
  46. package/dist/js/sveltekit.js +6 -55
  47. package/dist/js/vue/index.d.ts +29 -0
  48. package/dist/js/vue/index.js +57 -0
  49. package/package.json +47 -4
@@ -0,0 +1,229 @@
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 { swapJson } from "./htmx.js";
17
+ import { createQueueEventStream } from "./queues.js";
18
+ import { createEventStream } from "./stream.js";
19
+ const HTMLElementBase = (typeof HTMLElement === "undefined" ? class {
20
+ } : HTMLElement);
21
+ export class LitestarStreamElement extends HTMLElementBase {
22
+ static observedAttributes = ["url", "transport", "sse-events", "preset", "swap", "disabled"];
23
+ stream = null;
24
+ _buildUrl;
25
+ _onEvent;
26
+ _onGap;
27
+ _onHealthChange;
28
+ _onReconnect;
29
+ _shouldReconnect;
30
+ _isHeartbeat;
31
+ _getEventKey;
32
+ _getSequence;
33
+ _parseFrame;
34
+ _WebSocketCtor;
35
+ _EventSourceCtor;
36
+ constructor() {
37
+ super();
38
+ for (const property of [
39
+ "buildUrl",
40
+ "onEvent",
41
+ "onGap",
42
+ "onHealthChange",
43
+ "onReconnect",
44
+ "shouldReconnect",
45
+ "isHeartbeat",
46
+ "getEventKey",
47
+ "getSequence",
48
+ "parseFrame",
49
+ "WebSocketCtor",
50
+ "EventSourceCtor",
51
+ ]) {
52
+ this.upgradeProperty(property);
53
+ }
54
+ }
55
+ get buildUrl() {
56
+ return this._buildUrl;
57
+ }
58
+ set buildUrl(value) {
59
+ this._buildUrl = value;
60
+ this.restart();
61
+ }
62
+ get onEvent() {
63
+ return this._onEvent;
64
+ }
65
+ set onEvent(value) {
66
+ this._onEvent = value;
67
+ }
68
+ get onGap() {
69
+ return this._onGap;
70
+ }
71
+ set onGap(value) {
72
+ this._onGap = value;
73
+ }
74
+ get onHealthChange() {
75
+ return this._onHealthChange;
76
+ }
77
+ set onHealthChange(value) {
78
+ this._onHealthChange = value;
79
+ }
80
+ get onReconnect() {
81
+ return this._onReconnect;
82
+ }
83
+ set onReconnect(value) {
84
+ this._onReconnect = value;
85
+ }
86
+ get shouldReconnect() {
87
+ return this._shouldReconnect;
88
+ }
89
+ set shouldReconnect(value) {
90
+ this._shouldReconnect = value;
91
+ }
92
+ get isHeartbeat() {
93
+ return this._isHeartbeat;
94
+ }
95
+ set isHeartbeat(value) {
96
+ this._isHeartbeat = value;
97
+ }
98
+ get getEventKey() {
99
+ return this._getEventKey;
100
+ }
101
+ set getEventKey(value) {
102
+ this._getEventKey = value;
103
+ }
104
+ get getSequence() {
105
+ return this._getSequence;
106
+ }
107
+ set getSequence(value) {
108
+ this._getSequence = value;
109
+ }
110
+ get parseFrame() {
111
+ return this._parseFrame;
112
+ }
113
+ set parseFrame(value) {
114
+ this._parseFrame = value;
115
+ }
116
+ get WebSocketCtor() {
117
+ return this._WebSocketCtor;
118
+ }
119
+ set WebSocketCtor(value) {
120
+ this._WebSocketCtor = value;
121
+ }
122
+ get EventSourceCtor() {
123
+ return this._EventSourceCtor;
124
+ }
125
+ set EventSourceCtor(value) {
126
+ this._EventSourceCtor = value;
127
+ }
128
+ get healthy() {
129
+ return this.stream?.healthy ?? false;
130
+ }
131
+ connectedCallback() {
132
+ this.start();
133
+ }
134
+ disconnectedCallback() {
135
+ this.stop();
136
+ }
137
+ attributeChangedCallback(name, oldValue, newValue) {
138
+ if (oldValue === newValue || name === "swap") {
139
+ return;
140
+ }
141
+ this.restart();
142
+ }
143
+ upgradeProperty(property) {
144
+ if (!Object.prototype.hasOwnProperty.call(this, property)) {
145
+ return;
146
+ }
147
+ const value = this[property];
148
+ delete this[property];
149
+ this[property] = value;
150
+ }
151
+ start() {
152
+ if (!this.isConnected || this.hasAttribute("disabled") || this.stream !== null) {
153
+ return;
154
+ }
155
+ const url = this.getAttribute("url");
156
+ if (this._buildUrl === undefined && url === null) {
157
+ return;
158
+ }
159
+ const transport = this.getAttribute("transport") === "sse" ? "sse" : "websocket";
160
+ const sseEvents = this.getAttribute("sse-events")
161
+ ?.split(",")
162
+ .map((event) => event.trim())
163
+ .filter(Boolean);
164
+ const config = {
165
+ EventSourceCtor: this._EventSourceCtor,
166
+ WebSocketCtor: this._WebSocketCtor,
167
+ getEventKey: this._getEventKey,
168
+ getSequence: this._getSequence,
169
+ isHeartbeat: this._isHeartbeat,
170
+ onClose: () => this.dispatchStreamEvent("litestar:stream-close", {}),
171
+ onEvent: (frame) => {
172
+ this._onEvent?.(frame);
173
+ this.dispatchStreamEvent("litestar:stream-event", frame);
174
+ if (this.getAttribute("swap") === "json") {
175
+ swapJson(this, frame);
176
+ }
177
+ },
178
+ onGap: (gap) => {
179
+ this._onGap?.(gap);
180
+ this.dispatchStreamEvent("litestar:stream-gap", gap);
181
+ },
182
+ onHealthChange: (healthy) => {
183
+ this._onHealthChange?.(healthy);
184
+ this.dispatchStreamEvent("litestar:stream-health", { healthy });
185
+ },
186
+ onOpen: (resolvedUrl) => this.dispatchStreamEvent("litestar:stream-open", { url: resolvedUrl }),
187
+ onReconnect: () => this._onReconnect?.(),
188
+ parseFrame: this._parseFrame,
189
+ shouldReconnect: this._shouldReconnect,
190
+ sseEvents,
191
+ transport: transport,
192
+ };
193
+ const endpoint = this._buildUrl === undefined ? { url: url } : { buildUrl: this._buildUrl };
194
+ this.stream = this.getAttribute("preset") === "queues" ? createQueueEventStream({ ...config, ...endpoint }) : createEventStream({ ...config, ...endpoint });
195
+ this.stream.connect();
196
+ }
197
+ stop() {
198
+ const stream = this.stream;
199
+ this.stream = null;
200
+ stream?.dispose();
201
+ }
202
+ restart() {
203
+ if (!this.isConnected) {
204
+ return;
205
+ }
206
+ this.stop();
207
+ this.start();
208
+ }
209
+ dispatchStreamEvent(type, detail) {
210
+ this.dispatchEvent(new CustomEvent(type, { bubbles: true, composed: false, detail }));
211
+ }
212
+ }
213
+ /**
214
+ * Register the declarative stream element.
215
+ *
216
+ * @param options - Optional custom tag name.
217
+ */
218
+ export function defineStreamElement(options = {}) {
219
+ if (typeof window === "undefined" || typeof customElements === "undefined") {
220
+ return;
221
+ }
222
+ const tagName = options.tagName ?? "litestar-stream";
223
+ if (customElements.get(tagName) !== undefined) {
224
+ return;
225
+ }
226
+ const ElementClass = tagName === "litestar-stream" ? LitestarStreamElement : class extends LitestarStreamElement {
227
+ };
228
+ customElements.define(tagName, ElementClass);
229
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Resilient WebSocket and SSE helpers for Litestar event streams.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { createEventStream } from "litestar-vite-plugin/helpers"
7
+ *
8
+ * const stream = createEventStream({
9
+ * url: "/events",
10
+ * onEvent: (event) => console.log(event),
11
+ * })
12
+ *
13
+ * stream.connect()
14
+ * ```
15
+ *
16
+ * @module
17
+ */
18
+ export interface StreamGap {
19
+ stream: string;
20
+ from: number;
21
+ to: number;
22
+ missing: number;
23
+ }
24
+ export type EventStreamTransport = "websocket" | "sse";
25
+ export type StreamUrl = string | URL | (() => string | URL);
26
+ export interface EventStreamConfig<TFrame = unknown> {
27
+ transport?: "websocket" | "sse";
28
+ sseEvents?: readonly string[];
29
+ onEvent: (frame: TFrame) => void;
30
+ onOpen?: (url: string) => void;
31
+ onClose?: () => void;
32
+ onHealthChange?: (healthy: boolean) => void;
33
+ onReconnect?: () => void;
34
+ onGap?: (gap: StreamGap) => void;
35
+ shouldReconnect?: (closeCode: number) => boolean;
36
+ isHeartbeat?: (frame: TFrame) => boolean;
37
+ getEventKey?: (frame: TFrame) => string | undefined;
38
+ getSequence?: (frame: TFrame) => {
39
+ stream: string;
40
+ value: number;
41
+ } | undefined;
42
+ baseDelayMs?: number;
43
+ maxDelayMs?: number;
44
+ dedupWindow?: number;
45
+ parseFrame?: (data: string) => TFrame;
46
+ WebSocketCtor?: typeof WebSocket;
47
+ EventSourceCtor?: typeof EventSource;
48
+ }
49
+ export type EventStreamOptions<TFrame = unknown> = EventStreamConfig<TFrame> & ({
50
+ url: StreamUrl;
51
+ buildUrl?: never;
52
+ } | {
53
+ url?: never;
54
+ buildUrl: () => string | URL;
55
+ });
56
+ export interface EventStream {
57
+ connect(): void;
58
+ dispose(): void;
59
+ readonly healthy: boolean;
60
+ }
61
+ /**
62
+ * Resolve a stream endpoint against the browser origin.
63
+ *
64
+ * @param value - Absolute or same-origin relative endpoint.
65
+ * @param transport - Transport whose URL protocol should be used.
66
+ * @param baseUrl - Resolution base. Defaults to the current browser location.
67
+ * @returns An absolute transport URL.
68
+ */
69
+ export declare function resolveStreamUrl(value: string | URL, transport: EventStreamTransport, baseUrl?: string | URL): string;
70
+ /**
71
+ * Create a reconnecting, transport-agnostic Litestar event stream.
72
+ *
73
+ * @param options - Stream transport, lifecycle, and frame-processing options.
74
+ * @returns A disposable stream that connects only when `connect()` is called.
75
+ */
76
+ export declare function createEventStream<TFrame = unknown>(options: EventStreamOptions<TFrame>): EventStream;
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Resilient WebSocket and SSE helpers for Litestar event streams.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { createEventStream } from "litestar-vite-plugin/helpers"
7
+ *
8
+ * const stream = createEventStream({
9
+ * url: "/events",
10
+ * onEvent: (event) => console.log(event),
11
+ * })
12
+ *
13
+ * stream.connect()
14
+ * ```
15
+ *
16
+ * @module
17
+ */
18
+ const DEFAULT_BASE_DELAY_MS = 1000;
19
+ const DEFAULT_MAX_DELAY_MS = 10_000;
20
+ const DEFAULT_DEDUP_WINDOW = 1024;
21
+ const DEFAULT_SSE_EVENTS = ["message"];
22
+ function defaultParseFrame(data) {
23
+ try {
24
+ return JSON.parse(data);
25
+ }
26
+ catch {
27
+ return data;
28
+ }
29
+ }
30
+ /**
31
+ * Resolve a stream endpoint against the browser origin.
32
+ *
33
+ * @param value - Absolute or same-origin relative endpoint.
34
+ * @param transport - Transport whose URL protocol should be used.
35
+ * @param baseUrl - Resolution base. Defaults to the current browser location.
36
+ * @returns An absolute transport URL.
37
+ */
38
+ export function resolveStreamUrl(value, transport, baseUrl = window.location.href) {
39
+ const resolved = new URL(value, baseUrl);
40
+ if (transport === "websocket") {
41
+ if (resolved.protocol === "http:") {
42
+ resolved.protocol = "ws:";
43
+ }
44
+ else if (resolved.protocol === "https:") {
45
+ resolved.protocol = "wss:";
46
+ }
47
+ }
48
+ else if (resolved.protocol === "ws:") {
49
+ resolved.protocol = "http:";
50
+ }
51
+ else if (resolved.protocol === "wss:") {
52
+ resolved.protocol = "https:";
53
+ }
54
+ return resolved.toString();
55
+ }
56
+ /**
57
+ * Create a reconnecting, transport-agnostic Litestar event stream.
58
+ *
59
+ * @param options - Stream transport, lifecycle, and frame-processing options.
60
+ * @returns A disposable stream that connects only when `connect()` is called.
61
+ */
62
+ export function createEventStream(options) {
63
+ const { transport = "websocket", sseEvents = DEFAULT_SSE_EVENTS, onEvent, onOpen, onClose, onHealthChange, onReconnect, onGap, shouldReconnect = (closeCode) => closeCode !== 1000, isHeartbeat = () => false, getEventKey = () => undefined, getSequence = () => undefined, baseDelayMs = DEFAULT_BASE_DELAY_MS, maxDelayMs = DEFAULT_MAX_DELAY_MS, dedupWindow = DEFAULT_DEDUP_WINDOW, parseFrame = defaultParseFrame, } = options;
64
+ let connection = null;
65
+ let attempt = 0;
66
+ let timer = null;
67
+ let disposed = false;
68
+ let lastHealthy = null;
69
+ let hasOpened = false;
70
+ const seenKeys = [];
71
+ const seenKeySet = new Set();
72
+ const sequenceByStream = new Map();
73
+ function emitHealth(healthy) {
74
+ if (lastHealthy === healthy) {
75
+ return;
76
+ }
77
+ lastHealthy = healthy;
78
+ onHealthChange?.(healthy);
79
+ }
80
+ function clearTimer() {
81
+ if (timer !== null) {
82
+ clearTimeout(timer);
83
+ timer = null;
84
+ }
85
+ }
86
+ function scheduleReconnect() {
87
+ clearTimer();
88
+ attempt += 1;
89
+ const ceiling = Math.min(baseDelayMs * 2 ** (attempt - 1), maxDelayMs);
90
+ const delay = Math.random() * ceiling;
91
+ timer = setTimeout(() => {
92
+ timer = null;
93
+ if (!disposed) {
94
+ open();
95
+ }
96
+ }, delay);
97
+ }
98
+ function handleOpen(url) {
99
+ attempt = 0;
100
+ clearTimer();
101
+ emitHealth(true);
102
+ onOpen?.(url);
103
+ if (hasOpened) {
104
+ onReconnect?.();
105
+ }
106
+ else {
107
+ hasOpened = true;
108
+ }
109
+ }
110
+ function handleMessage(event) {
111
+ const frame = parseFrame(String(event.data));
112
+ if (isHeartbeat(frame)) {
113
+ return;
114
+ }
115
+ const eventKey = getEventKey(frame);
116
+ if (eventKey !== undefined && dedupWindow > 0) {
117
+ if (seenKeySet.has(eventKey)) {
118
+ return;
119
+ }
120
+ seenKeys.push(eventKey);
121
+ seenKeySet.add(eventKey);
122
+ if (seenKeys.length > dedupWindow) {
123
+ const evicted = seenKeys.shift();
124
+ if (evicted !== undefined) {
125
+ seenKeySet.delete(evicted);
126
+ }
127
+ }
128
+ }
129
+ const sequence = getSequence(frame);
130
+ if (sequence !== undefined && Number.isFinite(sequence.value)) {
131
+ const last = sequenceByStream.get(sequence.stream);
132
+ if (last === undefined) {
133
+ sequenceByStream.set(sequence.stream, sequence.value);
134
+ }
135
+ else if (sequence.value > last) {
136
+ sequenceByStream.set(sequence.stream, sequence.value);
137
+ if (sequence.value > last + 1) {
138
+ onGap?.({
139
+ stream: sequence.stream,
140
+ from: last,
141
+ to: sequence.value,
142
+ missing: sequence.value - last - 1,
143
+ });
144
+ }
145
+ }
146
+ }
147
+ onEvent(frame);
148
+ }
149
+ function buildConnectionUrl() {
150
+ const value = options.buildUrl === undefined ? options.url : options.buildUrl();
151
+ const endpoint = typeof value === "function" ? value() : value;
152
+ return resolveStreamUrl(endpoint, transport);
153
+ }
154
+ function openWebSocket() {
155
+ const WebSocketCtor = options.WebSocketCtor ?? window.WebSocket;
156
+ const url = buildConnectionUrl();
157
+ const next = new WebSocketCtor(url);
158
+ connection = next;
159
+ next.addEventListener("open", () => {
160
+ handleOpen(url);
161
+ });
162
+ next.addEventListener("message", (event) => {
163
+ handleMessage(event);
164
+ });
165
+ next.addEventListener("close", (event) => {
166
+ if (connection !== next) {
167
+ return;
168
+ }
169
+ connection = null;
170
+ onClose?.();
171
+ emitHealth(false);
172
+ if (disposed || !shouldReconnect(event.code)) {
173
+ return;
174
+ }
175
+ scheduleReconnect();
176
+ });
177
+ next.addEventListener("error", () => {
178
+ emitHealth(false);
179
+ });
180
+ }
181
+ function openEventSource() {
182
+ const EventSourceCtor = options.EventSourceCtor ?? window.EventSource;
183
+ const url = buildConnectionUrl();
184
+ const next = new EventSourceCtor(url);
185
+ connection = next;
186
+ next.addEventListener("open", () => {
187
+ handleOpen(url);
188
+ });
189
+ for (const eventType of sseEvents) {
190
+ next.addEventListener(eventType, (event) => {
191
+ handleMessage(event);
192
+ });
193
+ }
194
+ next.addEventListener("error", () => {
195
+ if (connection !== next) {
196
+ return;
197
+ }
198
+ connection = null;
199
+ next.close();
200
+ onClose?.();
201
+ emitHealth(false);
202
+ if (disposed || !shouldReconnect(1006)) {
203
+ return;
204
+ }
205
+ scheduleReconnect();
206
+ });
207
+ }
208
+ function open() {
209
+ if (disposed || typeof window === "undefined") {
210
+ return;
211
+ }
212
+ if (transport === "sse") {
213
+ openEventSource();
214
+ return;
215
+ }
216
+ openWebSocket();
217
+ }
218
+ return {
219
+ connect() {
220
+ if (disposed || typeof window === "undefined") {
221
+ return;
222
+ }
223
+ attempt = 0;
224
+ clearTimer();
225
+ open();
226
+ },
227
+ dispose() {
228
+ disposed = true;
229
+ clearTimer();
230
+ const current = connection;
231
+ connection = null;
232
+ if (current !== null) {
233
+ onClose?.();
234
+ emitHealth(false);
235
+ current.close();
236
+ }
237
+ },
238
+ get healthy() {
239
+ return lastHealthy ?? false;
240
+ },
241
+ };
242
+ }
@@ -1,114 +1,12 @@
1
1
  import { type Config as FullReloadConfig } from "vite-plugin-full-reload";
2
+ import { type TypesConfigShape } from "./shared/typegen-plugin.js";
2
3
  /**
3
4
  * Configuration for TypeScript type generation.
4
5
  *
5
- * Type generation works as follows:
6
- * 1. Python's Litestar exports openapi.json and routes.json on startup (and reload)
7
- * 2. The Vite plugin watches these files for changes
8
- * 3. When they change, it runs @hey-api/openapi-ts to generate TypeScript types
9
- * 4. HMR event is sent to notify the client
6
+ * Alias of the shared {@link TypesConfigShape}, which every framework integration also
7
+ * re-exports, so the `types` option is identical everywhere. Retained as a named export.
10
8
  */
11
- export interface TypesConfig {
12
- /**
13
- * Enable type generation.
14
- *
15
- * @default false
16
- */
17
- enabled?: boolean;
18
- /**
19
- * Path to output generated TypeScript types.
20
- *
21
- * @default 'src/generated'
22
- */
23
- output?: string;
24
- /**
25
- * Path where the OpenAPI schema is exported by Litestar.
26
- * The Vite plugin watches this file and runs @hey-api/openapi-ts when it changes.
27
- *
28
- * @default 'src/generated/openapi.json'
29
- */
30
- openapiPath?: string;
31
- /**
32
- * Path where route metadata is exported by Litestar.
33
- * The Vite plugin watches this file for route helper generation.
34
- *
35
- * @default 'src/generated/routes.json'
36
- */
37
- routesPath?: string;
38
- /**
39
- * Optional path for the generated schemas.ts helper file.
40
- * Defaults to `${output}/schemas.ts` when not set.
41
- */
42
- schemasTsPath?: string;
43
- /**
44
- * Path where Inertia page props metadata is exported by Litestar.
45
- * The Vite plugin watches this file for page props type generation.
46
- *
47
- * @default 'src/generated/inertia-pages.json'
48
- */
49
- pagePropsPath?: string;
50
- /**
51
- * Generate Zod schemas in addition to TypeScript types.
52
- *
53
- * @default false
54
- */
55
- generateZod?: boolean;
56
- /**
57
- * Generate a typed SDK client in addition to types.
58
- *
59
- * @default true
60
- */
61
- generateSdk?: boolean;
62
- /**
63
- * Generate typed routes.ts from routes.json metadata.
64
- *
65
- * Mirrors Python TypeGenConfig.generate_routes.
66
- *
67
- * @default true
68
- */
69
- generateRoutes?: boolean;
70
- /**
71
- * Generate Inertia page props types from inertia-pages.json metadata.
72
- *
73
- * Mirrors Python TypeGenConfig.generate_page_props.
74
- *
75
- * @default true
76
- */
77
- generatePageProps?: boolean;
78
- /**
79
- * Generate schemas.ts with ergonomic form/response type helpers.
80
- *
81
- * Creates helper types like FormInput<'api:login'> and FormResponse<'api:login', 201>
82
- * that wrap hey-api generated types with cleaner DX.
83
- *
84
- * @default true
85
- */
86
- generateSchemas?: boolean;
87
- /**
88
- * Register route() function globally on window object.
89
- *
90
- * When true, the generated routes.ts will include code that registers
91
- * the type-safe route() function on `window.route`, similar to Laravel's
92
- * Ziggy library. This allows using route() without imports.
93
- *
94
- * @default false
95
- */
96
- globalRoute?: boolean;
97
- /**
98
- * Fail Vite when type generation fails.
99
- *
100
- * Defaults to true during `vite build` and false during `vite serve`.
101
- */
102
- failOnError?: boolean;
103
- /**
104
- * Debounce time in milliseconds for type regeneration.
105
- * Prevents regeneration from running too frequently when
106
- * multiple files are written in quick succession.
107
- *
108
- * @default 300
109
- */
110
- debounce?: number;
111
- }
9
+ export type TypesConfig = TypesConfigShape;
112
10
  export interface PluginConfig {
113
11
  /**
114
12
  * The path or paths of the entry points to compile.
package/dist/js/index.js CHANGED
@@ -7,6 +7,7 @@ import fullReload from "vite-plugin-full-reload";
7
7
  import { checkBackendAvailability, loadLitestarMeta } from "./litestar-meta.js";
8
8
  import { readBridgeConfig } from "./shared/bridge-schema.js";
9
9
  import { createLogger } from "./shared/logger.js";
10
+ import { installManagedShutdown } from "./shared/managed-shutdown.js";
10
11
  import { resolveLitestarPort } from "./shared/network.js";
11
12
  import { resolveDefaultSdkClientPlugin } from "./shared/typegen-core.js";
12
13
  import { createLitestarTypeGenPlugin, resolveTypesConfig } from "./shared/typegen-plugin.js";
@@ -320,6 +321,9 @@ function resolveLitestarPlugin(pluginConfig) {
320
321
  }
321
322
  });
322
323
  if (!exitHandlersBound) {
324
+ installManagedShutdown(server, () => {
325
+ shuttingDown = true;
326
+ });
323
327
  const clean = () => {
324
328
  if (pluginConfig.hotFile && fs.existsSync(pluginConfig.hotFile)) {
325
329
  fs.rmSync(pluginConfig.hotFile);