@mauroandre/velojs 0.0.55 → 0.0.57

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,376 @@
1
+ import { DEFAULT_HEARTBEAT_MS as J } from "./events.js";
2
+ const U = "snapshot", V = "slice", ne = "error", oe = "close", H = 50, W = 3e5;
3
+ let d = {}, O = H, M = W, N = J, F = !0;
4
+ function ye(e, t) {
5
+ d = e ? { ...e } : {}, O = t?.coalesceMs ?? H, M = t?.idleMs ?? W, N = t?.heartbeatMs ?? J, F = t?.logEmits ?? !0, Q(), E.clear();
6
+ }
7
+ function we() {
8
+ return d;
9
+ }
10
+ function ge() {
11
+ return Object.keys(d);
12
+ }
13
+ const f = /* @__PURE__ */ new Map(), E = /* @__PURE__ */ new Map(), $ = /* @__PURE__ */ new Map();
14
+ function se(e, t) {
15
+ const r = P(e, t);
16
+ if ($.has(r)) return;
17
+ const n = setTimeout(() => {
18
+ $.delete(r), Y(e, t);
19
+ }, O), o = n.unref;
20
+ typeof o == "function" && o.call(n), $.set(r, { timer: n, channel: e, partition: t });
21
+ }
22
+ function Q() {
23
+ for (const e of $.values()) clearTimeout(e.timer);
24
+ $.clear();
25
+ }
26
+ function ve() {
27
+ const e = [];
28
+ let t = 0;
29
+ for (const [n, o] of f) {
30
+ const s = [];
31
+ for (const [l, c] of o) {
32
+ const p = /* @__PURE__ */ new Map();
33
+ for (const a of c) {
34
+ const u = p.get(a.moduleId) ?? { count: 0, last: void 0 };
35
+ u.count += 1, a.lastDeliveryAt !== void 0 && (u.last === void 0 || a.lastDeliveryAt > u.last) && (u.last = a.lastDeliveryAt), p.set(a.moduleId, u);
36
+ }
37
+ for (const [a, u] of p)
38
+ s.push({
39
+ moduleId: a,
40
+ partition: l,
41
+ connections: u.count,
42
+ lastDeliveryAt: u.last === void 0 ? null : new Date(u.last).toISOString()
43
+ });
44
+ }
45
+ e.push({
46
+ channel: n,
47
+ groups: s,
48
+ connections: q(o)
49
+ }), t += q(o);
50
+ }
51
+ const r = [];
52
+ for (const n of $.values())
53
+ r.push({ channel: n.channel, partition: n.partition });
54
+ return { channels: e, openCoalesceWindows: r, totalConnections: t };
55
+ }
56
+ function q(e) {
57
+ let t = 0;
58
+ for (const r of e.values()) t += r.size;
59
+ return t;
60
+ }
61
+ function P(e, t) {
62
+ return `${e}\0${t}`;
63
+ }
64
+ function ie(e) {
65
+ let t = f.get(e.channel);
66
+ t || (t = /* @__PURE__ */ new Map(), f.set(e.channel, t));
67
+ let r = t.get(e.partition);
68
+ r || (r = /* @__PURE__ */ new Set(), t.set(e.partition, r));
69
+ const n = r.size === 0;
70
+ return r.add(e), n;
71
+ }
72
+ function x(e) {
73
+ const t = f.get(e.channel), r = t?.get(e.partition);
74
+ if (!t || !r) return !1;
75
+ r.delete(e);
76
+ const n = r.size === 0;
77
+ return n && t.delete(e.partition), t.size === 0 && f.delete(e.channel), n;
78
+ }
79
+ function B(e, t, r) {
80
+ const n = P(e, t), o = d[e]?.onGroupOpen, s = (async () => {
81
+ if (typeof o == "function")
82
+ try {
83
+ await o({ partition: t, params: r });
84
+ } catch (l) {
85
+ console.error(
86
+ `[velojs] channel "${e}" onGroupOpen failed for partition "${t}":`,
87
+ l
88
+ );
89
+ }
90
+ })();
91
+ return E.set(n, s), s.then(() => {
92
+ E.get(n) === s && E.delete(n);
93
+ }), s;
94
+ }
95
+ async function ae(e) {
96
+ const t = P(e.channel, e.partition), r = E.get(t);
97
+ r && await r;
98
+ const n = f.get(e.channel)?.get(e.partition);
99
+ if (!n || !n.has(e) || n.size > 1) {
100
+ x(e);
101
+ return;
102
+ }
103
+ const o = d[e.channel]?.onGroupClose;
104
+ if (typeof o == "function")
105
+ try {
106
+ await o({ partition: e.partition });
107
+ } catch (c) {
108
+ console.error(
109
+ `[velojs] channel "${e.channel}" onGroupClose failed for partition "${e.partition}":`,
110
+ c
111
+ );
112
+ }
113
+ x(e);
114
+ const s = f.get(e.channel)?.get(e.partition), l = s ? [...s][0] : void 0;
115
+ l && await B(e.channel, e.partition, l.params);
116
+ }
117
+ function Ee() {
118
+ f.clear(), E.clear(), Q();
119
+ }
120
+ async function T(e, t) {
121
+ const r = e.scope;
122
+ return typeof r != "function" ? "all" : await r(t);
123
+ }
124
+ function le(e, t) {
125
+ return e.get(t);
126
+ }
127
+ async function K(e, t) {
128
+ const r = f.get(e)?.get(t);
129
+ if (!r || r.size === 0) return null;
130
+ const n = d[e], o = [];
131
+ for (const s of [...r]) {
132
+ let l;
133
+ try {
134
+ l = await T(n, {
135
+ user: s.principal,
136
+ params: s.params
137
+ });
138
+ } catch (c) {
139
+ console.error(`[velojs] channel "${e}" scope threw on emit:`, c), l = null;
140
+ }
141
+ if (l == null || l !== t) {
142
+ await s.terminate();
143
+ continue;
144
+ }
145
+ o.push(s);
146
+ }
147
+ return o.length > 0 ? o : null;
148
+ }
149
+ async function Y(e, t) {
150
+ const r = await K(e, t);
151
+ r && await Promise.all(r.map((n) => X(n)));
152
+ }
153
+ async function X(e) {
154
+ let t;
155
+ try {
156
+ t = await e.loader({
157
+ params: e.params,
158
+ query: e.query,
159
+ c: e.c
160
+ });
161
+ } catch (r) {
162
+ await e.send(
163
+ ne,
164
+ JSON.stringify({
165
+ message: r instanceof Error ? r.message : String(r)
166
+ })
167
+ );
168
+ return;
169
+ }
170
+ t instanceof Response || await e.send(U, JSON.stringify(t ?? null));
171
+ }
172
+ function $e(e, t, r, n) {
173
+ return typeof e == "string" ? ue(e, t ?? {}) : ce(e, t, r, n);
174
+ }
175
+ async function ue(e, t) {
176
+ const r = d[e];
177
+ if (!r)
178
+ throw new Error(
179
+ `[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.`
180
+ );
181
+ const n = await T(r, t), o = n == null ? 0 : f.get(e)?.get(n)?.size ?? 0;
182
+ if (Z(e, n, "invalidate", o), n != null && o !== 0) {
183
+ if (O <= 0) {
184
+ await Y(e, n);
185
+ return;
186
+ }
187
+ se(e, n);
188
+ }
189
+ }
190
+ async function ce(e, t, r, n) {
191
+ const o = e?.metadata?.moduleId;
192
+ if (!o)
193
+ throw new Error(
194
+ `[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.`
195
+ );
196
+ const s = e.channels;
197
+ if (!Array.isArray(s) || !s.includes(t))
198
+ throw new Error(
199
+ `[velojs] emit(module, "${t}", …): module "${o}" does not declare channel "${t}" — add it to the module's \`channels\` export, or use emit("${t}", ctx) for the invalidation mode.`
200
+ );
201
+ if (typeof e.loader != "function")
202
+ throw new Error(
203
+ `[velojs] emit(module, "${t}", …): module "${o}" has no \`loader\` — there is no data shape to slice.`
204
+ );
205
+ const l = d[t];
206
+ if (!l)
207
+ throw new Error(
208
+ `[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.`
209
+ );
210
+ if (n === null || typeof n != "object" || Array.isArray(n))
211
+ throw new Error(
212
+ `[velojs] emit(module, "${t}", …): the slice must be the object of changed keys — got ${n === null ? "null" : Array.isArray(n) ? "an array" : typeof n}.`
213
+ );
214
+ const c = await T(l, r), p = c == null ? void 0 : f.get(t)?.get(c), a = p?.size ?? 0;
215
+ if (Z(t, c, "slice", a), c == null || !p || a === 0) return;
216
+ const u = await K(t, c);
217
+ if (!u) return;
218
+ const w = u.filter((g) => g.moduleId === o);
219
+ if (w.length === 0) return;
220
+ const S = JSON.stringify(n);
221
+ await Promise.all(w.map((g) => g.send(V, S)));
222
+ }
223
+ function Z(e, t, r, n) {
224
+ if (!F) return;
225
+ const o = typeof t == "string" ? `"${t}"` : "null";
226
+ console.log(
227
+ `[velojs] emit kind=${r} channel="${e}" partition=${o} connections=${n} at=${(/* @__PURE__ */ new Date()).toISOString()}`
228
+ );
229
+ }
230
+ function fe(e, t) {
231
+ let r = Promise.resolve();
232
+ return (n) => (r = r.then(() => t(n)).then(() => {
233
+ }).catch((o) => {
234
+ console.warn(
235
+ `[velojs] channel "${e}": SSE write failed — the snapshot did not reach the client:`,
236
+ o
237
+ );
238
+ }), r);
239
+ }
240
+ const C = "_route";
241
+ function de(e) {
242
+ return e ? e.split("/").filter((t) => t.startsWith(":")).map((t) => t.slice(1)) : [];
243
+ }
244
+ function pe(e, t) {
245
+ if (e == null || e === "")
246
+ return { params: {}, error: null };
247
+ let r;
248
+ try {
249
+ r = JSON.parse(e);
250
+ } catch {
251
+ return { params: {}, error: `the ?${C}= payload is not valid JSON` };
252
+ }
253
+ if (r === null || typeof r != "object" || Array.isArray(r))
254
+ return {
255
+ params: {},
256
+ error: `the ?${C}= payload must be an object of route params`
257
+ };
258
+ const n = {};
259
+ for (const [o, s] of Object.entries(r)) {
260
+ if (!t.includes(o)) {
261
+ const l = t.length > 0 ? t.join(", ") : "none";
262
+ return {
263
+ params: {},
264
+ error: `route param "${o}" is not declared by the module's path (declared: ${l})`
265
+ };
266
+ }
267
+ if (typeof s != "string")
268
+ return { params: {}, error: `route param "${o}" must be a URL string` };
269
+ n[o] = s;
270
+ }
271
+ return { params: n, error: null };
272
+ }
273
+ function Se(e, t, r, n, o, s) {
274
+ if (typeof n.loader != "function")
275
+ throw new Error(
276
+ `[velojs] module "${o}" declares \`channels\` without a \`loader\` — there is nothing to synchronize. Add a loader or remove the export.`
277
+ );
278
+ if (!d[r])
279
+ throw new Error(
280
+ `[velojs] module "${o}" declares channel "${r}" with no entry in app/channels.ts — add the partition resolver for "${r}".`
281
+ );
282
+ const l = n.loader, c = de(n.metadata?.fullPath), p = async (a) => {
283
+ const u = d[r], w = pe(
284
+ a.req.query(C),
285
+ c
286
+ );
287
+ if (w.error)
288
+ return console.error(`[velojs] channel "${r}": rejecting subscription — ${w.error}`), a.json({ error: "invalid-route-params", message: w.error }, 400);
289
+ const S = w.params, g = le(a, "user");
290
+ let b;
291
+ try {
292
+ b = await T(u, { user: g, params: S });
293
+ } catch (h) {
294
+ return console.error(`[velojs] channel "${r}" scope threw:`, h), a.json({ error: "forbidden" }, 403);
295
+ }
296
+ if (b == null)
297
+ return a.json({ error: "forbidden" }, 403);
298
+ const { streamSSE: ee } = await import("hono/streaming"), D = {};
299
+ for (const [h, m] of Object.entries(a.req.query()))
300
+ h !== C && (D[h] = m);
301
+ const A = await ee(a, async (h) => {
302
+ let m = !1, R = () => {
303
+ };
304
+ const te = new Promise((i) => {
305
+ R = i;
306
+ }), _ = fe(
307
+ r,
308
+ (i) => typeof i == "string" ? h.write(i) : h.writeSSE(i)
309
+ ), j = {
310
+ channel: r,
311
+ partition: b,
312
+ moduleId: o,
313
+ principal: g,
314
+ loader: l,
315
+ c: a,
316
+ params: S,
317
+ query: D,
318
+ send: (i, G) => m ? Promise.resolve() : i === U || i === V ? _({ event: i, data: G }).then(() => {
319
+ m || (j.lastDeliveryAt = Date.now(), L());
320
+ }) : _({ event: i, data: G }),
321
+ terminate: (i) => z(i)
322
+ }, k = () => {
323
+ m || (m = !0, y !== null && clearTimeout(y), v !== null && clearInterval(v), R(), ae(j));
324
+ }, z = async (i = "partition") => {
325
+ m || (await j.send(oe, JSON.stringify({ reason: i })), k());
326
+ };
327
+ let y = null;
328
+ const L = () => {
329
+ if (M <= 0 || m) return;
330
+ y !== null && clearTimeout(y), y = setTimeout(() => {
331
+ y = null, z("idle");
332
+ }, M);
333
+ const i = y.unref;
334
+ typeof i == "function" && i.call(y);
335
+ };
336
+ let v = null;
337
+ N > 0 && (v = setInterval(() => {
338
+ m || _(`: ping
339
+
340
+ `);
341
+ }, N));
342
+ const re = ie(j);
343
+ if (h.onAbort(k), L(), v !== null) {
344
+ const i = v.unref;
345
+ typeof i == "function" && i.call(v);
346
+ }
347
+ re && await B(r, b, S), await X(j), await te;
348
+ }), I = new Headers(A.headers);
349
+ return I.set("Cache-Control", "no-store"), new Response(A.body, {
350
+ status: A.status,
351
+ statusText: A.statusText,
352
+ headers: I
353
+ });
354
+ };
355
+ s.length > 0 ? e.on(["GET"], [t], ...s, p) : e.on(["GET"], [t], p);
356
+ }
357
+ export {
358
+ oe as CHANNEL_CLOSE_EVENT,
359
+ ne as CHANNEL_ERROR_EVENT,
360
+ H as DEFAULT_COALESCE_MS,
361
+ W as DEFAULT_IDLE_MS,
362
+ C as ROUTE_PARAMS_QUERY,
363
+ V as SLICE_EVENT,
364
+ U as SNAPSHOT_EVENT,
365
+ Ee as __resetChannels,
366
+ ge as channelNames,
367
+ fe as createChannelWriteChain,
368
+ de as declaredRouteParams,
369
+ $e as emit,
370
+ we as getChannelMap,
371
+ ve as inspectChannels,
372
+ pe as parseRouteParamsQuery,
373
+ X as pushSnapshot,
374
+ Se as registerChannelRoute,
375
+ ye as registerChannels
376
+ };
package/dist/client.js CHANGED
@@ -1,92 +1,103 @@
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, h = (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 a = 0; a < t.length; a++) {
39
+ const o = t[a], s = `${c}${a}`;
40
+ if (o.path === "*") continue;
41
+ if (!o.module) {
42
+ if (o.children) {
43
+ const u = l(n, o.path, !1);
44
+ i.push(...h(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
+ fullPath: o.module?.metadata?.fullPath,
54
+ children: u
55
+ }
56
+ );
57
+ if (!o.children) {
58
+ const u = l(n, o.path, !0) || "/";
50
59
  i.push(
51
- /* @__PURE__ */ o(a, { path: f, children: /* @__PURE__ */ o(d, {}) }, l)
60
+ /* @__PURE__ */ r(f, { path: u, children: m(/* @__PURE__ */ r(p, {})) }, s)
52
61
  );
53
62
  continue;
54
63
  }
55
- if (r.isRoot) {
64
+ if (o.isRoot) {
56
65
  i.push(
57
- /* @__PURE__ */ o($, { children: y(
58
- h(r.children, "", e, `${l}-`),
66
+ /* @__PURE__ */ r(y, { children: R(
67
+ h(o.children, "", e, `${s}-`),
59
68
  e
60
- ) }, l)
69
+ ) }, s)
61
70
  );
62
71
  continue;
63
72
  }
64
- const m = s(n, r.path, !1), C = r.path ? v(m) : _(r, n);
73
+ const $ = l(n, o.path, !1), j = o.path ? E($) : _(o, n);
65
74
  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)
75
+ /* @__PURE__ */ r(f, { path: j, children: m(
76
+ /* @__PURE__ */ r(p, { children: /* @__PURE__ */ r(y, { children: R(
77
+ h(o.children, $, e, `${s}-`),
78
+ e
79
+ ) }) })
80
+ ) }, s)
70
81
  );
71
82
  }
72
83
  return i;
73
84
  }, g = () => {
74
- const [t] = S();
75
- return b(() => {
85
+ const [t] = b();
86
+ return B(() => {
76
87
  W(window.location.pathname);
77
88
  }, [t]), null;
78
89
  }, k = ({ routes: t }) => {
79
90
  L(t);
80
- const n = R(t), e = h(t, "", n, "");
81
- return /* @__PURE__ */ j(x, { children: [
82
- /* @__PURE__ */ o(g, {}),
91
+ const n = C(t), e = h(t, "", n, "");
92
+ return /* @__PURE__ */ w(S, { children: [
93
+ /* @__PURE__ */ r(g, {}),
83
94
  e
84
95
  ] });
85
- }, z = (t) => {
96
+ }, H = (t) => {
86
97
  const { routes: n } = t, e = document.querySelector("body");
87
- e && w(/* @__PURE__ */ o(k, { routes: n }), e);
98
+ e && x(/* @__PURE__ */ r(k, { routes: n }), e);
88
99
  };
89
100
  export {
90
101
  k as ClientRoutes,
91
- z as startClient
102
+ H as startClient
92
103
  };
@@ -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/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
  *