@mauroandre/velojs 0.0.25 → 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/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";
package/dist/server.js CHANGED
@@ -1,200 +1,257 @@
1
- import { jsx as p } from "preact/jsx-runtime";
2
- import { Hono as b } from "hono";
3
- import { logger as q } from "hono/logger";
1
+ import { jsx as g } from "preact/jsx-runtime";
2
+ import { Hono as R } from "hono";
3
+ import { logger as b } from "hono/logger";
4
4
  import { trimTrailingSlash as T } from "hono/trailing-slash";
5
- import { render as A } from "preact-render-to-string";
6
- import { Router as R } from "wouter-preact";
5
+ import { render as j } from "preact-render-to-string";
6
+ import { Router as A } from "wouter-preact";
7
7
  import { AsyncLocalStorage as C } from "node:async_hooks";
8
- import { getAppContext as g } from "./app-context.js";
9
- import { flushPendingStreamRoutes as D, registerStreamHandler as I } from "./events.js";
10
- const v = new C();
11
- globalThis.__veloServerData = v;
8
+ import { getAppContext as v } from "./app-context.js";
9
+ import { flushPendingStreamRoutes as $, registerStreamHandler as k } from "./events.js";
10
+ const y = new C();
11
+ globalThis.__veloServerData = y;
12
12
  let _ = null;
13
- function W(e) {
13
+ function z(e) {
14
14
  if (_) {
15
15
  e(_);
16
16
  return;
17
17
  }
18
- g().serverCallbacks.push(e);
18
+ v().serverCallbacks.push(e);
19
19
  }
20
- function y(e) {
20
+ function S(e) {
21
21
  _ = e;
22
- const t = g();
22
+ const t = v();
23
23
  for (const s of t.serverCallbacks) s(e);
24
24
  t.serverCallbacks.length = 0;
25
25
  }
26
- function J(e) {
27
- g().pendingRoutes.push(e);
26
+ function Q(e) {
27
+ v().pendingRoutes.push(e);
28
28
  }
29
- const N = (e, t, s) => {
29
+ const D = (e, t, s) => {
30
30
  if (e.req.query("_data") === "1") {
31
- const a = globalThis.__veloBuildHash || void 0, r = s ? { ...s, __buildHash: a } : { __buildHash: a };
32
- return e.json(r);
31
+ const r = globalThis.__veloBuildHash || void 0, i = s ? { ...s, __buildHash: r } : { __buildHash: r };
32
+ return e.json(i);
33
33
  }
34
- const n = e.req.path, o = v.run(
34
+ const a = e.req.path, o = y.run(
35
35
  s ?? {},
36
- () => A(/* @__PURE__ */ p(R, { ssrPath: n, children: t }))
36
+ () => j(/* @__PURE__ */ g(A, { ssrPath: a, children: t }))
37
37
  );
38
38
  if (e.header("Cache-Control", "no-cache"), !s)
39
39
  return e.html(o);
40
- const c = `<script>window.__PAGE_DATA__=${JSON.stringify(
40
+ const n = `<script>window.__PAGE_DATA__=${JSON.stringify(
41
41
  s
42
42
  )}<\/script>`;
43
- return e.html(o.replace("</head>", `${c}</head>`));
44
- }, O = async (e, t) => {
45
- const s = t.req.param(), n = t.req.query(), o = { params: s, query: n, c: t }, c = await Promise.all(
46
- e.map(async (r) => {
47
- if (!r.loader) return null;
48
- const d = await r.loader(o), i = r.metadata?.moduleId;
49
- return i ? { moduleId: i, loaderData: d } : null;
43
+ return e.html(o.replace("</head>", `${n}</head>`));
44
+ }, I = async (e, t) => {
45
+ const s = t.req.param(), a = t.req.query(), o = { params: s, query: a, c: t }, n = await Promise.all(
46
+ e.map(async (i) => {
47
+ if (!i.loader) return null;
48
+ const l = await i.loader(o), c = i.metadata?.moduleId;
49
+ return c ? { moduleId: c, loaderData: l } : null;
50
50
  })
51
- ), a = {
51
+ ), r = {
52
52
  __params: s,
53
- __query: n,
53
+ __query: a,
54
54
  __pathname: t.req.path
55
55
  };
56
- for (const r of c)
57
- r && (a[r.moduleId] = r.loaderData);
56
+ for (const i of n)
57
+ i && (r[i.moduleId] = i.loaderData);
58
58
  return {
59
- components: e.map((r) => r.Component),
60
- data: a
59
+ components: e.map((i) => i.Component),
60
+ data: r
61
61
  };
62
- }, j = (e) => {
62
+ }, N = (e) => {
63
63
  if (e.length === 0) return null;
64
64
  const t = e.filter(Boolean);
65
65
  if (t.length === 0) return null;
66
66
  if (t.length === 1) {
67
67
  const o = t[0];
68
- return /* @__PURE__ */ p(o, {});
68
+ return /* @__PURE__ */ g(o, {});
69
69
  }
70
70
  const s = t[t.length - 1];
71
- return t.slice(0, -1).reduceRight((o, c) => /* @__PURE__ */ p(c, { children: o }), /* @__PURE__ */ p(s, {}));
72
- }, w = (e, t, s = [], n = []) => {
71
+ return t.slice(0, -1).reduceRight((o, n) => /* @__PURE__ */ g(n, { children: o }), /* @__PURE__ */ g(s, {}));
72
+ }, w = (e, t, s = [], a = []) => {
73
73
  for (const o of t) {
74
- const c = [...s, o.module], a = [
75
- ...n,
74
+ if (o.handler && !o.module) {
75
+ o.children && w(
76
+ e,
77
+ o.children,
78
+ s,
79
+ [...a, ...o.middlewares || []]
80
+ );
81
+ continue;
82
+ }
83
+ const n = o.module ? [...s, o.module] : s, r = [
84
+ ...a,
76
85
  ...o.middlewares || []
77
86
  ];
78
87
  if (o.children)
79
88
  w(
80
89
  e,
81
90
  o.children,
82
- c,
83
- a
91
+ n,
92
+ r
84
93
  );
85
- else {
86
- const r = o.module.metadata?.fullPath;
87
- if (!r) {
94
+ else if (o.module) {
95
+ const i = o.module.metadata?.fullPath;
96
+ if (!i) {
88
97
  console.warn(
89
98
  `Module ${o.module.metadata?.moduleId} has no fullPath`
90
99
  );
91
100
  continue;
92
101
  }
93
- const d = async (i) => {
94
- const { components: u, data: m } = await O(c, i), l = j(u);
95
- return N(i, l, m);
102
+ const l = async (c) => {
103
+ const { components: u, data: h } = await I(n, c), d = N(u);
104
+ return D(c, d, h);
96
105
  };
97
- a.length > 0 ? e.on(["GET"], [r], ...a, d) : e.on(["GET"], [r], d);
98
- }
106
+ r.length > 0 ? e.on(["GET"], [i], ...r, l) : e.on(["GET"], [i], l);
107
+ } else
108
+ continue;
99
109
  }
100
- }, S = (e, t, s = []) => {
101
- for (const n of t) {
102
- const o = n.module.metadata?.moduleId, c = [
110
+ }, P = (e, t, s = []) => {
111
+ for (const a of t) {
112
+ const o = a.module?.metadata?.moduleId, n = [
103
113
  ...s,
104
- ...n.middlewares || []
114
+ ...a.middlewares || []
105
115
  ];
106
- if (o) {
107
- const a = Object.keys(n.module).filter(
108
- (r) => r.startsWith("action_")
116
+ if (a.module && o) {
117
+ const r = Object.keys(a.module).filter(
118
+ (i) => i.startsWith("action_")
109
119
  );
110
- for (const r of a) {
111
- const d = r.replace("action_", ""), i = n.module[r];
112
- if (typeof i == "function") {
113
- const u = `/_action/${o}/${d}`, m = async (l) => {
114
- let f = {};
120
+ for (const i of r) {
121
+ const l = i.replace("action_", ""), c = a.module[i];
122
+ if (typeof c == "function") {
123
+ const u = `/_action/${o}/${l}`, h = async (d) => {
124
+ let m = {};
115
125
  try {
116
- f = await l.req.json();
126
+ m = await d.req.json();
117
127
  } catch {
118
128
  }
119
- const P = {
120
- body: f,
121
- params: l.req.param(),
122
- query: l.req.query(),
123
- c: l
129
+ const f = {
130
+ body: m,
131
+ params: d.req.param(),
132
+ query: d.req.query(),
133
+ c: d
124
134
  };
125
135
  try {
126
- const h = await i(P);
127
- return l.json(h ?? { ok: !0 });
128
- } catch (h) {
129
- const E = h instanceof Error ? h.message : "Action failed";
130
- return l.json({ error: E }, 500);
136
+ const p = await c(f);
137
+ return d.json(p ?? { ok: !0 });
138
+ } catch (p) {
139
+ const q = p instanceof Error ? p.message : "Action failed";
140
+ return d.json({ error: q }, 500);
131
141
  }
132
142
  };
133
- c.length > 0 ? e.on(
143
+ n.length > 0 ? e.on(
134
144
  ["POST"],
135
145
  [u],
136
- ...c,
137
- m
138
- ) : e.on(["POST"], [u], m);
146
+ ...n,
147
+ h
148
+ ) : e.on(["POST"], [u], h);
139
149
  }
140
150
  }
141
151
  }
142
- n.children && S(e, n.children, c);
152
+ a.children && P(e, a.children, n);
143
153
  }
144
- }, $ = async (e, t, s = []) => {
145
- const n = (o, c) => {
146
- for (const a of o) {
147
- const r = a.module.metadata?.moduleId, d = [
148
- ...c,
149
- ...a.middlewares || []
154
+ }, O = async (e, t, s = []) => {
155
+ const a = (o, n) => {
156
+ for (const r of o) {
157
+ const i = r.module?.metadata?.moduleId, l = [
158
+ ...n,
159
+ ...r.middlewares || []
150
160
  ];
151
- if (r) {
152
- const i = Object.keys(a.module).filter(
161
+ if (r.module && i) {
162
+ const c = Object.keys(r.module).filter(
153
163
  (u) => u.startsWith("stream_")
154
164
  );
155
- for (const u of i) {
156
- const m = u.replace("stream_", ""), l = a.module[u];
157
- if (l?.__isVeloEventStream) {
158
- const f = `/_event/${r}/${m}`;
159
- l.__path = f, I(
165
+ for (const u of c) {
166
+ const h = u.replace("stream_", ""), d = r.module[u];
167
+ if (d?.__isVeloEventStream) {
168
+ const m = `/_event/${i}/${h}`;
169
+ d.__path = m, k(
160
170
  e,
161
- f,
162
- l,
163
- d
171
+ m,
172
+ d,
173
+ l
164
174
  );
165
175
  }
166
176
  }
167
177
  }
168
- a.children && n(a.children, d);
178
+ r.children && a(r.children, l);
169
179
  }
170
180
  };
171
- n(t, s);
172
- }, k = async (e) => {
173
- const t = new b();
174
- t.use(T()), process.env.NODE_ENV !== "production" && t.use("*", q());
175
- const n = g().pendingRoutes.splice(0);
176
- for (const o of n)
177
- await o(t);
178
- if (w(t, e), S(t, e), await $(t, e), D(t), process.env.NODE_ENV !== "production" && !_) {
179
- const o = globalThis.__veloDevServer;
180
- o && y(o);
181
+ a(t, s);
182
+ }, G = (e, t) => t ? t.startsWith("/") ? e + t : e ? e + "/" + t : "/" + t : e || "", H = (e) => {
183
+ const t = /* @__PURE__ */ new Set(), s = (a) => {
184
+ for (const o of a) {
185
+ if (o.module && !o.children) {
186
+ const n = o.module.metadata?.fullPath;
187
+ n && t.add(n);
188
+ }
189
+ o.children && s(o.children);
190
+ }
191
+ };
192
+ return s(e), t;
193
+ }, E = (e, t, s, a = "", o = []) => {
194
+ for (const n of t) {
195
+ const r = G(a, n.path), i = [
196
+ ...o,
197
+ ...n.middlewares || []
198
+ ], l = !!n.handler, c = !!n.method;
199
+ if (l && !c ? console.warn(
200
+ `[velojs] invalid route at "${r || "/"}" — "handler" set without "method"; endpoint skipped`
201
+ ) : c && !l ? console.warn(
202
+ `[velojs] invalid route at "${r || "/"}" — "method" set without "handler"; endpoint skipped`
203
+ ) : l && n.module ? console.warn(
204
+ `[velojs] ambiguous route at "${r || "/"}" — both "module" and "handler" set; endpoint ignored, page kept`
205
+ ) : l && n.isRoot && console.warn(
206
+ `[velojs] "isRoot" has no effect on endpoint at "${r || "/"}"; ignored`
207
+ ), l && c && !n.module) {
208
+ const h = n.method;
209
+ h === "GET" && s.has(r) && console.warn(
210
+ `[velojs] path "${r}" has both a page GET and an endpoint GET; endpoint will win. Consider renaming one.`
211
+ );
212
+ const d = n.handler, m = async (f) => await d({
213
+ c: f,
214
+ params: f.req.param(),
215
+ query: f.req.query()
216
+ });
217
+ i.length > 0 ? e.on([h], [r], ...i, m) : e.on([h], [r], m);
218
+ }
219
+ n.children && E(
220
+ e,
221
+ n.children,
222
+ s,
223
+ r,
224
+ i
225
+ );
226
+ }
227
+ }, x = async (e) => {
228
+ const t = new R();
229
+ t.use(T()), process.env.NODE_ENV !== "production" && t.use("*", b());
230
+ const a = v().pendingRoutes.splice(0);
231
+ for (const n of a)
232
+ await n(t);
233
+ w(t, e), P(t, e), await O(t, e);
234
+ const o = H(e);
235
+ if (E(t, e, o), $(t), process.env.NODE_ENV !== "production" && !_) {
236
+ const n = globalThis.__veloDevServer;
237
+ n && S(n);
181
238
  }
182
239
  return t;
183
- }, z = async (e) => {
184
- const { routes: t, port: s = Number(process.env.SERVER_PORT) || 3e3 } = e, n = await k(t);
240
+ }, X = async (e) => {
241
+ const { routes: t, port: s = Number(process.env.SERVER_PORT) || 3e3 } = e, a = await x(t);
185
242
  if (process.env.NODE_ENV === "production" && !process.env.VELO_STATIC) {
186
- const { serve: o } = await import("@hono/node-server"), { serveStatic: c } = await import("@hono/node-server/serve-static"), { join: a } = await import("node:path"), r = a(process.cwd(), "dist/client");
187
- (process.env.STATIC_BASE_URL || "").startsWith("http") || n.use("/*", c({ root: r })), console.log(`Server running on http://localhost:${s}`);
188
- const i = o({ fetch: n.fetch, port: s });
189
- y(i);
243
+ const { serve: o } = await import("@hono/node-server"), { serveStatic: n } = await import("@hono/node-server/serve-static"), { join: r } = await import("node:path"), i = r(process.cwd(), "dist/client");
244
+ (process.env.STATIC_BASE_URL || "").startsWith("http") || a.use("/*", n({ root: i })), console.log(`Server running on http://localhost:${s}`);
245
+ const c = o({ fetch: a.fetch, port: s });
246
+ S(c);
190
247
  }
191
- return n;
248
+ return a;
192
249
  };
193
250
  export {
194
- J as addRoutes,
195
- k as createApp,
196
- k as default,
197
- W as onServer,
198
- v as serverDataStorage,
199
- z as startServer
251
+ Q as addRoutes,
252
+ x as createApp,
253
+ x as default,
254
+ z as onServer,
255
+ y as serverDataStorage,
256
+ X as startServer
200
257
  };
package/dist/static.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import i from "node:fs";
2
2
  import a from "node:path";
3
- async function h(c, n = "") {
3
+ async function d(c, n = "") {
4
4
  const e = [];
5
5
  for (const t of c) {
6
- const o = t.module.metadata?.fullPath ?? "";
6
+ const o = t.module?.metadata?.fullPath ?? "";
7
7
  if (t.children) {
8
- const s = await h(t.children, o);
8
+ const s = await d(t.children, o);
9
9
  e.push(...s);
10
- } else {
10
+ } else if (t.module) {
11
11
  const s = t.module.metadata?.fullPath;
12
12
  if (!s) continue;
13
13
  const r = Object.keys(t.module).filter((l) => l.startsWith("action_"));
@@ -17,9 +17,9 @@ async function h(c, n = "") {
17
17
  const l = t.module.staticPaths;
18
18
  if (typeof l == "function") {
19
19
  const u = await l();
20
- for (const d of u) {
20
+ for (const h of u) {
21
21
  let f = s;
22
- for (const [p, m] of Object.entries(d))
22
+ for (const [p, m] of Object.entries(h))
23
23
  f = f.replace(`:${p}`, m);
24
24
  e.push({ fullPath: f, module: t.module });
25
25
  }
@@ -29,7 +29,8 @@ async function h(c, n = "") {
29
29
  );
30
30
  } else
31
31
  e.push({ fullPath: s, module: t.module });
32
- }
32
+ } else
33
+ continue;
33
34
  }
34
35
  return e;
35
36
  }
@@ -60,7 +61,7 @@ function w(c) {
60
61
  async function j(c, n) {
61
62
  const e = a.resolve(process.cwd(), "dist");
62
63
  console.log("Generating static pages...");
63
- const t = await h(n);
64
+ const t = await d(n);
64
65
  if (t.length === 0) {
65
66
  console.warn("No routes found for static generation.");
66
67
  return;