@mauroandre/velojs 0.0.36 → 0.0.38
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 +20 -10
- package/agent/AGENTS.md +143 -0
- package/agent/skills/velojs-actions/SKILL.md +192 -0
- package/agent/skills/velojs-components/SKILL.md +99 -0
- package/agent/skills/velojs-endpoints/SKILL.md +187 -0
- package/agent/skills/velojs-event-streams/SKILL.md +705 -0
- package/agent/skills/velojs-getting-started/SKILL.md +191 -0
- package/agent/skills/velojs-hooks/SKILL.md +103 -0
- package/agent/skills/velojs-link-component/SKILL.md +67 -0
- package/agent/skills/velojs-loaders/SKILL.md +134 -0
- package/agent/skills/velojs-middlewares/SKILL.md +143 -0
- package/agent/skills/velojs-production-deploy/SKILL.md +96 -0
- package/agent/skills/velojs-routes/SKILL.md +209 -0
- package/agent/skills/velojs-scripts-component/SKILL.md +62 -0
- package/agent/skills/velojs-server-api/SKILL.md +155 -0
- package/agent/skills/velojs-sockets/SKILL.md +303 -0
- package/agent/skills/velojs-static-generation/SKILL.md +148 -0
- package/agent/skills/velojs-testing/SKILL.md +472 -0
- package/agent/skills/velojs-type-reference/SKILL.md +142 -0
- package/agent/skills/velojs-vite-plugin/SKILL.md +71 -0
- package/dist/client.js +58 -47
- package/dist/components.js +18 -16
- package/dist/hooks.d.ts +3 -2
- package/dist/hooks.js +107 -110
- package/dist/loader-store.d.ts +49 -0
- package/dist/loader-store.js +103 -0
- package/dist/server.js +11 -11
- package/dist/testing/createTestApp.d.ts +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/types.d.ts +7 -3
- package/dist/vite.js +276 -277
- package/package.json +6 -2
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: velojs-endpoints
|
|
3
|
+
description: "Declarative HTTP endpoints in the route tree (`{ method, handler }`) for REST/JSON APIs, webhooks, downloads, health checks — anything returning a `Response` instead of a page. Use when building an API route or receiving a webhook."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Endpoints
|
|
7
|
+
|
|
8
|
+
Endpoints are **declarative HTTP routes** you add to `routes.tsx` alongside your pages. Use them for things a page wouldn't make sense for: webhooks, verification links clicked from email, redirects, health checks, OAuth callbacks.
|
|
9
|
+
|
|
10
|
+
They are registered on the server only — never on the client (wouter). The Vite plugin strips them from the client bundle so server-only handler imports never ship to the browser.
|
|
11
|
+
|
|
12
|
+
## When to use an endpoint (and when not to)
|
|
13
|
+
|
|
14
|
+
- **Use an endpoint** for: webhooks (GitHub, Stripe, banks), email verification links, OAuth redirect callbacks, health checks, short redirects, and any URL not meant to be rendered by a page.
|
|
15
|
+
- **Use a page** (`module: SomePage`) when the URL is visited by a human and the server returns HTML.
|
|
16
|
+
- **Use an action** (`action_*` inside a page module) when logic belongs to a page — form submits, mutations triggered by UI.
|
|
17
|
+
|
|
18
|
+
Rule of thumb: if the URL is part of the UI flow, it's a page or action. If it's an HTTP endpoint hit by something that isn't your frontend, it's an endpoint.
|
|
19
|
+
|
|
20
|
+
## Declaring an endpoint
|
|
21
|
+
|
|
22
|
+
Endpoints live in `routes.tsx` as objects with `path`, `method`, and `handler`:
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import type { AppRoutes } from "@mauroandre/velojs";
|
|
26
|
+
import * as Home from "./pages/Home.js";
|
|
27
|
+
import { githubWebhook } from "./webhooks/github.handler.js";
|
|
28
|
+
|
|
29
|
+
export default [
|
|
30
|
+
{ path: "/", module: Home },
|
|
31
|
+
{ path: "/api/github/webhook", method: "POST", handler: githubWebhook },
|
|
32
|
+
] satisfies AppRoutes;
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
That's it. The endpoint is now registered on the server.
|
|
36
|
+
|
|
37
|
+
## The handler signature
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import type { EndpointHandler } from "@mauroandre/velojs";
|
|
41
|
+
|
|
42
|
+
export const githubWebhook: EndpointHandler = async ({ c, params, query }) => {
|
|
43
|
+
const body = await c.req.json();
|
|
44
|
+
// ...your logic...
|
|
45
|
+
return c.json({ ok: true });
|
|
46
|
+
};
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`EndpointHandlerArgs`:
|
|
50
|
+
|
|
51
|
+
- `c` — the Hono `Context`. Use `c.req.json()` / `c.req.formData()` / `c.req.text()` / `c.req.header(...)` to read the request. Use `c.json(...)`, `c.text(...)`, `c.redirect(...)`, `c.html(...)` to respond.
|
|
52
|
+
- `params` — URL params (`/api/verify/:token` → `params.token`).
|
|
53
|
+
- `query` — query-string values.
|
|
54
|
+
|
|
55
|
+
Handlers must return a `Response` (or a `Promise<Response>`). Hono helpers like `c.json`, `c.redirect`, `c.text` return `Response` directly. Unlike `action_*`, endpoints do **not** auto-serialize return values — HTTP endpoints need full control over status, headers, and body format (a GitHub webhook, for example, needs the raw body to verify the HMAC signature).
|
|
56
|
+
|
|
57
|
+
## Supported HTTP methods
|
|
58
|
+
|
|
59
|
+
`GET`, `POST`, `PUT`, `PATCH`, `DELETE`. One method per entry. If you need both GET and POST on the same path, declare two entries.
|
|
60
|
+
|
|
61
|
+
## Inline vs module-only handlers
|
|
62
|
+
|
|
63
|
+
Both forms work. The Vite plugin strips either one from the client bundle.
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
// OK inline — handler is trivial and doesn't import anything server-only
|
|
67
|
+
{ path: "/health", method: "GET", handler: ({ c }) => c.text("ok") },
|
|
68
|
+
|
|
69
|
+
// PREFER module — handler imports server-only libraries
|
|
70
|
+
import { githubWebhook } from "./webhooks/github.handler.js";
|
|
71
|
+
{ path: "/api/github/webhook", method: "POST", handler: githubWebhook },
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Recommendation:** when the handler imports anything server-only (`node:crypto`, DB client, SDK that doesn't bundle for the browser), put it in a separate module file. The plugin strips references on the client, so the handler module is tree-shaken away — but putting it in its own file makes the intent obvious to other readers and avoids edge cases where the client bundle tries to resolve server-only imports.
|
|
75
|
+
|
|
76
|
+
## Grouping and middleware inheritance
|
|
77
|
+
|
|
78
|
+
Endpoints inherit middlewares from parent nodes, just like pages:
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import { rateLimitMiddleware } from "./middlewares/rate-limit.js";
|
|
82
|
+
import { verifyGithubSignature } from "./middlewares/github-sig.js";
|
|
83
|
+
import { githubWebhook } from "./webhooks/github.handler.js";
|
|
84
|
+
import { pixWebhook } from "./webhooks/pix.handler.js";
|
|
85
|
+
|
|
86
|
+
export default [
|
|
87
|
+
{
|
|
88
|
+
path: "/api",
|
|
89
|
+
middlewares: [rateLimitMiddleware],
|
|
90
|
+
children: [
|
|
91
|
+
{
|
|
92
|
+
path: "/github/webhook",
|
|
93
|
+
method: "POST",
|
|
94
|
+
middlewares: [verifyGithubSignature],
|
|
95
|
+
handler: githubWebhook,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
path: "/pix",
|
|
99
|
+
method: "POST",
|
|
100
|
+
handler: pixWebhook,
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
- `/api/github/webhook` runs `[rateLimitMiddleware, verifyGithubSignature]` before the handler.
|
|
108
|
+
- `/api/pix` runs `[rateLimitMiddleware]`.
|
|
109
|
+
|
|
110
|
+
A node can be a **pure group** — only `path`, `children`, and `middlewares`, no `module` and no `handler`. The children inherit the path prefix and middlewares.
|
|
111
|
+
|
|
112
|
+
## Redirects from email links
|
|
113
|
+
|
|
114
|
+
A common use case:
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
import { verifyEmail } from "./auth/verify.handler.js";
|
|
118
|
+
|
|
119
|
+
// routes.tsx
|
|
120
|
+
{ path: "/verify/:token", method: "GET", handler: verifyEmail }
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
// auth/verify.handler.ts
|
|
125
|
+
import type { EndpointHandler } from "@mauroandre/velojs";
|
|
126
|
+
import { consumeVerifyToken } from "../services/auth.js";
|
|
127
|
+
|
|
128
|
+
export const verifyEmail: EndpointHandler = async ({ c, params }) => {
|
|
129
|
+
const ok = await consumeVerifyToken(params.token);
|
|
130
|
+
if (!ok) return c.redirect("/login?verified=0", 302);
|
|
131
|
+
return c.redirect("/stacks?verified=1", 302);
|
|
132
|
+
};
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Because the endpoint lives in `routes.tsx`, it's also visible to the testing toolkit — you can test the whole flow end-to-end (see below).
|
|
136
|
+
|
|
137
|
+
## Path conflicts with pages
|
|
138
|
+
|
|
139
|
+
An endpoint and a page on the same path with the **same method** (both `GET`) conflict. VeloJS registers pages first, endpoints second, and a page handler answers without deferring — so the **page wins and the endpoint is unreachable**. You'll see a loud warning at startup telling you to rename one.
|
|
140
|
+
|
|
141
|
+
The page has to win: a page `GET` also serves `?_data=1`, the JSON every client-side navigation fetches. An endpoint taking the path over would silently break that page's navigation.
|
|
142
|
+
|
|
143
|
+
Different methods on the same path (page `GET` + endpoint `POST`) don't conflict. They're both registered and each is routed according to the request method.
|
|
144
|
+
|
|
145
|
+
## Validation warnings
|
|
146
|
+
|
|
147
|
+
The server logs a warning and skips the entry when you declare an invalid combination:
|
|
148
|
+
|
|
149
|
+
| Problem | Warning |
|
|
150
|
+
|---|---|
|
|
151
|
+
| `handler` without `method` | `"handler" set without "method"; endpoint skipped` |
|
|
152
|
+
| `method` without `handler` | `"method" set without "handler"; endpoint skipped` |
|
|
153
|
+
| Both `module` and `handler` on the same node | `both "module" and "handler" set; endpoint ignored, page kept` |
|
|
154
|
+
| `isRoot: true` on an endpoint | `"isRoot" has no effect on endpoint` |
|
|
155
|
+
|
|
156
|
+
None of these throw — the server still boots. This keeps dev fast; you just see the problem in the console.
|
|
157
|
+
|
|
158
|
+
## Testing endpoints
|
|
159
|
+
|
|
160
|
+
Because endpoints live in `routes.tsx`, `createTestApp` picks them up automatically. No bootstrap required for simple webhooks:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import { createTestApp } from "@mauroandre/velojs/testing";
|
|
164
|
+
import routes from "../app/routes.js";
|
|
165
|
+
|
|
166
|
+
const app = await createTestApp({ routes });
|
|
167
|
+
|
|
168
|
+
const res = await app.post("/api/github/webhook", {
|
|
169
|
+
headers: { "x-hub-signature-256": signature },
|
|
170
|
+
body: { action: "push", ref: "refs/heads/main" },
|
|
171
|
+
});
|
|
172
|
+
expect(res.status).toBe(200);
|
|
173
|
+
|
|
174
|
+
const verify = await app.get("/verify/abc123");
|
|
175
|
+
expect(verify.status).toBe(302);
|
|
176
|
+
expect(verify.headers.location).toBe("/stacks?verified=1");
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
See [17-testing.md](/docs/testing) for the full testing API.
|
|
180
|
+
|
|
181
|
+
## Related
|
|
182
|
+
|
|
183
|
+
- [Routes](/docs/routes) — page routing and layouts
|
|
184
|
+
- [Loaders](/docs/loaders) — page data fetching
|
|
185
|
+
- [Actions](/docs/actions) — mutations from pages
|
|
186
|
+
- [Middlewares](/docs/middlewares) — authentication, logging, rate limiting
|
|
187
|
+
- [Testing](/docs/testing) — the backend testing toolkit
|