@mauroandre/velojs 0.0.54 → 0.0.56

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.
@@ -0,0 +1,283 @@
1
+ import { DEFAULT_HEARTBEAT_MS as L } from "./events.js";
2
+ const O = "snapshot", R = "slice", U = "error", B = "close", k = 50, q = 3e5;
3
+ let m = {}, C = k, b = q, j = L, x = !0;
4
+ function ie(e, t) {
5
+ m = e ? { ...e } : {}, C = t?.coalesceMs ?? k, b = t?.idleMs ?? q, j = t?.heartbeatMs ?? L, x = t?.logEmits ?? !0, V();
6
+ }
7
+ function se() {
8
+ return m;
9
+ }
10
+ function ae() {
11
+ return Object.keys(m);
12
+ }
13
+ const d = /* @__PURE__ */ new Map(), v = /* @__PURE__ */ new Map();
14
+ function K(e, t) {
15
+ return `${e}\0${t}`;
16
+ }
17
+ function Q(e, t) {
18
+ const n = K(e, t);
19
+ if (v.has(n)) return;
20
+ const o = setTimeout(() => {
21
+ v.delete(n), G(e, t);
22
+ }, C), r = o.unref;
23
+ typeof r == "function" && r.call(o), v.set(n, { timer: o, channel: e, partition: t });
24
+ }
25
+ function V() {
26
+ for (const e of v.values()) clearTimeout(e.timer);
27
+ v.clear();
28
+ }
29
+ function le() {
30
+ const e = [];
31
+ let t = 0;
32
+ for (const [o, r] of d) {
33
+ const l = [];
34
+ for (const [f, u] of r) {
35
+ const a = /* @__PURE__ */ new Map();
36
+ for (const c of u) {
37
+ const s = a.get(c.moduleId) ?? { count: 0, last: void 0 };
38
+ s.count += 1, c.lastDeliveryAt !== void 0 && (s.last === void 0 || c.lastDeliveryAt > s.last) && (s.last = c.lastDeliveryAt), a.set(c.moduleId, s);
39
+ }
40
+ for (const [c, s] of a)
41
+ l.push({
42
+ moduleId: c,
43
+ partition: f,
44
+ connections: s.count,
45
+ lastDeliveryAt: s.last === void 0 ? null : new Date(s.last).toISOString()
46
+ });
47
+ }
48
+ e.push({
49
+ channel: o,
50
+ groups: l,
51
+ connections: z(r)
52
+ }), t += z(r);
53
+ }
54
+ const n = [];
55
+ for (const o of v.values())
56
+ n.push({ channel: o.channel, partition: o.partition });
57
+ return { channels: e, openCoalesceWindows: n, totalConnections: t };
58
+ }
59
+ function z(e) {
60
+ let t = 0;
61
+ for (const n of e.values()) t += n.size;
62
+ return t;
63
+ }
64
+ function X(e) {
65
+ let t = d.get(e.channel);
66
+ t || (t = /* @__PURE__ */ new Map(), d.set(e.channel, t));
67
+ let n = t.get(e.partition);
68
+ n || (n = /* @__PURE__ */ new Set(), t.set(e.partition, n)), n.add(e);
69
+ }
70
+ function Y(e) {
71
+ const t = d.get(e.channel), n = t?.get(e.partition);
72
+ !t || !n || (n.delete(e), n.size === 0 && t.delete(e.partition), t.size === 0 && d.delete(e.channel));
73
+ }
74
+ function ue() {
75
+ d.clear(), V();
76
+ }
77
+ async function $(e, t) {
78
+ const n = e.scope;
79
+ return typeof n != "function" ? "all" : await n(t);
80
+ }
81
+ function Z(e, t) {
82
+ return e.get(t);
83
+ }
84
+ async function H(e, t) {
85
+ const n = d.get(e)?.get(t);
86
+ if (!n || n.size === 0) return null;
87
+ const o = m[e], r = [];
88
+ for (const l of [...n]) {
89
+ let f;
90
+ try {
91
+ f = await $(o, l.principal);
92
+ } catch (u) {
93
+ console.error(`[velojs] channel "${e}" scope threw on emit:`, u), f = null;
94
+ }
95
+ if (f == null || f !== t) {
96
+ await l.terminate();
97
+ continue;
98
+ }
99
+ r.push(l);
100
+ }
101
+ return r.length > 0 ? r : null;
102
+ }
103
+ async function G(e, t) {
104
+ const n = await H(e, t);
105
+ n && await Promise.all(n.map((o) => J(o)));
106
+ }
107
+ async function J(e) {
108
+ let t;
109
+ try {
110
+ t = await e.loader({
111
+ params: e.params,
112
+ query: e.query,
113
+ c: e.c
114
+ });
115
+ } catch (n) {
116
+ await e.send(
117
+ U,
118
+ JSON.stringify({
119
+ message: n instanceof Error ? n.message : String(n)
120
+ })
121
+ );
122
+ return;
123
+ }
124
+ t instanceof Response || await e.send(O, JSON.stringify(t ?? null));
125
+ }
126
+ function ce(e, t, n, o) {
127
+ return typeof e == "string" ? ee(e, t) : te(e, t, n, o);
128
+ }
129
+ async function ee(e, t) {
130
+ const n = m[e];
131
+ if (!n)
132
+ throw new Error(
133
+ `[velojs] emit("${e}"): this channel has no entry in app/channels.ts. Add one — a typo or a renamed channel would otherwise be a silent no-op.`
134
+ );
135
+ const o = await $(n, t), r = o == null ? 0 : d.get(e)?.get(o)?.size ?? 0;
136
+ if (W(e, o, "invalidate", r), o != null && r !== 0) {
137
+ if (C <= 0) {
138
+ await G(e, o);
139
+ return;
140
+ }
141
+ Q(e, o);
142
+ }
143
+ }
144
+ async function te(e, t, n, o) {
145
+ const r = e?.metadata?.moduleId;
146
+ if (!r)
147
+ throw new Error(
148
+ `[velojs] emit(module, "${t}", …): the module has no metadata.moduleId — pass the imported route module (the Vite plugin injects its metadata), the same object the route tree uses.`
149
+ );
150
+ const l = e.channels;
151
+ if (!Array.isArray(l) || !l.includes(t))
152
+ throw new Error(
153
+ `[velojs] emit(module, "${t}", …): module "${r}" does not declare channel "${t}" — add it to the module's \`channels\` export, or use emit("${t}", ctx) for the invalidation mode.`
154
+ );
155
+ if (typeof e.loader != "function")
156
+ throw new Error(
157
+ `[velojs] emit(module, "${t}", …): module "${r}" has no \`loader\` — there is no data shape to slice.`
158
+ );
159
+ const f = m[t];
160
+ if (!f)
161
+ throw new Error(
162
+ `[velojs] emit(module, "${t}", …): this channel has no entry in app/channels.ts. Add one — a typo or a renamed channel would otherwise be a silent no-op.`
163
+ );
164
+ if (o === null || typeof o != "object" || Array.isArray(o))
165
+ throw new Error(
166
+ `[velojs] emit(module, "${t}", …): the slice must be the object of changed keys — got ${o === null ? "null" : Array.isArray(o) ? "an array" : typeof o}.`
167
+ );
168
+ const u = await $(f, n), a = u == null ? void 0 : d.get(t)?.get(u), c = a?.size ?? 0;
169
+ if (W(t, u, "slice", c), u == null || !a || c === 0) return;
170
+ const s = await H(t, u);
171
+ if (!s) return;
172
+ const y = s.filter((p) => p.moduleId === r);
173
+ if (y.length === 0) return;
174
+ const A = JSON.stringify(o);
175
+ await Promise.all(y.map((p) => p.send(R, A)));
176
+ }
177
+ function W(e, t, n, o) {
178
+ if (!x) return;
179
+ const r = typeof t == "string" ? `"${t}"` : "null";
180
+ console.log(
181
+ `[velojs] emit kind=${n} channel="${e}" partition=${r} connections=${o} at=${(/* @__PURE__ */ new Date()).toISOString()}`
182
+ );
183
+ }
184
+ function ne(e, t) {
185
+ let n = Promise.resolve();
186
+ return (o) => (n = n.then(() => t(o)).then(() => {
187
+ }).catch((r) => {
188
+ console.warn(
189
+ `[velojs] channel "${e}": SSE write failed — the snapshot did not reach the client:`,
190
+ r
191
+ );
192
+ }), n);
193
+ }
194
+ function fe(e, t, n, o, r, l) {
195
+ if (typeof o.loader != "function")
196
+ throw new Error(
197
+ `[velojs] module "${r}" declares \`channels\` without a \`loader\` — there is nothing to synchronize. Add a loader or remove the export.`
198
+ );
199
+ if (!m[n])
200
+ throw new Error(
201
+ `[velojs] module "${r}" declares channel "${n}" with no entry in app/channels.ts — add the partition resolver for "${n}".`
202
+ );
203
+ const f = o.loader, u = async (a) => {
204
+ const c = m[n], s = Z(a, "user");
205
+ let y;
206
+ try {
207
+ y = await $(c, s);
208
+ } catch (E) {
209
+ return console.error(`[velojs] channel "${n}" scope threw:`, E), a.json({ error: "internal" }, 500);
210
+ }
211
+ if (y == null)
212
+ return a.json({ error: "forbidden" }, 403);
213
+ const { streamSSE: A } = await import("hono/streaming"), p = await A(a, async (E) => {
214
+ let w = !1, I = () => {
215
+ };
216
+ const F = new Promise((i) => {
217
+ I = i;
218
+ }), T = ne(
219
+ n,
220
+ (i) => typeof i == "string" ? E.write(i) : E.writeSSE(i)
221
+ ), S = {
222
+ channel: n,
223
+ partition: y,
224
+ moduleId: r,
225
+ principal: s,
226
+ loader: f,
227
+ c: a,
228
+ params: a.req.param(),
229
+ query: a.req.query(),
230
+ send: (i, P) => w ? Promise.resolve() : i === O || i === R ? T({ event: i, data: P }).then(() => {
231
+ w || (S.lastDeliveryAt = Date.now(), _());
232
+ }) : T({ event: i, data: P }),
233
+ terminate: (i) => N(i)
234
+ }, M = () => {
235
+ w || (w = !0, h !== null && clearTimeout(h), g !== null && clearInterval(g), Y(S), I());
236
+ }, N = async (i = "partition") => {
237
+ w || (await S.send(B, JSON.stringify({ reason: i })), M());
238
+ };
239
+ let h = null;
240
+ const _ = () => {
241
+ if (b <= 0 || w) return;
242
+ h !== null && clearTimeout(h), h = setTimeout(() => {
243
+ h = null, N("idle");
244
+ }, b);
245
+ const i = h.unref;
246
+ typeof i == "function" && i.call(h);
247
+ };
248
+ let g = null;
249
+ if (j > 0 && (g = setInterval(() => {
250
+ w || T(`: ping
251
+
252
+ `);
253
+ }, j)), X(S), E.onAbort(M), _(), g !== null) {
254
+ const i = g.unref;
255
+ typeof i == "function" && i.call(g);
256
+ }
257
+ await J(S), await F;
258
+ }), D = new Headers(p.headers);
259
+ return D.set("Cache-Control", "no-store"), new Response(p.body, {
260
+ status: p.status,
261
+ statusText: p.statusText,
262
+ headers: D
263
+ });
264
+ };
265
+ l.length > 0 ? e.on(["GET"], [t], ...l, u) : e.on(["GET"], [t], u);
266
+ }
267
+ export {
268
+ B as CHANNEL_CLOSE_EVENT,
269
+ U as CHANNEL_ERROR_EVENT,
270
+ k as DEFAULT_COALESCE_MS,
271
+ q as DEFAULT_IDLE_MS,
272
+ R as SLICE_EVENT,
273
+ O as SNAPSHOT_EVENT,
274
+ ue as __resetChannels,
275
+ ae as channelNames,
276
+ ne as createChannelWriteChain,
277
+ ce as emit,
278
+ se as getChannelMap,
279
+ le as inspectChannels,
280
+ J as pushSnapshot,
281
+ fe as registerChannelRoute,
282
+ ie as registerChannels
283
+ };
package/dist/client.js CHANGED
@@ -1,92 +1,102 @@
1
- import { jsxs as j, jsx as o } from "preact/jsx-runtime";
2
- import { hydrate as w } from "preact";
3
- import { Router as x, Route as a, Switch as $, useLocation as S } from "wouter-preact";
4
- import { useEffect as b } from "preact/hooks";
1
+ import { jsxs as w, jsx as r } from "preact/jsx-runtime";
2
+ import { hydrate as x } from "preact";
3
+ import { Router as S, Route as f, Switch as y, useLocation as b } from "wouter-preact";
4
+ import { useEffect as B } from "preact/hooks";
5
5
  import { registerRoutePatterns as L, syncLocation as W } from "./loader-store.js";
6
- const s = (t, n, e) => n ? e && n === "/" ? t || "/" : n.startsWith("/") ? t + n : t + "/" + n : t, v = (t) => t === "" ? "*" : `${t}/*?`, E = (t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), M = (t) => {
6
+ import { ChannelBoundary as v } from "./live-loader.js";
7
+ const l = (t, n, e) => n ? e && n === "/" ? t || "/" : n.startsWith("/") ? t + n : t + "/" + n : t, E = (t) => t === "" ? "*" : `${t}/*?`, I = (t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), M = (t) => {
7
8
  let n = !1;
8
9
  t.endsWith("/*?") && (t = t.slice(0, -3), n = !0);
9
- const e = t.split("/").map((c) => c.startsWith(":") ? "[^/]+" : E(c)).join("/");
10
+ const e = t.split("/").map((c) => c.startsWith(":") ? "[^/]+" : I(c)).join("/");
10
11
  return n ? `${e}(?:/.*)?` : e;
11
- }, p = (t, n) => {
12
+ }, d = (t, n) => {
12
13
  if (t.path === "*") return [];
13
14
  if (!t.module) {
14
- const c = s(n, t.path, !1);
15
- return (t.children ?? []).flatMap((i) => p(i, c));
15
+ const c = l(n, t.path, !1);
16
+ return (t.children ?? []).flatMap((i) => d(i, c));
16
17
  }
17
18
  if (!t.children)
18
- return [s(n, t.path, !0) || "/"];
19
+ return [l(n, t.path, !0) || "/"];
19
20
  if (t.path)
20
- return [`${s(n, t.path, !1)}/*?`];
21
- const e = s(n, t.path, !1);
22
- return (t.children ?? []).flatMap((c) => p(c, e));
21
+ return [`${l(n, t.path, !1)}/*?`];
22
+ const e = l(n, t.path, !1);
23
+ return (t.children ?? []).flatMap((c) => d(c, e));
23
24
  }, _ = (t, n) => {
24
- const e = p(t, n);
25
+ const e = d(t, n);
25
26
  return new RegExp(`^(?:${e.map(M).join("|")})$`);
26
- }, R = (t) => {
27
+ }, C = (t) => {
27
28
  for (const n of t) {
28
29
  if (n.path === "*" && n.module) return n.module.Component;
29
30
  if (n.children) {
30
- const e = R(n.children);
31
+ const e = C(n.children);
31
32
  if (e) return e;
32
33
  }
33
34
  }
34
35
  return null;
35
- }, y = (t, n) => n ? [...t, /* @__PURE__ */ o(a, { children: /* @__PURE__ */ o(n, {}) }, "__notfound")] : t, h = (t, n, e, c) => {
36
+ }, R = (t, n) => n ? [...t, /* @__PURE__ */ r(f, { children: /* @__PURE__ */ r(n, {}) }, "__notfound")] : t, a = (t, n, e, c) => {
36
37
  const i = [];
37
- for (let u = 0; u < t.length; u++) {
38
- const r = t[u], l = `${c}${u}`;
39
- if (r.path === "*") continue;
40
- if (!r.module) {
41
- if (r.children) {
42
- const f = s(n, r.path, !1);
43
- i.push(...h(r.children, f, e, `${l}-`));
38
+ for (let h = 0; h < t.length; h++) {
39
+ const o = t[h], s = `${c}${h}`;
40
+ if (o.path === "*") continue;
41
+ if (!o.module) {
42
+ if (o.children) {
43
+ const u = l(n, o.path, !1);
44
+ i.push(...a(o.children, u, e, `${s}-`));
44
45
  }
45
46
  continue;
46
47
  }
47
- const d = r.module.Component;
48
- if (!r.children) {
49
- const f = s(n, r.path, !0) || "/";
48
+ const p = o.module.Component, m = (u) => /* @__PURE__ */ r(
49
+ v,
50
+ {
51
+ moduleId: o.module?.metadata?.moduleId,
52
+ channels: o.module?.channels,
53
+ children: u
54
+ }
55
+ );
56
+ if (!o.children) {
57
+ const u = l(n, o.path, !0) || "/";
50
58
  i.push(
51
- /* @__PURE__ */ o(a, { path: f, children: /* @__PURE__ */ o(d, {}) }, l)
59
+ /* @__PURE__ */ r(f, { path: u, children: m(/* @__PURE__ */ r(p, {})) }, s)
52
60
  );
53
61
  continue;
54
62
  }
55
- if (r.isRoot) {
63
+ if (o.isRoot) {
56
64
  i.push(
57
- /* @__PURE__ */ o($, { children: y(
58
- h(r.children, "", e, `${l}-`),
65
+ /* @__PURE__ */ r(y, { children: R(
66
+ a(o.children, "", e, `${s}-`),
59
67
  e
60
- ) }, l)
68
+ ) }, s)
61
69
  );
62
70
  continue;
63
71
  }
64
- const m = s(n, r.path, !1), C = r.path ? v(m) : _(r, n);
72
+ const $ = l(n, o.path, !1), j = o.path ? E($) : _(o, n);
65
73
  i.push(
66
- /* @__PURE__ */ o(a, { path: C, children: /* @__PURE__ */ o(d, { children: /* @__PURE__ */ o($, { children: y(
67
- h(r.children, m, e, `${l}-`),
68
- e
69
- ) }) }) }, l)
74
+ /* @__PURE__ */ r(f, { path: j, children: m(
75
+ /* @__PURE__ */ r(p, { children: /* @__PURE__ */ r(y, { children: R(
76
+ a(o.children, $, e, `${s}-`),
77
+ e
78
+ ) }) })
79
+ ) }, s)
70
80
  );
71
81
  }
72
82
  return i;
73
83
  }, g = () => {
74
- const [t] = S();
75
- return b(() => {
84
+ const [t] = b();
85
+ return B(() => {
76
86
  W(window.location.pathname);
77
87
  }, [t]), null;
78
88
  }, k = ({ routes: t }) => {
79
89
  L(t);
80
- const n = R(t), e = h(t, "", n, "");
81
- return /* @__PURE__ */ j(x, { children: [
82
- /* @__PURE__ */ o(g, {}),
90
+ const n = C(t), e = a(t, "", n, "");
91
+ return /* @__PURE__ */ w(S, { children: [
92
+ /* @__PURE__ */ r(g, {}),
83
93
  e
84
94
  ] });
85
- }, z = (t) => {
95
+ }, H = (t) => {
86
96
  const { routes: n } = t, e = document.querySelector("body");
87
- e && w(/* @__PURE__ */ o(k, { routes: n }), e);
97
+ e && x(/* @__PURE__ */ r(k, { routes: n }), e);
88
98
  };
89
99
  export {
90
100
  k as ClientRoutes,
91
- z as startClient
101
+ H as startClient
92
102
  };
@@ -3,7 +3,8 @@ import { useRouter as L, Link as $ } from "wouter-preact";
3
3
  import "@preact/signals";
4
4
  import "preact/hooks";
5
5
  import { __veloUpdatePending as C } from "./loader-store.js";
6
- function w({ basePath: t, favicon: e = "/favicon.ico" } = {}) {
6
+ import "./live-loader.js";
7
+ function y({ basePath: t, favicon: e = "/favicon.ico" } = {}) {
7
8
  const n = process.env.NODE_ENV !== "production";
8
9
  t = t || process.env.STATIC_BASE_URL || (process.env.VELO_STATIC ? "/client" : "");
9
10
  const s = e === !1 ? void 0 : e.endsWith(".svg") ? "image/svg+xml" : e.endsWith(".png") ? "image/png" : "image/x-icon", i = e !== !1 && /* @__PURE__ */ r("link", { rel: "icon", href: `${t}${e}`, type: s });
@@ -25,7 +26,7 @@ function S({ localStorage: t, cookies: e } = {}) {
25
26
  const n = (c) => JSON.stringify(c).replace(/</g, "\\u003c"), s = t && t.length > 0 ? `var L=${n(t)};for(var i=0;i<L.length;i++){var v=localStorage.getItem(L[i]);if(v!==null)b[L[i]]=v;}` : "", i = e && e.length > 0 ? `var C=${n(e)};var s=document.cookie?document.cookie.split("; "):[];var m={};for(var j=0;j<s.length;j++){var p=s[j].indexOf("=");if(p>0){m[s[j].slice(0,p)]=decodeURIComponent(s[j].slice(p+1));}}for(var k=0;k<C.length;k++){if(C[k]in m)b[C[k]]=m[C[k]];}` : "";
26
27
  return `(function(){var b={};try{${s}}catch(e){}try{${i}}catch(e){}if(Object.keys(b).length){window.__VELO_BOOT__=Object.assign(window.__VELO_BOOT__||{},b);var d=document.documentElement;for(var q in b){d.setAttribute("data-"+String(q).replace(/[^a-zA-Z0-9-]/g,"-"),b[q]);}}})();`;
27
28
  }
28
- function y(t) {
29
+ function I(t) {
29
30
  const e = S(t);
30
31
  return e ? /* @__PURE__ */ r("script", { dangerouslySetInnerHTML: { __html: e } }) : null;
31
32
  }
@@ -35,7 +36,7 @@ function m(t, e) {
35
36
  n = n.replace(`:${s}`, i);
36
37
  return n;
37
38
  }
38
- function I({ to: t, params: e, search: n, absolute: s, ...i }) {
39
+ function V({ to: t, params: e, search: n, absolute: s, ...i }) {
39
40
  const c = typeof t != "string", l = L(), _ = c ? t.metadata?.fullPath ?? "/" : t, f = e ? m(_, e) : _, u = n ? `?${new URLSearchParams(n).toString()}` : "", v = `${f}${u}`;
40
41
  if (typeof window < "u" && C.value) {
41
42
  let a;
@@ -61,9 +62,9 @@ function I({ to: t, params: e, search: n, absolute: s, ...i }) {
61
62
  return /* @__PURE__ */ r($, { to: v, ...i });
62
63
  }
63
64
  export {
64
- y as Boot,
65
- I as Link,
66
- w as Scripts,
65
+ I as Boot,
66
+ V as Link,
67
+ y as Scripts,
67
68
  S as bootScript,
68
69
  m as substituteParams
69
70
  };
package/dist/config.d.ts CHANGED
@@ -26,9 +26,11 @@ export interface VeloConfig {
26
26
  */
27
27
  port?: number;
28
28
  /**
29
- * Interface de bind do servidor de produção. A env `HOST` tem precedência.
30
- * Default do Node: todas as interfaces (o que Docker/cloud esperam).
31
- * Apps locais/sensíveis devem declarar "127.0.0.1".
29
+ * Interface de bind do servidor, em dev e em produção. A env `HOST` tem
30
+ * precedência nos dois modos; em dev a ordem é: `server.host` do Vite
31
+ * (`--host`/vite.config) > env `HOST` > este campo. Default do Node:
32
+ * todas as interfaces (o que Docker/cloud esperam). Apps
33
+ * locais/sensíveis devem declarar "127.0.0.1".
32
34
  */
33
35
  hostname?: string;
34
36
  /**
package/dist/events.d.ts CHANGED
@@ -284,6 +284,12 @@ interface StreamListener<TEvent> {
284
284
  (event: TEvent): void;
285
285
  close(): void;
286
286
  }
287
+ /**
288
+ * The house's single SSE transport heartbeat, in milliseconds — the same
289
+ * default the live-loader channels use. Exported so the two surfaces never
290
+ * diverge in operational expectation.
291
+ */
292
+ export declare const DEFAULT_HEARTBEAT_MS = 20000;
287
293
  export declare function flushPendingStreamRoutes(app: Hono): void;
288
294
  /** @internal Used by `@mauroandre/velojs/testing` to reset all streams. */
289
295
  export declare function getRegisteredStreams(): ReadonlySet<EventStream<unknown, unknown>>;
package/dist/events.js CHANGED
@@ -232,6 +232,7 @@ function k(r, b, o, a = []) {
232
232
  a.length > 0 ? r.on(["GET"], [b], ...a, p) : r.on(["GET"], [b], p);
233
233
  }
234
234
  export {
235
+ D as DEFAULT_HEARTBEAT_MS,
235
236
  x as createEventStream,
236
237
  F as flushPendingStreamRoutes,
237
238
  O as getRegisteredStreams,
package/dist/hooks.d.ts CHANGED
@@ -1,7 +1,28 @@
1
- import { type Signal } from "@preact/signals";
1
+ import { type ReadonlySignal, type Signal } from "@preact/signals";
2
2
  import type { EventStream } from "./events.js";
3
3
  import type { SocketHandler, SocketStub } from "./sockets.js";
4
4
  import { __veloUpdatePending } from "./loader-store.js";
5
+ import { type Freshness } from "./live-loader.js";
6
+ export type { Freshness } from "./live-loader.js";
7
+ /**
8
+ * What `Loader()`/`useLoader()` return. `freshness` is the page-level state of
9
+ * the live loader: `"live"` (connected), `"stale"` (connection closed,
10
+ * reconnecting or silent) and `"error"` (the last loader re-execution failed).
11
+ * On a page with no `channels` it exists and stays `"live"`. The framework
12
+ * exposes it as data for CSS to react to — it renders no JSX of its own.
13
+ *
14
+ * `freshnessByChannel` is the same state with per-channel granularity, keyed
15
+ * by the channel names the module declares — a page with two channels, one
16
+ * fallen and one following, shows both states at once. Empty record when the
17
+ * module declares no channels.
18
+ */
19
+ export interface LoaderHandle<T> {
20
+ data: Signal<T | null>;
21
+ loading: Signal<boolean>;
22
+ refetch: () => void;
23
+ freshness: Signal<Freshness>;
24
+ freshnessByChannel: ReadonlySignal<Record<string, Freshness>>;
25
+ }
5
26
  export { __veloUpdatePending };
6
27
  declare global {
7
28
  interface Window {
@@ -58,11 +79,7 @@ export declare function touch<T>(sig: Signal<T | null>): void;
58
79
  *
59
80
  * @param moduleId - Injetado automaticamente pelo veloPlugin
60
81
  */
61
- export declare function Loader<T>(moduleId?: string): {
62
- data: Signal<T | null>;
63
- loading: Signal<boolean>;
64
- refetch: () => void;
65
- };
82
+ export declare function Loader<T>(moduleId?: string): LoaderHandle<T>;
66
83
  /**
67
84
  * Hook para acessar dados do loader dentro de componentes
68
85
  * Suporta SSR hydration e navegação SPA (faz fetch se necessário)
@@ -82,21 +99,9 @@ export declare function Loader<T>(moduleId?: string): {
82
99
  * @param moduleId - Injetado automaticamente pelo veloPlugin
83
100
  * @param deps - Array de dependências (triggers re-fetch quando mudam)
84
101
  */
85
- export declare function useLoader<T>(): {
86
- data: Signal<T | null>;
87
- loading: Signal<boolean>;
88
- refetch: () => void;
89
- };
90
- export declare function useLoader<T>(deps: any[]): {
91
- data: Signal<T | null>;
92
- loading: Signal<boolean>;
93
- refetch: () => void;
94
- };
95
- export declare function useLoader<T>(moduleId: string, deps?: any[]): {
96
- data: Signal<T | null>;
97
- loading: Signal<boolean>;
98
- refetch: () => void;
99
- };
102
+ export declare function useLoader<T>(): LoaderHandle<T>;
103
+ export declare function useLoader<T>(deps: any[]): LoaderHandle<T>;
104
+ export declare function useLoader<T>(moduleId: string, deps?: any[]): LoaderHandle<T>;
100
105
  /**
101
106
  * Hook para navegação programática
102
107
  *