litestar-vite-plugin 0.27.0 → 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/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/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/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/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
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
|
|
24
|
+
from litestar_vite import VitePlugin
|
|
27
25
|
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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";
|
package/dist/js/helpers/index.js
CHANGED
|
@@ -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 {};
|
|
@@ -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
|
+
}
|