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 +37 -18
- package/dist/js/astro.js +4 -0
- package/dist/js/dev-server-index.html +2 -2
- package/dist/js/helpers/channels.d.ts +31 -0
- package/dist/js/helpers/channels.js +37 -0
- package/dist/js/helpers/index.d.ts +4 -0
- package/dist/js/helpers/index.js +8 -0
- package/dist/js/helpers/queues.d.ts +61 -0
- package/dist/js/helpers/queues.js +117 -0
- package/dist/js/helpers/stream-element.d.ts +79 -0
- package/dist/js/helpers/stream-element.js +229 -0
- package/dist/js/helpers/stream.d.ts +76 -0
- package/dist/js/helpers/stream.js +242 -0
- package/dist/js/index.js +4 -0
- package/dist/js/install-hint.d.ts +8 -1
- package/dist/js/install-hint.js +20 -8
- package/dist/js/nuxt.js +10 -16
- package/dist/js/react/index.d.ts +28 -0
- package/dist/js/react/index.js +54 -0
- package/dist/js/shared/bridge-schema.d.ts +3 -3
- package/dist/js/shared/constants.d.ts +5 -2
- package/dist/js/shared/constants.js +5 -1
- package/dist/js/shared/managed-shutdown.d.ts +8 -0
- package/dist/js/shared/managed-shutdown.js +26 -0
- package/dist/js/shared/network.d.ts +4 -3
- package/dist/js/shared/typegen-core.js +4 -3
- package/dist/js/svelte/index.d.ts +27 -0
- package/dist/js/svelte/index.js +51 -0
- package/dist/js/sveltekit.js +2 -0
- package/dist/js/vue/index.d.ts +29 -0
- package/dist/js/vue/index.js +57 -0
- 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
|
+
}
|
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);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Priority: LITESTAR_VITE_RUNTIME env > .litestar.json executor > lockfile detection > 'node'
|
|
4
4
|
*/
|
|
5
5
|
export declare function detectExecutor(): string;
|
|
6
|
-
export declare function resolveInstallHint(pkg?: string): string;
|
|
6
|
+
export declare function resolveInstallHint(pkg?: string | readonly string[]): string;
|
|
7
7
|
/**
|
|
8
8
|
* Resolves the package executor command based on runtime.
|
|
9
9
|
* Priority: explicit executor > .litestar.json > LITESTAR_VITE_RUNTIME env > lockfile detection > 'npx'
|
|
@@ -26,6 +26,13 @@ export interface PackageExecutorArgvOptions {
|
|
|
26
26
|
* separate from the command binary, e.g. npm exec --package.
|
|
27
27
|
*/
|
|
28
28
|
packageSpec?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Additional package specs required in the temporary execution environment.
|
|
31
|
+
*
|
|
32
|
+
* npm, pnpm, and Yarn support this. Bun and Deno do not currently expose a
|
|
33
|
+
* reliable equivalent, so their resolver returns no fallback.
|
|
34
|
+
*/
|
|
35
|
+
additionalPackageSpecs?: readonly string[];
|
|
29
36
|
/** Binary command exposed by packageSpec. */
|
|
30
37
|
binName?: string;
|
|
31
38
|
}
|
package/dist/js/install-hint.js
CHANGED
|
@@ -32,21 +32,23 @@ function detectExecutor() {
|
|
|
32
32
|
}
|
|
33
33
|
function resolveInstallHint(pkg = "@hey-api/openapi-ts") {
|
|
34
34
|
const runtime = detectExecutor();
|
|
35
|
+
const packageSpecs = typeof pkg === "string" ? [pkg] : pkg;
|
|
36
|
+
const packageArgs = packageSpecs.join(" ");
|
|
35
37
|
switch (runtime) {
|
|
36
38
|
case "bun":
|
|
37
|
-
return `bun add -d ${
|
|
39
|
+
return `bun add -d ${packageArgs}`;
|
|
38
40
|
case "deno":
|
|
39
|
-
return `deno add -d npm:${
|
|
41
|
+
return `deno add -d ${packageSpecs.map((packageSpec) => `npm:${packageSpec}`).join(" ")}`;
|
|
40
42
|
case "pnpm":
|
|
41
|
-
return `pnpm add -D ${
|
|
43
|
+
return `pnpm add -D ${packageArgs}`;
|
|
42
44
|
case "yarn":
|
|
43
|
-
return `yarn add -D ${
|
|
45
|
+
return `yarn add -D ${packageArgs}`;
|
|
44
46
|
}
|
|
45
47
|
const envInstall = process.env.LITESTAR_VITE_INSTALL_CMD?.trim();
|
|
46
48
|
if (envInstall) {
|
|
47
|
-
return `${envInstall} -D ${
|
|
49
|
+
return `${envInstall} -D ${packageArgs}`;
|
|
48
50
|
}
|
|
49
|
-
return `npm install -D ${
|
|
51
|
+
return `npm install -D ${packageArgs}`;
|
|
50
52
|
}
|
|
51
53
|
function resolvePackageExecutor(pkg, executor) {
|
|
52
54
|
const runtime = executor || detectExecutor();
|
|
@@ -79,19 +81,29 @@ function resolveDenoPackageSpec(packageSpec, binName) {
|
|
|
79
81
|
}
|
|
80
82
|
function resolvePackageExecutorArgv(args, executor, options = {}) {
|
|
81
83
|
const runtime = executor || detectExecutor();
|
|
82
|
-
const { packageSpec, binName } = options;
|
|
84
|
+
const { packageSpec, additionalPackageSpecs = [], binName } = options;
|
|
85
|
+
const packageSpecs = packageSpec ? [packageSpec, ...additionalPackageSpecs] : [];
|
|
86
|
+
const requiresMultiplePackages = packageSpecs.length > 1;
|
|
83
87
|
switch (runtime) {
|
|
84
88
|
case "bun":
|
|
89
|
+
if (requiresMultiplePackages) return [];
|
|
85
90
|
return ["bunx", ...packageSpec ? [packageSpec, ...args] : args];
|
|
86
91
|
case "deno":
|
|
92
|
+
if (requiresMultiplePackages) return [];
|
|
87
93
|
return ["deno", "run", "-A", ...packageSpec ? [`npm:${resolveDenoPackageSpec(packageSpec, binName)}`, ...args] : args];
|
|
88
94
|
case "pnpm":
|
|
95
|
+
if (requiresMultiplePackages && binName) {
|
|
96
|
+
return ["pnpm", "dlx", ...packageSpecs.map((spec) => `--package=${spec}`), binName, ...args];
|
|
97
|
+
}
|
|
89
98
|
return ["pnpm", "dlx", ...packageSpec ? [packageSpec, ...args] : args];
|
|
90
99
|
case "yarn":
|
|
100
|
+
if (requiresMultiplePackages && binName) {
|
|
101
|
+
return ["yarn", "dlx", ...packageSpecs.flatMap((spec) => ["-p", spec]), binName, ...args];
|
|
102
|
+
}
|
|
91
103
|
return ["yarn", "dlx", ...packageSpec ? [packageSpec, ...args] : args];
|
|
92
104
|
default:
|
|
93
105
|
if (packageSpec && binName) {
|
|
94
|
-
return ["npm", "exec", "--yes", "--package",
|
|
106
|
+
return ["npm", "exec", "--yes", ...packageSpecs.flatMap((spec) => ["--package", spec]), "--", binName, ...args];
|
|
95
107
|
}
|
|
96
108
|
return ["npx", ...args];
|
|
97
109
|
}
|