@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 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 r } from "preact/jsx-runtime";
2
- import { hydrate as l } from "preact";
3
- import { Router as p, Route as s, Switch as h } from "wouter-preact";
4
- const u = (e, o = !1) => e.map((t, n) => {
5
- const c = t.module.Component;
6
- if (!t.children)
7
- return /* @__PURE__ */ r(s, { path: t.path || "", children: /* @__PURE__ */ r(c, {}) }, n);
8
- const i = u(t.children, !!t.path);
9
- return t.isRoot ? /* @__PURE__ */ r(h, { children: i }, n) : t.path ? /* @__PURE__ */ r(s, { path: t.path, nest: !0, children: /* @__PURE__ */ r(c, { children: /* @__PURE__ */ r(h, { children: i }) }) }, n) : /* @__PURE__ */ r(c, { children: /* @__PURE__ */ r(h, { children: i }) }, n);
10
- }), m = ({ routes: e }) => {
11
- const o = u(e);
12
- return /* @__PURE__ */ r(p, { children: o });
13
- }, R = (e) => {
14
- const { routes: o } = e, t = document.querySelector("body");
15
- t && l(/* @__PURE__ */ r(m, { routes: o }), t);
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
- R as startClient
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, v = "";
2
+ const M = 2e4, R = 300 * 1e3, m = "";
3
3
  function x(t) {
4
- const o = A().pendingStreamRoutes.splice(0);
5
- for (const c of o) c(t);
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(), o = /* @__PURE__ */ new Map(), c = /* @__PURE__ */ new Set(), f = /* @__PURE__ */ new Map();
19
- let l = null, w = 0;
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 && (l || (l = new AbortController(), Promise.resolve(
22
- t.source(p, { abortSignal: l.signal })
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
- }, m = () => {
27
- w > 0 || l && (l.abort(), l = null);
28
- }, u = (e) => {
26
+ }, g = () => {
27
+ w > 0 || c && (c.abort(), c = null);
28
+ }, d = (e) => {
29
29
  if (!t.perChannelSource || i.has(e)) return;
30
- const n = new AbortController();
31
- i.set(e, n);
32
- const s = (r, S) => {
33
- h.emit(e, r, S);
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, s, { abortSignal: n.signal })
37
- ).catch((r) => {
36
+ t.perChannelSource(e, o, { abortSignal: r.signal })
37
+ ).catch((s) => {
38
38
  console.error(
39
39
  `[velojs] perChannelSource threw for channel "${e}":`,
40
- r
40
+ s
41
41
  );
42
42
  });
43
- }, d = (e) => {
44
- const n = i.get(e);
45
- n && (n.abort(), i.delete(e));
43
+ }, S = (e) => {
44
+ const r = i.get(e);
45
+ r && (r.abort(), i.delete(e));
46
46
  };
47
- function p(e, n, s) {
48
- let r, S, E;
49
- if (!!t.broadcast ? (r = v, S = e, E = n) : (r = e, S = n, E = s), c.has(r)) {
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 "${r}" — ignored. Reopen the channel by emitting to a fresh ID, or remove the close() call.`
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 (E?.snapshot) {
56
- const _ = o.get(r) ?? [];
57
- _.push(S);
55
+ if (u?.snapshot) {
56
+ const v = n.get(s) ?? [];
57
+ v.push(f);
58
58
  const T = t.bufferSize ?? 1 / 0;
59
- _.length > T && _.splice(0, _.length - T), o.set(r, _);
59
+ v.length > T && v.splice(0, v.length - T), n.set(s, v);
60
60
  }
61
- const C = a.get(r);
62
- if (C)
63
- for (const _ of C) _(S);
61
+ const E = a.get(s);
62
+ if (E)
63
+ for (const v of E) v(f);
64
64
  }
65
- function g(e = v) {
66
- if (c.has(e)) return;
67
- c.add(e);
68
- const n = a.get(e);
69
- if (n)
70
- for (const S of n) S.close();
71
- const s = t.retainMs ?? R, r = f.get(e);
72
- r && clearTimeout(r), f.set(
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
- o.delete(e), f.delete(e), c.delete(e);
76
- }, s)
75
+ n.delete(e), h.delete(e), l.delete(e);
76
+ }, o)
77
77
  );
78
78
  }
79
- const h = {
80
- emit: p,
81
- close: g,
79
+ const p = {
80
+ emit: _,
81
+ close: C,
82
82
  __isVeloEventStream: !0,
83
83
  __config: t,
84
84
  __listeners: a,
85
- __buffers: o,
85
+ __buffers: n,
86
86
  __path: t.path,
87
- __onConnect(e, n) {
88
- let s = a.get(e);
89
- const r = !s || s.size === 0;
90
- s || (s = /* @__PURE__ */ new Set(), a.set(e, s)), s.add(n), w++, b(), r && u(e);
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, n) {
93
- const s = a.get(e);
94
- s && (s.delete(n), s.size === 0 && (a.delete(e), d(e)), w--, m());
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 n of e)
98
+ for (const r of e)
99
99
  try {
100
- n.close();
100
+ r.close();
101
101
  } catch {
102
102
  }
103
- a.clear(), o.clear(), c.clear();
104
- for (const e of f.values()) clearTimeout(e);
105
- f.clear(), l && (l.abort(), l = null);
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
- D(e, t.path, h, t.middlewares ?? []);
112
- }), I.add(h), h;
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: o }) => {
116
- for (; !o.aborted; ) {
115
+ return async (a, { abortSignal: n }) => {
116
+ for (; !n.aborted; ) {
117
117
  try {
118
118
  await t.tick(a);
119
- } catch (c) {
120
- console.error("[velojs] poll tick threw:", c);
119
+ } catch (l) {
120
+ console.error("[velojs] poll tick threw:", l);
121
121
  }
122
- await new Promise((c) => {
123
- const f = setTimeout(c, t.intervalMs);
124
- o.addEventListener(
122
+ await new Promise((l) => {
123
+ const h = setTimeout(l, t.intervalMs);
124
+ n.addEventListener(
125
125
  "abort",
126
126
  () => {
127
- clearTimeout(f), c();
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") ?? v;
136
+ return t.req.query("channel") ?? m;
137
137
  }
138
- function D(t, a, o, c = []) {
139
- const f = async (l) => {
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 (o.__config.broadcast)
143
- i = v;
142
+ if (n.__config.broadcast)
143
+ i = m;
144
144
  else {
145
- const u = o.__config.channel ?? j;
146
- let d;
145
+ const d = n.__config.channel ?? j;
146
+ let S;
147
147
  try {
148
- d = await u(l);
149
- } catch (p) {
150
- return console.error("[velojs] channel resolver threw:", p), l.json({ error: "internal" }, 500);
148
+ S = await d(c);
149
+ } catch (_) {
150
+ return console.error("[velojs] channel resolver threw:", _), c.json({ error: "internal" }, 500);
151
151
  }
152
- if (d == null)
153
- return l.json({ error: "forbidden" }, 403);
154
- i = d;
152
+ if (S == null)
153
+ return c.json({ error: "forbidden" }, 403);
154
+ i = S;
155
155
  }
156
- const b = o.__config.heartbeatMs, m = b === !1 ? 0 : b ?? M;
157
- return w(l, async (u) => {
158
- const d = o.__buffers.get(i);
159
- if (d && d.length > 0 && await u.writeSSE({
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(d)
162
- }), o.__config.snapshot) {
163
- const r = await o.__config.snapshot(
164
- i === v ? void 0 : 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
- r !== void 0 && await u.writeSSE({
166
+ u !== void 0 && await d.writeSSE({
167
167
  event: "snapshot",
168
- data: JSON.stringify(r)
168
+ data: JSON.stringify(u)
169
169
  });
170
170
  }
171
- let p;
172
- const g = new Promise((r) => {
173
- p = r;
171
+ let _;
172
+ const C = new Promise((u) => {
173
+ _ = u;
174
174
  });
175
- let h = !1, e = null;
176
- const n = () => {
177
- h || (h = !0, e && clearInterval(e), o.__onDisconnect(i, s), p());
178
- }, s = ((r) => {
179
- h || (u.writeSSE({ data: JSON.stringify(r) }).catch(() => {
180
- }), o.__config.closeOn?.(r) && (u.close(), n()));
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
- s.close = () => {
183
- h || (u.writeSSE({ event: "close", data: "" }).catch(() => {
184
- }), u.close(), n());
185
- }, o.__onConnect(i, s), e = m > 0 ? setInterval(() => {
186
- h || u.writeSSE({ event: "heartbeat", data: "" }).catch(() => {
187
- });
188
- }, m) : null, u.onAbort(n), await g;
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
- c.length > 0 ? t.on(["GET"], [a], ...c, f) : t.on(["GET"], [a], f);
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
- D as registerStreamHandler
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";