@mauroandre/velojs 0.0.56 → 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.
- package/README.md +121 -24
- package/agent/AGENTS.md +84 -17
- package/agent/skills/velojs-live-loader/SKILL.md +105 -24
- package/dist/channels.d.ts +109 -28
- package/dist/channels.js +285 -192
- package/dist/client.js +8 -7
- package/dist/live-loader.d.ts +40 -7
- package/dist/live-loader.js +91 -81
- package/dist/server.d.ts +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +231 -228
- package/dist/testing/types.d.ts +21 -4
- package/package.json +1 -1
package/dist/live-loader.d.ts
CHANGED
|
@@ -3,11 +3,18 @@
|
|
|
3
3
|
*
|
|
4
4
|
* After hydration, every rendered module that declares `channels` opens one SSE
|
|
5
5
|
* connection per (module, channel) at `/_channel/{moduleId}/{channel}`. The
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* connection declares the route params the module's path declares, extracted
|
|
7
|
+
* from the current URL and serialized in `?_route=` — the client declares the
|
|
8
|
+
* address it is already seeing, the server validates it against the declared
|
|
9
|
+
* path and decides the partition. The snapshot the connection receives — the
|
|
10
|
+
* connect snapshot and each emit's — replaces the value of the module's entry
|
|
11
|
+
* in the loader store, so the page stays faithful to server state without a
|
|
12
|
+
* line of code from the developer: `useLoader()`/`Loader()` handles keep the
|
|
13
|
+
* same signal instance and the reading component re-renders on its own.
|
|
14
|
+
*
|
|
15
|
+
* The connection is keyed by those params, not by the pathname: navigating
|
|
16
|
+
* between children of the same params keeps the connection; a move to another
|
|
17
|
+
* resource closes it and opens a new one.
|
|
11
18
|
*
|
|
12
19
|
* A `slice` event is the other delivery mode: the producer already holds the
|
|
13
20
|
* value, so the payload carries only the changed keys. A slice is merged
|
|
@@ -68,19 +75,45 @@ export declare function loaderFreshness(): Signal<Freshness>;
|
|
|
68
75
|
* closer — the channel lives exactly as long as the module that declares it is
|
|
69
76
|
* mounted.
|
|
70
77
|
*
|
|
78
|
+
* `routeParams` are the params the module's route declares, extracted from the
|
|
79
|
+
* current URL: the client **declares** the address it is already seeing (the
|
|
80
|
+
* same information `?_data=1` carries on every SPA navigation), serialized in
|
|
81
|
+
* the `?_route=` query param of the connection. It is not free input — the
|
|
82
|
+
* server validates the keys against the module's path and rejects what the path
|
|
83
|
+
* does not declare.
|
|
84
|
+
*
|
|
71
85
|
* Inert (no connection, no freshness change) in a static build: there is no
|
|
72
86
|
* server to speak SSE.
|
|
73
87
|
*/
|
|
74
|
-
export declare function connectChannel(moduleId: string, channel: string): () => void;
|
|
88
|
+
export declare function connectChannel(moduleId: string, channel: string, routeParams?: Record<string, string> | undefined): () => void;
|
|
89
|
+
/**
|
|
90
|
+
* The params the module's route declares, extracted from a pathname. The
|
|
91
|
+
* module's `fullPath` matches as a **prefix** of the pathname: a layout
|
|
92
|
+
* declared at `/projeto/:id` covers `/projeto/7/sala` and extracts `id=7` —
|
|
93
|
+
* the child's suffix takes no part in the extraction. A path with a catch-all
|
|
94
|
+
* (`/docs/*`) declares no `:` key and yields the empty object, so the channel
|
|
95
|
+
* travels by principal.
|
|
96
|
+
*/
|
|
97
|
+
export declare function channelRouteParams(fullPath: string | undefined, pathname: string): Record<string, string>;
|
|
75
98
|
/**
|
|
76
99
|
* Mounts the live channels of one route module. It renders its children
|
|
77
100
|
* unchanged (no DOM node, so SSR output and hydration are untouched) and only
|
|
78
101
|
* exists to tie the connection lifecycle to the module's own mount/unmount —
|
|
79
102
|
* SPA navigation and page close close the connections.
|
|
103
|
+
*
|
|
104
|
+
* The connection is **keyed by the params the module's route declares**, not
|
|
105
|
+
* by the pathname: navigating between children of the same params
|
|
106
|
+
* (`/projeto/7` → `/projeto/7/sala`) does not reconnect (the child's suffix is
|
|
107
|
+
* not part of the connection's identity), while a move to another resource
|
|
108
|
+
* (`/projeto/7` → `/projeto/9`) closes the old connection and opens a new one —
|
|
109
|
+
* with a fresh snapshot on connect, mirroring the loader store, which refetches
|
|
110
|
+
* when its own route's params move.
|
|
80
111
|
*/
|
|
81
|
-
export declare function ChannelBoundary({ moduleId, channels, children, }: {
|
|
112
|
+
export declare function ChannelBoundary({ moduleId, channels, fullPath, children, }: {
|
|
82
113
|
moduleId?: string | undefined;
|
|
83
114
|
channels?: readonly string[] | undefined;
|
|
115
|
+
/** The module's declared route path (`metadata.fullPath`) — its `:params`. */
|
|
116
|
+
fullPath?: string | undefined;
|
|
84
117
|
children?: ComponentChildren;
|
|
85
118
|
}): ComponentChildren;
|
|
86
119
|
/** Test-only. Drops every connection and resets every freshness signal. */
|
package/dist/live-loader.js
CHANGED
|
@@ -1,33 +1,34 @@
|
|
|
1
|
-
import { signal as
|
|
2
|
-
import { useEffect as
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { signal as v } from "@preact/signals";
|
|
2
|
+
import { useEffect as m } from "preact/hooks";
|
|
3
|
+
import { useLocation as g } from "wouter-preact";
|
|
4
|
+
import { extractParams as _, loaderEntry as S } from "./loader-store.js";
|
|
5
|
+
const L = 6e4, C = "snapshot", w = "slice", f = /* @__PURE__ */ new Set(), p = v("live"), y = /* @__PURE__ */ new Map();
|
|
6
|
+
function R(e) {
|
|
7
|
+
let n = y.get(e);
|
|
8
|
+
return n || (n = v({}), y.set(e, n)), n;
|
|
8
9
|
}
|
|
9
|
-
function
|
|
10
|
-
return
|
|
10
|
+
function V() {
|
|
11
|
+
return p;
|
|
11
12
|
}
|
|
12
|
-
const
|
|
13
|
-
let
|
|
14
|
-
function
|
|
15
|
-
let
|
|
16
|
-
|
|
13
|
+
const u = /* @__PURE__ */ new Map();
|
|
14
|
+
let h = !1;
|
|
15
|
+
function N(e, n) {
|
|
16
|
+
let o = u.get(e);
|
|
17
|
+
o || (o = [], u.set(e, o)), o.push(n), !h && (h = !0, queueMicrotask(E));
|
|
17
18
|
}
|
|
18
|
-
function
|
|
19
|
-
if (
|
|
20
|
-
const e = [...
|
|
21
|
-
|
|
22
|
-
for (const [n,
|
|
23
|
-
const
|
|
24
|
-
let
|
|
25
|
-
for (const
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
function E() {
|
|
20
|
+
if (h = !1, u.size === 0) return;
|
|
21
|
+
const e = [...u];
|
|
22
|
+
u.clear();
|
|
23
|
+
for (const [n, o] of e) {
|
|
24
|
+
const s = S(n);
|
|
25
|
+
let t = s.value;
|
|
26
|
+
for (const l of o)
|
|
27
|
+
t = { ...t !== null && typeof t == "object" && !Array.isArray(t) ? t : {}, ...l };
|
|
28
|
+
s.value = t;
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
|
-
function
|
|
31
|
+
function T(e) {
|
|
31
32
|
if (typeof e != "string" || e.length === 0) return null;
|
|
32
33
|
try {
|
|
33
34
|
const n = JSON.parse(e);
|
|
@@ -36,96 +37,105 @@ function L(e) {
|
|
|
36
37
|
return null;
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
|
-
function
|
|
40
|
+
function O(e) {
|
|
40
41
|
return e.some((n) => n.failed) ? "error" : e.some((n) => n.state === "open" && !n.silent) ? "live" : "stale";
|
|
41
42
|
}
|
|
42
|
-
function
|
|
43
|
+
function a() {
|
|
43
44
|
let e = !1, n = !1;
|
|
44
|
-
for (const
|
|
45
|
-
|
|
46
|
-
e ?
|
|
47
|
-
for (const [
|
|
48
|
-
const
|
|
49
|
-
for (const
|
|
50
|
-
if (
|
|
51
|
-
const
|
|
52
|
-
|
|
45
|
+
for (const o of f)
|
|
46
|
+
o.failed && (e = !0), o.state === "open" && !o.silent && (n = !0);
|
|
47
|
+
e ? p.value = "error" : f.size > 0 && !n ? p.value = "stale" : p.value = "live";
|
|
48
|
+
for (const [o, s] of y) {
|
|
49
|
+
const t = /* @__PURE__ */ new Map();
|
|
50
|
+
for (const i of f) {
|
|
51
|
+
if (i.moduleId !== o) continue;
|
|
52
|
+
const r = t.get(i.channel);
|
|
53
|
+
r ? r.push(i) : t.set(i.channel, [i]);
|
|
53
54
|
}
|
|
54
|
-
const
|
|
55
|
-
for (const [
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
const l = {};
|
|
56
|
+
for (const [i, r] of t)
|
|
57
|
+
l[i] = O(r);
|
|
58
|
+
s.value = l;
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
|
-
function
|
|
61
|
+
function A(e, n, o) {
|
|
61
62
|
if (typeof __VELO_STATIC__ < "u" && __VELO_STATIC__) return () => {
|
|
62
63
|
};
|
|
63
64
|
if (typeof window > "u" || typeof EventSource > "u")
|
|
64
65
|
return () => {
|
|
65
66
|
};
|
|
66
|
-
const
|
|
67
|
+
const s = {
|
|
67
68
|
state: "open",
|
|
68
69
|
failed: !1,
|
|
69
70
|
silent: !1,
|
|
70
71
|
moduleId: e,
|
|
71
72
|
channel: n
|
|
72
73
|
};
|
|
73
|
-
|
|
74
|
-
let
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
},
|
|
74
|
+
f.add(s), a();
|
|
75
|
+
let t = null;
|
|
76
|
+
const l = () => {
|
|
77
|
+
t !== null && clearTimeout(t), t = setTimeout(() => {
|
|
78
|
+
t = null, s.silent = !0, a();
|
|
79
|
+
}, L);
|
|
79
80
|
};
|
|
80
|
-
|
|
81
|
-
const o = new EventSource(
|
|
82
|
-
`/_channel/${encodeURI(e)}/${encodeURIComponent(n)}`
|
|
81
|
+
l();
|
|
82
|
+
const i = o && Object.keys(o).length > 0 ? `?_route=${encodeURIComponent(JSON.stringify(o))}` : "", r = new EventSource(
|
|
83
|
+
`/_channel/${encodeURI(e)}/${encodeURIComponent(n)}${i}`
|
|
83
84
|
);
|
|
84
|
-
return
|
|
85
|
+
return r.addEventListener(C, (c) => {
|
|
85
86
|
try {
|
|
86
|
-
|
|
87
|
+
E(), S(e).value = JSON.parse(c.data), s.state = "open", s.failed = !1, s.silent = !1, l(), a();
|
|
87
88
|
} catch {
|
|
88
89
|
}
|
|
89
|
-
}),
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
}),
|
|
93
|
-
const
|
|
94
|
-
typeof
|
|
95
|
-
}),
|
|
96
|
-
|
|
90
|
+
}), r.addEventListener(w, (c) => {
|
|
91
|
+
const d = T(c.data);
|
|
92
|
+
d && (s.state = "open", s.failed = !1, s.silent = !1, l(), a(), N(e, d));
|
|
93
|
+
}), r.addEventListener("error", (c) => {
|
|
94
|
+
const d = c.data;
|
|
95
|
+
typeof d == "string" && d.length > 0 ? s.failed = !0 : s.state = r.readyState === EventSource.CLOSED ? "closed" : "reconnecting", a();
|
|
96
|
+
}), r.addEventListener("close", () => {
|
|
97
|
+
s.state = "closed", a();
|
|
97
98
|
}), () => {
|
|
98
|
-
|
|
99
|
+
f.delete(s), t !== null && clearTimeout(t);
|
|
99
100
|
try {
|
|
100
|
-
|
|
101
|
+
r.close();
|
|
101
102
|
} catch {
|
|
102
103
|
}
|
|
103
|
-
|
|
104
|
+
a();
|
|
104
105
|
};
|
|
105
106
|
}
|
|
106
|
-
function
|
|
107
|
+
function b(e, n) {
|
|
108
|
+
return e ? _(e, n) ?? {} : {};
|
|
109
|
+
}
|
|
110
|
+
function $({
|
|
107
111
|
moduleId: e,
|
|
108
112
|
channels: n,
|
|
109
|
-
|
|
113
|
+
fullPath: o,
|
|
114
|
+
children: s
|
|
110
115
|
}) {
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
116
|
+
const t = n && n.length > 0 ? n.join(",") : "";
|
|
117
|
+
g();
|
|
118
|
+
const l = t === "" || typeof window > "u" ? "" : JSON.stringify(b(o, window.location.pathname));
|
|
119
|
+
return m(() => {
|
|
120
|
+
if (!e || t === "") return;
|
|
121
|
+
const i = l === "" ? void 0 : JSON.parse(l), r = t.split(",").map(
|
|
122
|
+
(c) => A(e, c, i)
|
|
123
|
+
);
|
|
115
124
|
return () => {
|
|
116
|
-
for (const
|
|
125
|
+
for (const c of r) c();
|
|
117
126
|
};
|
|
118
|
-
}, [e,
|
|
127
|
+
}, [e, t, l]), s;
|
|
119
128
|
}
|
|
120
|
-
function
|
|
121
|
-
|
|
122
|
-
for (const e of
|
|
129
|
+
function k() {
|
|
130
|
+
f.clear(), u.clear(), h = !1, p.value = "live";
|
|
131
|
+
for (const e of y.values()) e.value = {};
|
|
123
132
|
}
|
|
124
133
|
export {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
134
|
+
L as CHANNEL_SILENCE_MS,
|
|
135
|
+
$ as ChannelBoundary,
|
|
136
|
+
k as __resetLiveLoader,
|
|
137
|
+
b as channelRouteParams,
|
|
138
|
+
A as connectChannel,
|
|
139
|
+
V as loaderFreshness,
|
|
140
|
+
R as moduleChannelFreshness
|
|
131
141
|
};
|
package/dist/server.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Hono } from "hono";
|
|
|
2
2
|
import type { AppRoutes } from "./types.js";
|
|
3
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
4
|
export { emit, inspectChannels, registerChannels } from "./channels.js";
|
|
5
|
-
export type { ChannelDefinition, ChannelMap, ChannelScopeResult, ChannelRegistryOptions, ChannelEmitModule, ChannelEmitKind, ChannelSlice, ChannelInspectGroup, ChannelInspectChannel, ChannelInspectWindow, ChannelInspectReport, } from "./channels.js";
|
|
5
|
+
export type { ChannelDefinition, ChannelMap, ChannelScopeContext, ChannelGroupOpenContext, ChannelGroupCloseContext, ChannelScopeResult, ChannelRegistryOptions, ChannelEmitModule, ChannelEmitKind, ChannelSlice, ChannelInspectGroup, ChannelInspectChannel, ChannelInspectWindow, ChannelInspectReport, } from "./channels.js";
|
|
6
6
|
export declare const serverDataStorage: AsyncLocalStorage<Record<string, unknown>>;
|
|
7
7
|
type ServerCallback = (server: import("http").Server) => void;
|
|
8
8
|
export declare function onServer(fn: ServerCallback): void;
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -21,4 +21,4 @@
|
|
|
21
21
|
* snapshot — no sleep, no retry.
|
|
22
22
|
*/
|
|
23
23
|
export { createTestApp } from "./createTestApp.js";
|
|
24
|
-
export type { TestApp, TestResponse, TestSubscription, TestChannelSubscription, ChannelEvent, ChannelModuleRef, CreateTestAppOptions, RequestOptions, BodyRequestOptions, LoaderRequestOptions, SubscribeOptions, NextOptions, MockContextOptions, Cookies, Headers, Query, Params, } from "./types.js";
|
|
24
|
+
export type { TestApp, TestResponse, TestSubscription, TestChannelSubscription, ChannelEvent, ChannelModuleRef, CreateTestAppOptions, RequestOptions, BodyRequestOptions, LoaderRequestOptions, SubscribeOptions, ChannelSubscribeOptions, NextOptions, MockContextOptions, Cookies, Headers, Query, Params, } from "./types.js";
|