@mauroandre/velojs 0.0.18 → 0.0.19

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
@@ -501,6 +501,136 @@ export const action_login = async ({ body }: { body: LoginBody }) => {
501
501
 
502
502
  **Error handling**: Actions do NOT throw on server errors. They resolve with `{ error: "message" }`. Always check `result.error` explicitly.
503
503
 
504
+ ### Shared actions in Layouts
505
+
506
+ Actions are tied to the module where they're declared — the route is always `/_action/{moduleId}/{actionName}`. Declare an action in a Layout and import it from any child module to share it across multiple pages.
507
+
508
+ ```typescript
509
+ // app/admin/Layout.tsx — action declared once in the layout
510
+ import type { ActionArgs } from "@mauroandre/velojs";
511
+
512
+ export const action_logout = async ({ c }: ActionArgs) => {
513
+ const { deleteCookie } = await import("@mauroandre/velojs/cookie");
514
+ deleteCookie(c!, "session");
515
+ return { ok: true };
516
+ };
517
+
518
+ export const Component = ({ children }) => (/* layout with children */);
519
+ ```
520
+
521
+ ```typescript
522
+ // app/admin/Dashboard.tsx — imports and uses the action from Layout
523
+ import { action_logout } from "./Layout.js";
524
+
525
+ export const Component = () => (
526
+ <button onClick={async () => {
527
+ await action_logout({});
528
+ window.location.href = "/login";
529
+ }}>Logout</button>
530
+ );
531
+ ```
532
+
533
+ The client-side transform rewrites the import to fetch `/_action/admin/Layout/logout`, so the action always points to the correct URL regardless of where it's imported. Middlewares on the Layout apply automatically.
534
+
535
+ ---
536
+
537
+ ## Event Streams
538
+
539
+ Push real-time updates from server to client via Server-Sent Events (SSE). Useful for live progress, notifications, metrics, log streaming, AI tokens, and any server → client push scenario.
540
+
541
+ ### `stream_*` convention
542
+
543
+ Declare a stream alongside `loader` / `action_*` in any page or layout module. The framework registers an SSE route at `/_event/{moduleId}/{name}` automatically:
544
+
545
+ ```typescript
546
+ // app/admin/Deploy.tsx
547
+ import { createEventStream } from "@mauroandre/velojs";
548
+ import { useEventStream, useParams } from "@mauroandre/velojs/hooks";
549
+
550
+ type DeployState = { step: string; status: "running" | "success" | "error" };
551
+
552
+ const activeDeploys = new Map<string, DeployState>();
553
+
554
+ export const stream_progress = createEventStream<DeployState>({
555
+ channel: (c) => c.req.query("channel") ?? "",
556
+ snapshot: (channel) => activeDeploys.get(channel ?? ""),
557
+ closeOn: (s) => s.status === "success" || s.status === "error",
558
+ });
559
+
560
+ export const Component = () => {
561
+ const params = useParams<{ id: string }>();
562
+ const { data, snapshot, closed } = useEventStream(stream_progress, {
563
+ channel: params.id,
564
+ });
565
+
566
+ const state = data.value ?? snapshot.value;
567
+ return <div>{state?.step ?? "waiting..."}</div>;
568
+ };
569
+ ```
570
+
571
+ Emit from any service that imports the stream:
572
+
573
+ ```typescript
574
+ import { stream_progress } from "./Deploy.js";
575
+
576
+ stream_progress.emit("app-123", { step: "building", status: "running" });
577
+ ```
578
+
579
+ ### Standalone streams
580
+
581
+ For cross-cutting streams (global metrics, notifications) consumed by many pages, use an explicit `path`:
582
+
583
+ ```typescript
584
+ // app/streams/metrics.ts
585
+ export const metricsStream = createEventStream<Metric[]>({
586
+ path: "/api/metrics/containers",
587
+ });
588
+
589
+ setInterval(() => metricsStream.emit(collectMetrics()), 2000);
590
+ ```
591
+
592
+ ### Configuration
593
+
594
+ | Option | Type | Description |
595
+ |--------|------|-------------|
596
+ | `path` | `string` | (Standalone) Explicit URL path |
597
+ | `channel` | `(c: Context) => string` | Extract channel ID from request — subscribers only get matching events |
598
+ | `snapshot` | `(channel) => TSnapshot \| undefined` | Returns current state on connect (and reconnect) |
599
+ | `closeOn` | `(event: TEvent) => boolean` | Closes the SSE connection when matching event is sent |
600
+ | `heartbeatMs` | `number \| false` | Keep-alive heartbeat interval, default `20000` (20s). `false` to disable |
601
+ | `middlewares` | `MiddlewareHandler[]` | (Standalone) Hono middlewares applied to the SSE route |
602
+
603
+ ### Middleware inheritance
604
+
605
+ Streams declared with `stream_*` automatically inherit middlewares from parent route nodes — same as pages and actions. A stream under an `AdminLayout` with `authMiddleware` is protected without extra wiring.
606
+
607
+ For standalone streams, pass middlewares explicitly:
608
+
609
+ ```typescript
610
+ export const metricsStream = createEventStream<Metric[]>({
611
+ path: "/api/metrics",
612
+ middlewares: [authMiddleware],
613
+ });
614
+ ```
615
+
616
+ ### `useEventStream` hook
617
+
618
+ ```typescript
619
+ const { data, snapshot, closed, error } = useEventStream(stream, {
620
+ channel: "deploy-123", // optional
621
+ enabled: true, // optional
622
+ });
623
+ ```
624
+
625
+ | Signal | Description |
626
+ |--------|-------------|
627
+ | `data` | Latest event received from the server |
628
+ | `snapshot` | Initial state on connect (if `snapshot` is configured) |
629
+ | `closed` | `true` when server closed the stream (`closeOn` matched) |
630
+ | `error` | Parse or connection error, if any |
631
+
632
+ The hook handles `EventSource` lifecycle automatically: opens on mount, closes on unmount, re-opens when `channel` changes.
633
+
504
634
  ---
505
635
 
506
636
  ## Hooks
@@ -511,6 +641,7 @@ All hooks work in both SSR and client (via AsyncLocalStorage on server, wouter/D
511
641
  |------|-------------|
512
642
  | `useLoader<T>(deps?)` | Loader data with SSR + SPA support. Returns `{ data, loading, refetch }` |
513
643
  | `Loader<T>()` | Module-level SSR-only loader. Returns `{ data, loading }` |
644
+ | `useEventStream<T, S>(stream, opts?)` | Subscribe to a server-sent event stream. Returns `{ data, snapshot, closed, error }` |
514
645
  | `useParams<T>()` | Route parameters (e.g., `:id`) |
515
646
  | `useQuery<T>()` | Query string parameters |
516
647
  | `useNavigate()` | Programmatic navigation. Returns `navigate(path)` function |
@@ -701,7 +832,25 @@ export default [
701
832
 
702
833
  ### Inheritance
703
834
 
704
- Middlewares accumulate from parent to child. In the example above, `/master/workers` runs `authMiddleware` first, then `masterMiddleware`. This applies to both page loads (loaders) and action calls.
835
+ Middlewares accumulate from parent to child and apply to **every nested route**, including:
836
+
837
+ - **Page routes** (GET) — when loading a page
838
+ - **Action routes** (POST `/_action/...`) — when calling a server action
839
+ - **Data fetches** (GET with `?_data=1`) — during SPA navigation
840
+
841
+ In the example above, `/master/workers` runs `authMiddleware` first, then `masterMiddleware`. The same applies when calling `action_*` functions from any page under `MasterLayout` — both middlewares run before the action executes.
842
+
843
+ ```typescript
844
+ // This action, defined in app/master/Workers.tsx, is registered as
845
+ // POST /_action/master/Workers/delete
846
+ // with authMiddleware + masterMiddleware applied.
847
+ export const action_delete = async ({ body, c }: ActionArgs<{ id: string }>) => {
848
+ const user = c!.get("user"); // set by authMiddleware
849
+ // ...
850
+ };
851
+ ```
852
+
853
+ A middleware on a Layout guards everything underneath it — pages, loaders, and actions — with no extra wiring.
705
854
 
706
855
  ### Accessing middleware data in loaders and actions
707
856
 
@@ -927,10 +1076,11 @@ Hooks (`useParams`, `useQuery`, `usePathname`, `Loader`, `useLoader`) access thi
927
1076
 
928
1077
  | Import | Contents |
929
1078
  |--------|----------|
930
- | `@mauroandre/velojs` | Types (`AppRoutes`, `ActionArgs`, `LoaderArgs`, `Metadata`), `Scripts`, `Link`, `defineConfig` |
1079
+ | `@mauroandre/velojs` | Types (`AppRoutes`, `ActionArgs`, `LoaderArgs`, `Metadata`, `EventStream`, `EventStreamConfig`), `Scripts`, `Link`, `createEventStream`, `defineConfig` |
931
1080
  | `@mauroandre/velojs/server` | `startServer`, `createApp`, `addRoutes`, `onServer`, `serverDataStorage` |
932
1081
  | `@mauroandre/velojs/client` | `startClient` |
933
- | `@mauroandre/velojs/hooks` | `Loader`, `useLoader`, `useParams`, `useQuery`, `useNavigate`, `usePathname`, `touch` |
1082
+ | `@mauroandre/velojs/hooks` | `Loader`, `useLoader`, `useEventStream`, `useParams`, `useQuery`, `useNavigate`, `usePathname`, `touch` |
1083
+ | `@mauroandre/velojs/events` | `createEventStream`, `EventStream`, `EventStreamConfig` (also re-exported from root) |
934
1084
  | `@mauroandre/velojs/cookie` | `getCookie`, `setCookie`, `deleteCookie`, `getSignedCookie`, `setSignedCookie` |
935
1085
  | `@mauroandre/velojs/factory` | `createMiddleware`, `createFactory` |
936
1086
  | `@mauroandre/velojs/vite` | `veloPlugin` |
@@ -0,0 +1,91 @@
1
+ /**
2
+ * VeloJS Event Streams
3
+ * Server-Sent Events (SSE) with snapshot, channels, and auto-close.
4
+ *
5
+ * Two ways to use:
6
+ *
7
+ * 1. Standalone (cross-cutting streams like global metrics):
8
+ * ```typescript
9
+ * export const metricsStream = createEventStream<Metric[]>({
10
+ * path: "/api/metrics",
11
+ * });
12
+ * metricsStream.emit(currentMetrics);
13
+ * ```
14
+ *
15
+ * 2. Convention `stream_*` (per-page streams, route registered automatically):
16
+ * ```typescript
17
+ * // app/admin/Deploy.tsx
18
+ * export const stream_progress = createEventStream<DeployState>({
19
+ * channel: (c) => c.req.param("id"),
20
+ * snapshot: (id) => activeDeploys.get(id),
21
+ * closeOn: (s) => s.status === "success" || s.status === "error",
22
+ * });
23
+ * ```
24
+ */
25
+ import type { Context, Hono, MiddlewareHandler } from "hono";
26
+ export interface EventStreamConfig<TEvent, TSnapshot = TEvent> {
27
+ /**
28
+ * Explicit URL path for the SSE endpoint (standalone usage).
29
+ * When omitted, the path is derived from the module ID at build time
30
+ * (convention `stream_*`).
31
+ */
32
+ path?: string;
33
+ /**
34
+ * Channel selector — extracts a channel ID from the request.
35
+ * Subscribers only receive events emitted to their channel.
36
+ * When omitted, the stream is a broadcast (all subscribers receive all events).
37
+ */
38
+ channel?: (c: Context) => string;
39
+ /**
40
+ * Returns the current state of a channel for snapshot-on-connect.
41
+ * Sent before any new events to bring the client up to date.
42
+ * Must remain available even after the stream "closes" — clients reconnecting
43
+ * after a terminal event still need to see the final state.
44
+ */
45
+ snapshot?: (channel: string | undefined) => TSnapshot | undefined | Promise<TSnapshot | undefined>;
46
+ /**
47
+ * Returns true when an event marks the end of the stream for that channel.
48
+ * The server closes the SSE connection after sending such an event.
49
+ */
50
+ closeOn?: (event: TEvent) => boolean;
51
+ /**
52
+ * Heartbeat interval in milliseconds. Sends `:heartbeat\n\n` to keep
53
+ * proxies (Cloudflare, Nginx, ALB) from closing idle connections.
54
+ * Default: 20000 (20s). Set to `false` to disable.
55
+ */
56
+ heartbeatMs?: number | false;
57
+ /**
58
+ * Middlewares to apply to the SSE route (standalone streams only).
59
+ * For `stream_*` convention streams, middlewares are inherited from
60
+ * parent route nodes automatically.
61
+ */
62
+ middlewares?: MiddlewareHandler[];
63
+ }
64
+ export interface EventStream<TEvent, TSnapshot = TEvent> {
65
+ /** Broadcast emit (no channel) */
66
+ emit(event: TEvent): void;
67
+ /** Channel-targeted emit */
68
+ emit(channel: string, event: TEvent): void;
69
+ /** Internal: marker for runtime detection */
70
+ readonly __isVeloEventStream: true;
71
+ /** Internal: configuration */
72
+ readonly __config: EventStreamConfig<TEvent, TSnapshot>;
73
+ /** Internal: registered listeners by channel ("" for broadcast) */
74
+ readonly __listeners: Map<string, Set<(event: TEvent) => void>>;
75
+ /** Internal: assigned URL path (set by standalone or by stream_* discovery) */
76
+ __path: string | undefined;
77
+ }
78
+ /**
79
+ * Returns and clears the queue of pending standalone streams.
80
+ * Called by createApp to register their routes.
81
+ */
82
+ export declare function flushPendingStreamRoutes(app: Hono): void;
83
+ /**
84
+ * Creates a typed event stream with optional channel, snapshot, and close-on-event.
85
+ */
86
+ export declare function createEventStream<TEvent, TSnapshot = TEvent>(config?: EventStreamConfig<TEvent, TSnapshot>): EventStream<TEvent, TSnapshot>;
87
+ /**
88
+ * Registers a GET SSE route for the given stream at the given path.
89
+ * Used both by standalone streams and by stream_* discovery.
90
+ */
91
+ export declare function registerStreamHandler<TEvent, TSnapshot>(app: Hono, path: string, stream: EventStream<TEvent, TSnapshot>, middlewares?: Array<(c: Context, next: () => Promise<void>) => any>): void;
package/dist/events.js ADDED
@@ -0,0 +1,62 @@
1
+ const A = [];
2
+ function w(t) {
3
+ for (const s of A) s(t);
4
+ A.length = 0;
5
+ }
6
+ function N(t = {}) {
7
+ const s = /* @__PURE__ */ new Map();
8
+ function e(a, f) {
9
+ let l, n;
10
+ arguments.length === 1 ? (l = "", n = a) : (l = a, n = f);
11
+ const c = s.get(l);
12
+ if (c)
13
+ for (const h of c) h(n);
14
+ }
15
+ const r = {
16
+ emit: e,
17
+ __isVeloEventStream: !0,
18
+ __config: t,
19
+ __listeners: s,
20
+ __path: t.path
21
+ };
22
+ return t.path && A.push((a) => {
23
+ p(a, t.path, r, t.middlewares ?? []);
24
+ }), r;
25
+ }
26
+ function p(t, s, e, r = []) {
27
+ const a = async (f) => {
28
+ const { streamSSE: l } = await import("hono/streaming"), n = e.__config.channel ? e.__config.channel(f) : "", c = e.__config.heartbeatMs, h = c === !1 ? 0 : c ?? 2e4;
29
+ return l(f, async (_) => {
30
+ if (e.__config.snapshot) {
31
+ const o = await e.__config.snapshot(
32
+ n === "" ? void 0 : n
33
+ );
34
+ o !== void 0 && await _.writeSSE({
35
+ event: "snapshot",
36
+ data: JSON.stringify(o)
37
+ });
38
+ }
39
+ let E;
40
+ const T = new Promise((o) => {
41
+ E = o;
42
+ });
43
+ let S = !1, u = null, i;
44
+ const d = () => {
45
+ S || (S = !0, u && clearInterval(u), i?.delete(g), i?.size === 0 && e.__listeners.delete(n), E());
46
+ }, g = (o) => {
47
+ S || (_.writeSSE({ data: JSON.stringify(o) }).catch(() => {
48
+ }), e.__config.closeOn?.(o) && (_.close(), d()));
49
+ };
50
+ i = e.__listeners.get(n), i || (i = /* @__PURE__ */ new Set(), e.__listeners.set(n, i)), i.add(g), u = h > 0 ? setInterval(() => {
51
+ S || _.writeSSE({ event: "heartbeat", data: "" }).catch(() => {
52
+ });
53
+ }, h) : null, _.onAbort(d), await T;
54
+ });
55
+ };
56
+ r.length > 0 ? t.on(["GET"], [s], ...r, a) : t.on(["GET"], [s], a);
57
+ }
58
+ export {
59
+ N as createEventStream,
60
+ w as flushPendingStreamRoutes,
61
+ p as registerStreamHandler
62
+ };
package/dist/hooks.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { type Signal } from "@preact/signals";
2
+ import type { EventStream } from "./events.js";
2
3
  export declare const __veloUpdatePending: Signal<boolean>;
3
4
  /**
4
5
  * Força o signal a notificar mudanças após mutação de propriedades aninhadas
@@ -118,3 +119,33 @@ export declare function useQuery<T extends Record<string, string> = Record<strin
118
119
  * ```
119
120
  */
120
121
  export declare function usePathname(): string;
122
+ /**
123
+ * Subscribes to a server event stream (SSE) created with createEventStream.
124
+ *
125
+ * Returns:
126
+ * - `data`: Signal with the latest event received (null until first event)
127
+ * - `snapshot`: Signal with the initial snapshot if the server provided one
128
+ * - `closed`: Signal that becomes true when the stream is closed by the server
129
+ * - `error`: Signal with the connection error, if any
130
+ *
131
+ * Usage:
132
+ * ```tsx
133
+ * // Per-page stream (stream_* convention)
134
+ * const { data, snapshot } = useEventStream(stream_progress, { channel: deployId });
135
+ *
136
+ * // Standalone stream (no channel)
137
+ * const { data } = useEventStream(metricsStream);
138
+ * ```
139
+ *
140
+ * The connection closes automatically when the component unmounts or when
141
+ * the channel changes (re-opens with new channel).
142
+ */
143
+ export declare function useEventStream<TEvent, TSnapshot = TEvent>(stream: EventStream<TEvent, TSnapshot>, options?: {
144
+ channel?: string;
145
+ enabled?: boolean;
146
+ }): {
147
+ data: Signal<TEvent | null>;
148
+ snapshot: Signal<TSnapshot | null>;
149
+ closed: Signal<boolean>;
150
+ error: Signal<Error | null>;
151
+ };
package/dist/hooks.js CHANGED
@@ -1,61 +1,61 @@
1
- import { signal as _, useSignal as d } from "@preact/signals";
2
- import { useRef as g, useEffect as h } from "preact/hooks";
3
- import { useLocation as v, useParams as D } from "wouter-preact";
4
- const p = _(!1);
5
- function m(e) {
1
+ import { signal as v, useSignal as c } from "@preact/signals";
2
+ import { useEffect as h, useRef as p } from "preact/hooks";
3
+ import { useLocation as w, useParams as g } from "wouter-preact";
4
+ const S = v(!1);
5
+ function L(e) {
6
6
  e.value !== null && typeof e.value == "object" && (e.value = { ...e.value });
7
7
  }
8
- function P(e) {
9
- const a = _(!1);
8
+ function m(e) {
9
+ const o = v(!1);
10
10
  if (typeof window > "u")
11
11
  return { data: {
12
12
  get value() {
13
13
  return e ? globalThis.__veloServerData?.getStore?.()?.[e] ?? null : null;
14
14
  }
15
- }, loading: a };
15
+ }, loading: o };
16
16
  const t = window.__PAGE_DATA__, n = e && t?.[e] ? t[e] : null;
17
- return { data: _(n), loading: a };
17
+ return { data: v(n), loading: o };
18
18
  }
19
- function T(e, a) {
19
+ function A(e, o) {
20
20
  let t, n;
21
- Array.isArray(e) ? n = e : (t = e, n = a);
21
+ Array.isArray(e) ? n = e : (t = e, n = o);
22
22
  let r = null;
23
23
  if (typeof window > "u" && t) {
24
- const c = globalThis.__veloServerData?.getStore?.();
25
- c?.[t] && (r = c[t]);
24
+ const l = globalThis.__veloServerData?.getStore?.();
25
+ l?.[t] && (r = l[t]);
26
26
  }
27
27
  if (typeof window < "u" && t) {
28
- const o = window.__PAGE_DATA__;
29
- o?.[t] && (r = o[t], delete o[t]);
28
+ const a = window.__PAGE_DATA__;
29
+ a?.[t] && (r = a[t], delete a[t]);
30
30
  }
31
- const u = d(r), i = d(!1), l = () => {
31
+ const u = c(r), s = c(!1), _ = () => {
32
32
  if (typeof window > "u" || !t) return;
33
- const o = window.location.pathname;
34
- i.value = !0;
35
- const w = typeof __VELO_STATIC__ < "u" && __VELO_STATIC__ ? `${o === "/" ? "" : o}/index.json` : (() => {
36
- const s = new URLSearchParams(window.location.search);
37
- return s.set("_data", "1"), `${o}?${s.toString()}`;
33
+ const a = window.location.pathname;
34
+ s.value = !0;
35
+ const f = typeof __VELO_STATIC__ < "u" && __VELO_STATIC__ ? `${a === "/" ? "" : a}/index.json` : (() => {
36
+ const i = new URLSearchParams(window.location.search);
37
+ return i.set("_data", "1"), `${a}?${i.toString()}`;
38
38
  })();
39
- fetch(w, { cache: "no-cache" }).then((s) => s.json()).then((s) => {
40
- typeof __VELO_BUILD_HASH__ < "u" && s.__buildHash && s.__buildHash !== __VELO_BUILD_HASH__ && (p.value = !0), u.value = s[t], i.value = !1;
39
+ fetch(f, { cache: "no-cache" }).then((i) => i.json()).then((i) => {
40
+ typeof __VELO_BUILD_HASH__ < "u" && i.__buildHash && i.__buildHash !== __VELO_BUILD_HASH__ && (S.value = !0), u.value = i[t], s.value = !1;
41
41
  }).catch(() => {
42
- i.value = !1;
42
+ s.value = !1;
43
43
  });
44
- }, f = g(r !== null);
44
+ }, d = p(r !== null);
45
45
  return h(() => {
46
- if (f.current) {
47
- f.current = !1;
46
+ if (d.current) {
47
+ d.current = !1;
48
48
  return;
49
49
  }
50
- l();
51
- }, n ?? []), { data: u, loading: i, refetch: l };
50
+ _();
51
+ }, n ?? []), { data: u, loading: s, refetch: _ };
52
52
  }
53
- function L() {
54
- const [, e] = v();
53
+ function P() {
54
+ const [, e] = w();
55
55
  return e;
56
56
  }
57
- function E() {
58
- return typeof window > "u" ? globalThis.__veloServerData?.getStore?.()?.__params ?? {} : D();
57
+ function T() {
58
+ return typeof window > "u" ? globalThis.__veloServerData?.getStore?.()?.__params ?? {} : g();
59
59
  }
60
60
  function b() {
61
61
  if (typeof window > "u")
@@ -63,21 +63,52 @@ function b() {
63
63
  const e = window.__PAGE_DATA__;
64
64
  if (e?.__query)
65
65
  return e.__query;
66
- const a = new URLSearchParams(window.location.search), t = {};
67
- return a.forEach((n, r) => {
66
+ const o = new URLSearchParams(window.location.search), t = {};
67
+ return o.forEach((n, r) => {
68
68
  t[r] = n;
69
69
  }), t;
70
70
  }
71
71
  function H() {
72
- return typeof window > "u" ? globalThis.__veloServerData?.getStore?.()?.__pathname ?? "/" : (v(), window.location.pathname);
72
+ return typeof window > "u" ? globalThis.__veloServerData?.getStore?.()?.__pathname ?? "/" : (w(), window.location.pathname);
73
+ }
74
+ function U(e, o = {}) {
75
+ const t = c(null), n = c(null), r = c(!1), u = c(null), { channel: s, enabled: _ = !0 } = o;
76
+ return h(() => {
77
+ if (typeof window > "u" || !_) return;
78
+ if (!e.__path) {
79
+ console.warn("[velojs] useEventStream: stream has no __path");
80
+ return;
81
+ }
82
+ t.value = null, n.value = null, r.value = !1, u.value = null;
83
+ const d = s ? `${e.__path}?channel=${encodeURIComponent(s)}` : e.__path, a = new EventSource(d);
84
+ return a.addEventListener("snapshot", (l) => {
85
+ try {
86
+ n.value = JSON.parse(l.data);
87
+ } catch (f) {
88
+ u.value = f;
89
+ }
90
+ }), a.addEventListener("message", (l) => {
91
+ try {
92
+ t.value = JSON.parse(l.data);
93
+ } catch (f) {
94
+ u.value = f;
95
+ }
96
+ }), a.addEventListener("heartbeat", () => {
97
+ }), a.addEventListener("error", () => {
98
+ a.readyState === EventSource.CLOSED && (r.value = !0);
99
+ }), () => {
100
+ a.close();
101
+ };
102
+ }, [e, s, _]), { data: t, snapshot: n, closed: r, error: u };
73
103
  }
74
104
  export {
75
- P as Loader,
76
- p as __veloUpdatePending,
77
- m as touch,
78
- T as useLoader,
79
- L as useNavigate,
80
- E as useParams,
105
+ m as Loader,
106
+ S as __veloUpdatePending,
107
+ L as touch,
108
+ U as useEventStream,
109
+ A as useLoader,
110
+ P as useNavigate,
111
+ T as useParams,
81
112
  H as usePathname,
82
113
  b as useQuery
83
114
  };
package/dist/index.d.ts CHANGED
@@ -2,4 +2,6 @@ export type { AppRoutes, RouteNode, RouteModule, LoaderArgs, ActionArgs, Metadat
2
2
  export type { VeloConfig } from "./config.js";
3
3
  export { defineConfig } from "./config.js";
4
4
  export { Scripts, Link } from "./components.js";
5
+ export { createEventStream } from "./events.js";
6
+ export type { EventStream, EventStreamConfig } from "./events.js";
5
7
  export type { Context, MiddlewareHandler, Next } from "hono";
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
- import { defineConfig as e } from "./config.js";
2
- import { Link as i, Scripts as n } from "./components.js";
1
+ import { defineConfig as o } from "./config.js";
2
+ import { Link as f, Scripts as i } from "./components.js";
3
+ import { createEventStream as n } from "./events.js";
3
4
  export {
4
- i as Link,
5
- n as Scripts,
6
- e as defineConfig
5
+ f as Link,
6
+ i as Scripts,
7
+ n as createEventStream,
8
+ o as defineConfig
7
9
  };