@mauroandre/velojs 0.0.24 → 0.0.26
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 +41 -0
- package/dist/client.js +43 -16
- package/dist/events.js +110 -108
- package/dist/index.d.ts +1 -1
- package/dist/server.js +175 -118
- package/dist/static.js +9 -8
- package/dist/testing/index.js +84 -84
- package/dist/types.d.ts +10 -1
- package/dist/vite.d.ts +10 -0
- package/dist/vite.js +313 -236
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -534,6 +534,47 @@ The client-side transform rewrites the import to fetch `/_action/admin/Layout/lo
|
|
|
534
534
|
|
|
535
535
|
---
|
|
536
536
|
|
|
537
|
+
## Endpoints
|
|
538
|
+
|
|
539
|
+
Declarative HTTP endpoints for anything that isn't a page — webhooks, email-verification redirects, OAuth callbacks, health checks.
|
|
540
|
+
|
|
541
|
+
Declare them directly in `routes.tsx`:
|
|
542
|
+
|
|
543
|
+
```tsx
|
|
544
|
+
import type { AppRoutes, EndpointHandler } from "@mauroandre/velojs";
|
|
545
|
+
import * as Home from "./pages/Home.js";
|
|
546
|
+
import { githubWebhook } from "./webhooks/github.handler.js";
|
|
547
|
+
|
|
548
|
+
export default [
|
|
549
|
+
{ path: "/", module: Home },
|
|
550
|
+
{ path: "/api/github/webhook", method: "POST", handler: githubWebhook },
|
|
551
|
+
] satisfies AppRoutes;
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
The handler gets `{ c, params, query }` and must return a `Response`:
|
|
555
|
+
|
|
556
|
+
```ts
|
|
557
|
+
import type { EndpointHandler } from "@mauroandre/velojs";
|
|
558
|
+
|
|
559
|
+
export const githubWebhook: EndpointHandler = async ({ c }) => {
|
|
560
|
+
const body = await c.req.json();
|
|
561
|
+
// ...verify signature, process event...
|
|
562
|
+
return c.json({ ok: true });
|
|
563
|
+
};
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
Endpoints inherit parent `middlewares` and can be nested inside grouping nodes. The Vite plugin strips endpoint objects (and their handler imports) from the client bundle, so server-only code never ships to the browser.
|
|
567
|
+
|
|
568
|
+
Because endpoints live in `routes.tsx`, the testing toolkit sees them automatically:
|
|
569
|
+
|
|
570
|
+
```ts
|
|
571
|
+
const res = await app.post("/api/github/webhook", { body: {...}, headers: {...} });
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
Full documentation: [site/docs/17-endpoints.md](./site/docs/17-endpoints.md).
|
|
575
|
+
|
|
576
|
+
---
|
|
577
|
+
|
|
537
578
|
## Event Streams
|
|
538
579
|
|
|
539
580
|
Push real-time updates from server to client via Server-Sent Events (SSE). Live progress, notifications, metrics, log streaming, AI tokens — anything server → client.
|
package/dist/client.js
CHANGED
|
@@ -1,19 +1,46 @@
|
|
|
1
|
-
import { jsx as
|
|
2
|
-
import { hydrate as
|
|
3
|
-
import { Router as
|
|
4
|
-
const u = (
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
import { jsx as e } from "preact/jsx-runtime";
|
|
2
|
+
import { hydrate as p } from "preact";
|
|
3
|
+
import { Router as f, Route as l, Switch as s } from "wouter-preact";
|
|
4
|
+
const u = (r, i = !1) => {
|
|
5
|
+
const o = [];
|
|
6
|
+
for (let n = 0; n < r.length; n++) {
|
|
7
|
+
const t = r[n];
|
|
8
|
+
if (!t.module) {
|
|
9
|
+
if (t.children)
|
|
10
|
+
for (const d of u(t.children, i))
|
|
11
|
+
o.push(d);
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
const c = t.module.Component;
|
|
15
|
+
if (!t.children) {
|
|
16
|
+
o.push(
|
|
17
|
+
/* @__PURE__ */ e(l, { path: t.path || "", children: /* @__PURE__ */ e(c, {}) }, n)
|
|
18
|
+
);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const h = u(t.children, !!t.path);
|
|
22
|
+
if (t.isRoot) {
|
|
23
|
+
o.push(/* @__PURE__ */ e(s, { children: h }, n));
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (!t.path) {
|
|
27
|
+
o.push(
|
|
28
|
+
/* @__PURE__ */ e(c, { children: /* @__PURE__ */ e(s, { children: h }) }, n)
|
|
29
|
+
);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
o.push(
|
|
33
|
+
/* @__PURE__ */ e(l, { path: t.path, nest: !0, children: /* @__PURE__ */ e(c, { children: /* @__PURE__ */ e(s, { children: h }) }) }, n)
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return o;
|
|
37
|
+
}, m = ({ routes: r }) => {
|
|
38
|
+
const i = u(r);
|
|
39
|
+
return /* @__PURE__ */ e(f, { children: i });
|
|
40
|
+
}, C = (r) => {
|
|
41
|
+
const { routes: i } = r, o = document.querySelector("body");
|
|
42
|
+
o && p(/* @__PURE__ */ e(m, { routes: i }), o);
|
|
16
43
|
};
|
|
17
44
|
export {
|
|
18
|
-
|
|
45
|
+
C as startClient
|
|
19
46
|
};
|
package/dist/events.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { getAppContext as A } from "./app-context.js";
|
|
2
|
-
const M = 2e4, R = 300 * 1e3,
|
|
2
|
+
const M = 2e4, R = 300 * 1e3, m = "";
|
|
3
3
|
function x(t) {
|
|
4
|
-
const
|
|
5
|
-
for (const
|
|
4
|
+
const n = A().pendingStreamRoutes.splice(0);
|
|
5
|
+
for (const l of n) l(t);
|
|
6
6
|
}
|
|
7
7
|
const I = /* @__PURE__ */ new Set();
|
|
8
8
|
function y() {
|
|
@@ -15,116 +15,116 @@ function L(t = {}) {
|
|
|
15
15
|
throw new Error(
|
|
16
16
|
"[velojs] createEventStream: `source` and `perChannelSource` are mutually exclusive. Use `source` for stream-wide producers or `perChannelSource` for per-channel resources, not both."
|
|
17
17
|
);
|
|
18
|
-
const a = /* @__PURE__ */ new Map(),
|
|
19
|
-
let
|
|
18
|
+
const a = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map(), l = /* @__PURE__ */ new Set(), h = /* @__PURE__ */ new Map();
|
|
19
|
+
let c = null, w = 0;
|
|
20
20
|
const i = /* @__PURE__ */ new Map(), b = () => {
|
|
21
|
-
t.source && (
|
|
22
|
-
t.source(
|
|
21
|
+
t.source && (c || (c = new AbortController(), Promise.resolve(
|
|
22
|
+
t.source(_, { abortSignal: c.signal })
|
|
23
23
|
).catch((e) => {
|
|
24
24
|
console.error("[velojs] event stream source threw:", e);
|
|
25
25
|
})));
|
|
26
|
-
},
|
|
27
|
-
w > 0 ||
|
|
28
|
-
},
|
|
26
|
+
}, g = () => {
|
|
27
|
+
w > 0 || c && (c.abort(), c = null);
|
|
28
|
+
}, d = (e) => {
|
|
29
29
|
if (!t.perChannelSource || i.has(e)) return;
|
|
30
|
-
const
|
|
31
|
-
i.set(e,
|
|
32
|
-
const
|
|
33
|
-
|
|
30
|
+
const r = new AbortController();
|
|
31
|
+
i.set(e, r);
|
|
32
|
+
const o = (s, f) => {
|
|
33
|
+
p.emit(e, s, f);
|
|
34
34
|
};
|
|
35
35
|
Promise.resolve(
|
|
36
|
-
t.perChannelSource(e,
|
|
37
|
-
).catch((
|
|
36
|
+
t.perChannelSource(e, o, { abortSignal: r.signal })
|
|
37
|
+
).catch((s) => {
|
|
38
38
|
console.error(
|
|
39
39
|
`[velojs] perChannelSource threw for channel "${e}":`,
|
|
40
|
-
|
|
40
|
+
s
|
|
41
41
|
);
|
|
42
42
|
});
|
|
43
|
-
},
|
|
44
|
-
const
|
|
45
|
-
|
|
43
|
+
}, S = (e) => {
|
|
44
|
+
const r = i.get(e);
|
|
45
|
+
r && (r.abort(), i.delete(e));
|
|
46
46
|
};
|
|
47
|
-
function
|
|
48
|
-
let
|
|
49
|
-
if (!!t.broadcast ? (
|
|
47
|
+
function _(e, r, o) {
|
|
48
|
+
let s, f, u;
|
|
49
|
+
if (!!t.broadcast ? (s = m, f = e, u = r) : (s = e, f = r, u = o), l.has(s)) {
|
|
50
50
|
console.warn(
|
|
51
|
-
`[velojs] emit() called on closed channel "${
|
|
51
|
+
`[velojs] emit() called on closed channel "${s}" — ignored. Reopen the channel by emitting to a fresh ID, or remove the close() call.`
|
|
52
52
|
);
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
|
-
if (
|
|
56
|
-
const
|
|
57
|
-
|
|
55
|
+
if (u?.snapshot) {
|
|
56
|
+
const v = n.get(s) ?? [];
|
|
57
|
+
v.push(f);
|
|
58
58
|
const T = t.bufferSize ?? 1 / 0;
|
|
59
|
-
|
|
59
|
+
v.length > T && v.splice(0, v.length - T), n.set(s, v);
|
|
60
60
|
}
|
|
61
|
-
const
|
|
62
|
-
if (
|
|
63
|
-
for (const
|
|
61
|
+
const E = a.get(s);
|
|
62
|
+
if (E)
|
|
63
|
+
for (const v of E) v(f);
|
|
64
64
|
}
|
|
65
|
-
function
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
if (
|
|
70
|
-
for (const
|
|
71
|
-
const
|
|
72
|
-
|
|
65
|
+
function C(e = m) {
|
|
66
|
+
if (l.has(e)) return;
|
|
67
|
+
l.add(e);
|
|
68
|
+
const r = a.get(e);
|
|
69
|
+
if (r)
|
|
70
|
+
for (const f of r) f.close();
|
|
71
|
+
const o = t.retainMs ?? R, s = h.get(e);
|
|
72
|
+
s && clearTimeout(s), h.set(
|
|
73
73
|
e,
|
|
74
74
|
setTimeout(() => {
|
|
75
|
-
|
|
76
|
-
},
|
|
75
|
+
n.delete(e), h.delete(e), l.delete(e);
|
|
76
|
+
}, o)
|
|
77
77
|
);
|
|
78
78
|
}
|
|
79
|
-
const
|
|
80
|
-
emit:
|
|
81
|
-
close:
|
|
79
|
+
const p = {
|
|
80
|
+
emit: _,
|
|
81
|
+
close: C,
|
|
82
82
|
__isVeloEventStream: !0,
|
|
83
83
|
__config: t,
|
|
84
84
|
__listeners: a,
|
|
85
|
-
__buffers:
|
|
85
|
+
__buffers: n,
|
|
86
86
|
__path: t.path,
|
|
87
|
-
__onConnect(e,
|
|
88
|
-
let
|
|
89
|
-
const
|
|
90
|
-
|
|
87
|
+
__onConnect(e, r) {
|
|
88
|
+
let o = a.get(e);
|
|
89
|
+
const s = !o || o.size === 0;
|
|
90
|
+
o || (o = /* @__PURE__ */ new Set(), a.set(e, o)), o.add(r), w++, b(), s && d(e);
|
|
91
91
|
},
|
|
92
|
-
__onDisconnect(e,
|
|
93
|
-
const
|
|
94
|
-
|
|
92
|
+
__onDisconnect(e, r) {
|
|
93
|
+
const o = a.get(e);
|
|
94
|
+
o && (o.delete(r), o.size === 0 && (a.delete(e), S(e)), w--, g());
|
|
95
95
|
},
|
|
96
96
|
__reset() {
|
|
97
97
|
for (const e of a.values())
|
|
98
|
-
for (const
|
|
98
|
+
for (const r of e)
|
|
99
99
|
try {
|
|
100
|
-
|
|
100
|
+
r.close();
|
|
101
101
|
} catch {
|
|
102
102
|
}
|
|
103
|
-
a.clear(),
|
|
104
|
-
for (const e of
|
|
105
|
-
|
|
103
|
+
a.clear(), n.clear(), l.clear();
|
|
104
|
+
for (const e of h.values()) clearTimeout(e);
|
|
105
|
+
h.clear(), c && (c.abort(), c = null);
|
|
106
106
|
for (const e of i.values()) e.abort();
|
|
107
107
|
i.clear(), w = 0;
|
|
108
108
|
}
|
|
109
109
|
};
|
|
110
110
|
return t.path && A().pendingStreamRoutes.push((e) => {
|
|
111
|
-
|
|
112
|
-
}), I.add(
|
|
111
|
+
P(e, t.path, p, t.middlewares ?? []);
|
|
112
|
+
}), I.add(p), p;
|
|
113
113
|
}
|
|
114
114
|
function B(t) {
|
|
115
|
-
return async (a, { abortSignal:
|
|
116
|
-
for (; !
|
|
115
|
+
return async (a, { abortSignal: n }) => {
|
|
116
|
+
for (; !n.aborted; ) {
|
|
117
117
|
try {
|
|
118
118
|
await t.tick(a);
|
|
119
|
-
} catch (
|
|
120
|
-
console.error("[velojs] poll tick threw:",
|
|
119
|
+
} catch (l) {
|
|
120
|
+
console.error("[velojs] poll tick threw:", l);
|
|
121
121
|
}
|
|
122
|
-
await new Promise((
|
|
123
|
-
const
|
|
124
|
-
|
|
122
|
+
await new Promise((l) => {
|
|
123
|
+
const h = setTimeout(l, t.intervalMs);
|
|
124
|
+
n.addEventListener(
|
|
125
125
|
"abort",
|
|
126
126
|
() => {
|
|
127
|
-
clearTimeout(
|
|
127
|
+
clearTimeout(h), l();
|
|
128
128
|
},
|
|
129
129
|
{ once: !0 }
|
|
130
130
|
);
|
|
@@ -133,67 +133,69 @@ function B(t) {
|
|
|
133
133
|
};
|
|
134
134
|
}
|
|
135
135
|
function j(t) {
|
|
136
|
-
return t.req.query("channel") ??
|
|
136
|
+
return t.req.query("channel") ?? m;
|
|
137
137
|
}
|
|
138
|
-
function
|
|
139
|
-
const
|
|
138
|
+
function P(t, a, n, l = []) {
|
|
139
|
+
const h = async (c) => {
|
|
140
140
|
const { streamSSE: w } = await import("hono/streaming");
|
|
141
141
|
let i;
|
|
142
|
-
if (
|
|
143
|
-
i =
|
|
142
|
+
if (n.__config.broadcast)
|
|
143
|
+
i = m;
|
|
144
144
|
else {
|
|
145
|
-
const
|
|
146
|
-
let
|
|
145
|
+
const d = n.__config.channel ?? j;
|
|
146
|
+
let S;
|
|
147
147
|
try {
|
|
148
|
-
|
|
149
|
-
} catch (
|
|
150
|
-
return console.error("[velojs] channel resolver threw:",
|
|
148
|
+
S = await d(c);
|
|
149
|
+
} catch (_) {
|
|
150
|
+
return console.error("[velojs] channel resolver threw:", _), c.json({ error: "internal" }, 500);
|
|
151
151
|
}
|
|
152
|
-
if (
|
|
153
|
-
return
|
|
154
|
-
i =
|
|
152
|
+
if (S == null)
|
|
153
|
+
return c.json({ error: "forbidden" }, 403);
|
|
154
|
+
i = S;
|
|
155
155
|
}
|
|
156
|
-
const b =
|
|
157
|
-
return w(
|
|
158
|
-
const
|
|
159
|
-
if (
|
|
156
|
+
const b = n.__config.heartbeatMs, g = b === !1 ? 0 : b ?? M;
|
|
157
|
+
return w(c, async (d) => {
|
|
158
|
+
const S = n.__buffers.get(i);
|
|
159
|
+
if (S && S.length > 0 && await d.writeSSE({
|
|
160
160
|
event: "snapshot",
|
|
161
|
-
data: JSON.stringify(
|
|
162
|
-
}),
|
|
163
|
-
const
|
|
164
|
-
i ===
|
|
161
|
+
data: JSON.stringify(S)
|
|
162
|
+
}), n.__config.snapshot) {
|
|
163
|
+
const u = await n.__config.snapshot(
|
|
164
|
+
i === m ? void 0 : i
|
|
165
165
|
);
|
|
166
|
-
|
|
166
|
+
u !== void 0 && await d.writeSSE({
|
|
167
167
|
event: "snapshot",
|
|
168
|
-
data: JSON.stringify(
|
|
168
|
+
data: JSON.stringify(u)
|
|
169
169
|
});
|
|
170
170
|
}
|
|
171
|
-
let
|
|
172
|
-
const
|
|
173
|
-
|
|
171
|
+
let _;
|
|
172
|
+
const C = new Promise((u) => {
|
|
173
|
+
_ = u;
|
|
174
174
|
});
|
|
175
|
-
let
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}),
|
|
175
|
+
let p = !1, e = null, r = Promise.resolve();
|
|
176
|
+
const o = (u) => (r = r.then(() => d.writeSSE(u)).catch(() => {
|
|
177
|
+
}), r), s = () => {
|
|
178
|
+
p || (p = !0, e && clearInterval(e), n.__onDisconnect(i, f), _());
|
|
179
|
+
}, f = ((u) => {
|
|
180
|
+
p || (o({ data: JSON.stringify(u) }), n.__config.closeOn?.(u) && r.then(() => {
|
|
181
|
+
d.close(), s();
|
|
182
|
+
}));
|
|
181
183
|
});
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
});
|
|
188
|
-
},
|
|
184
|
+
f.close = () => {
|
|
185
|
+
p || (o({ event: "close", data: "" }), r.then(() => {
|
|
186
|
+
d.close(), s();
|
|
187
|
+
}));
|
|
188
|
+
}, n.__onConnect(i, f), e = g > 0 ? setInterval(() => {
|
|
189
|
+
p || o({ event: "heartbeat", data: "" });
|
|
190
|
+
}, g) : null, d.onAbort(s), await C;
|
|
189
191
|
});
|
|
190
192
|
};
|
|
191
|
-
|
|
193
|
+
l.length > 0 ? t.on(["GET"], [a], ...l, h) : t.on(["GET"], [a], h);
|
|
192
194
|
}
|
|
193
195
|
export {
|
|
194
196
|
L as createEventStream,
|
|
195
197
|
x as flushPendingStreamRoutes,
|
|
196
198
|
y as getRegisteredStreams,
|
|
197
199
|
B as poll,
|
|
198
|
-
|
|
200
|
+
P as registerStreamHandler
|
|
199
201
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { AppRoutes, RouteNode, RouteModule, LoaderArgs, ActionArgs, Metadata, } from "./types.js";
|
|
1
|
+
export type { AppRoutes, RouteNode, RouteModule, LoaderArgs, ActionArgs, Metadata, HTTPMethod, EndpointHandler, EndpointHandlerArgs, } from "./types.js";
|
|
2
2
|
export type { VeloConfig } from "./config.js";
|
|
3
3
|
export { defineConfig } from "./config.js";
|
|
4
4
|
export { Scripts, Link } from "./components.js";
|