@thednp/rpc 0.1.1 → 0.2.1

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.
Files changed (39) hide show
  1. package/AGENTS.md +43 -26
  2. package/CHANGELOG.md +100 -0
  3. package/README.md +39 -31
  4. package/dist/express/express.d.mts +14 -2
  5. package/dist/express/express.d.mts.map +1 -1
  6. package/dist/express/express.mjs +103 -70
  7. package/dist/express/express.mjs.map +1 -1
  8. package/dist/fastify/fastify.d.mts +16 -1
  9. package/dist/fastify/fastify.d.mts.map +1 -1
  10. package/dist/fastify/fastify.mjs +61 -63
  11. package/dist/fastify/fastify.mjs.map +1 -1
  12. package/dist/fastify/plugin/fastify/plugin.d.mts +20 -3
  13. package/dist/fastify/plugin/fastify/plugin.d.mts.map +1 -1
  14. package/dist/fastify/plugin/fastify/plugin.mjs +60 -62
  15. package/dist/fastify/plugin/fastify/plugin.mjs.map +1 -1
  16. package/dist/h3/h3.d.mts +94 -0
  17. package/dist/h3/h3.d.mts.map +1 -0
  18. package/dist/h3/h3.mjs +267 -0
  19. package/dist/h3/h3.mjs.map +1 -0
  20. package/dist/helpers/helpers.d.mts +2 -0
  21. package/dist/helpers/helpers.d.mts.map +1 -1
  22. package/dist/helpers/helpers.mjs.map +1 -1
  23. package/dist/hono/hono.d.mts +14 -40
  24. package/dist/hono/hono.d.mts.map +1 -1
  25. package/dist/hono/hono.mjs +61 -73
  26. package/dist/hono/hono.mjs.map +1 -1
  27. package/dist/index.d.mts +36 -5
  28. package/dist/index.d.mts.map +1 -1
  29. package/dist/index.mjs +4 -0
  30. package/dist/index.mjs.map +1 -1
  31. package/dist/koa/koa.d.mts +12 -1
  32. package/dist/koa/koa.d.mts.map +1 -1
  33. package/dist/koa/koa.mjs +69 -64
  34. package/dist/koa/koa.mjs.map +1 -1
  35. package/dist/server/server.d.mts +180 -3
  36. package/dist/server/server.d.mts.map +1 -1
  37. package/dist/server/server.mjs +157 -1
  38. package/dist/server/server.mjs.map +1 -1
  39. package/package.json +10 -4
package/AGENTS.md CHANGED
@@ -6,8 +6,11 @@
6
6
  pnpm dev # Run examples/spa dev server
7
7
  pnpm dev:express # Run examples/express dev server
8
8
  pnpm dev:fastify # Run examples/fastify dev server
9
+ pnpm dev:h3 # Run examples/h3 dev server
9
10
  pnpm dev:hono # Run examples/hono dev server
10
11
  pnpm dev:koa # Run examples/koa dev server
12
+ pnpm dev:react-query # Run examples/react-query dev server
13
+ pnpm dev:solid-query # Run examples/solid-query dev server
11
14
  pnpm dev:ssr # Run examples/ssr dev server
12
15
  pnpm lint # Lint + typecheck (deno lint + tsc)
13
16
  pnpm test # Run tests with coverage
@@ -32,32 +35,39 @@ pnpm release # Publish npm + jsr (scripts/release.js)
32
35
 
33
36
  ## Examples
34
37
 
35
- The `examples/` directory contains 6 example apps:
38
+ The `examples/` directory contains 9 example apps:
36
39
 
37
- | Example | Adapter | Type | Run Command | Config |
38
- | -----------| -------------------------------------------------------------------| ------| --------------------| ----------------------------------|
39
- | `spa` | Vite dev server (no adapter) | SPA | `pnpm dev` | `examples/spa/rpc.config.ts` |
40
- | `express` | Express | SSR | `pnpm dev:express` | `examples/express/rpc.config.ts` |
41
- | `fastify` | Fastify | SSR | `pnpm dev:fastify` | `examples/fastify/rpc.config.ts` |
42
- | `hono` | Hono | SSR | `pnpm dev:hono` | `examples/hono/rpc.config.ts` |
43
- | `koa` | Koa | SSR | `pnpm dev:koa` | `examples/koa/rpc.config.ts` |
44
- | `ssr` | Custom `http-express.ts` (Express-compatible `node:http` server ) | SSR | `pnpm dev:ssr` | `examples/ssr/rpc.config.ts` |
40
+ | Example | Adapter | Type | Run Command | Config |
41
+ | ----------------| -------------------------------------------------------------------| ------| -------------------------| -----------------------------------------|
42
+ | `spa` | Vite dev server (no adapter) | SPA | `pnpm dev` | `examples/spa/rpc.config.ts` |
43
+ | `express` | Express | SSR | `pnpm dev:express` | `examples/express/rpc.config.ts` |
44
+ | `fastify` | Fastify | SSR | `pnpm dev:fastify` | `examples/fastify/rpc.config.ts` |
45
+ | `h3` | h3 | SSR | `pnpm dev:h3` | `examples/h3/rpc.config.ts` |
46
+ | `hono` | Hono | SSR | `pnpm dev:hono` | `examples/hono/rpc.config.ts` |
47
+ | `koa` | Koa | SSR | `pnpm dev:koa` | `examples/koa/rpc.config.ts` |
48
+ | `react-query` | Express (React + @tanstack/react-query SSR) | SSR | `pnpm dev:react-query` | `examples/react-query/rpc.config.ts` |
49
+ | `solid-query` | Express (Solid + @tanstack/solid-query SSR) | SSR | `pnpm dev:solid-query` | `examples/solid-query/rpc.config.ts` |
50
+ | `ssr` | Custom `http-express.ts` (Express-compatible `node:http` server ) | SSR | `pnpm dev:ssr` | `examples/ssr/rpc.config.ts` |
45
51
 
46
52
  Each example follows the same structure:
47
53
 
48
54
  - `server.js` / `server.ts` — production server with Vite middleware in dev, static serving in prod
49
55
  - `src/api/server.ts` — RPC server functions (auto-scanned by the plugin)
50
- - `src/entry-server.ts` — SSR entry (hydrates on client)
51
- - `src/entry-client.ts` — Client entry
56
+ - `src/entry-server.ts` / `src/entry-server.tsx` — SSR entry (hydrates on client)
57
+ - `src/entry-client.ts` / `src/entry-client.tsx` — Client entry
52
58
  - `vite.config.ts` + `rpc.config.ts` — Configuration files
53
59
 
60
+ > Note: the `react-query` example is SSR with React 19 + `@tanstack/react-query` (Express adapter). It prefetches queries in `entry-server.tsx`, dehydrates them into `window.__REACT_QUERY_STATE__`, and hydrates on the client via `HydrationBoundary`.
61
+ >
62
+ > Note: the `solid-query` example is SSR with Solid + `@tanstack/solid-query` (Express adapter). The greeting is prefetched and serialized with `renderToStringAsync`; the GET form does **not** use a disabled `createQuery` (which would hang SSR — see `wiki/client-usage.md`), it calls `queryClient.fetchQuery()` on submit instead.
63
+
54
64
  ## Key Directories
55
65
 
56
- - `src/` — source for all packages (vite plugin, server, express, fastify, hono, koa adapters)
66
+ - `src/` — source for all packages (vite plugin, server, express, fastify, h3, hono, koa adapters)
57
67
  - `dist/` — build output (not committed, generated by tsdown)
58
- - `tests/` — test files (one per adapter + plugin)
68
+ - `tests/` — test files (one per adapter + plugin + helpers)
59
69
  - `tests/fixtures/` — test fixtures (config files, vite-mock.ts)
60
- - `examples/` — example apps (spa, express, fastify, hono, koa, ssr)
70
+ - `examples/` — example apps (spa, express, fastify, h3, hono, koa, react-query, solid-query, ssr)
61
71
 
62
72
  ## Build Output (tsdown)
63
73
 
@@ -67,19 +77,24 @@ The tsdown.config.ts produces multiple entries:
67
77
  - `dist/server/server.mjs` — standalone server
68
78
  - `dist/express/express.mjs` — Express middleware
69
79
  - `dist/fastify/fastify.mjs` — Fastify middleware
80
+ - `dist/h3/h3.mjs` — h3 middleware
70
81
  - `dist/hono/hono.mjs` — Hono middleware
71
82
  - `dist/koa/koa.mjs` — Koa middleware
72
83
 
73
84
  ## Test Files
74
85
 
75
- | File | Tests | |
76
- | -------------------------| --------------------------------------------------------------------| -----|
77
- | `tests/plugin.test.ts` | Plugin init, loadRPCConfig, createServerFunction, getClientModules | |
78
- | `tests/express.test.ts` | Express helpers, createMiddleware, createRPCMiddleware | |
79
- | `tests/fastify.test.ts` | Fastify helpers, plugin, createMiddleware, createRPCMiddleware | |
80
- | `tests/hono.test.ts` | Hono helpers, createMiddleware, createRPCMiddleware | |
81
- | `tests/koa.test.ts` | Koa helpers, createMiddleware, createRPCMiddleware | |
82
- | `tests/scan.test.ts` | scanForServerFiles (real scan, skip, devServer, error handling) | |
86
+ | File | Tests | |
87
+ | ---------------------------------| --------------------------------------------------------------------| -----|
88
+ | `tests/plugin.test.ts` | Plugin init, loadRPCConfig, createServerFunction, getClientModules | |
89
+ | `tests/scan.test.ts` | scanForServerFiles (real scan, skip, devServer, error handling) | |
90
+ | `tests/server-helpers.test.ts` | RPCError, formatError, redirect, glob walking | |
91
+ | `tests/client-helpers.test.ts` | Client fetch stubs and retrieval helpers | |
92
+ | `tests/context.test.ts` | provideRequestContext / getRequestContext (AsyncLocalStorage) | |
93
+ | `tests/express.test.ts` | Express helpers, createMiddleware, createRPCMiddleware | |
94
+ | `tests/fastify.test.ts` | Fastify helpers, plugin, createMiddleware, createRPCMiddleware | |
95
+ | `tests/h3.test.ts` | h3 helpers, viteMiddleware, createMiddleware, createRPCMiddleware | |
96
+ | `tests/hono.test.ts` | Hono helpers, createMiddleware, createRPCMiddleware | |
97
+ | `tests/koa.test.ts` | Koa helpers, createMiddleware, createRPCMiddleware | |
83
98
 
84
99
  ## Important Notes
85
100
 
@@ -105,7 +120,7 @@ The tsdown.config.ts produces multiple entries:
105
120
  - **Koa URL normalization**: Koa adapter parses `ctx.url` through `new URL()` to strip query strings and normalize encoding before prefix checking
106
121
  - **Code injection prevention in client module generation**: `getClientModules.ts` validates all interpolated identifiers (`fnName`, `fnEntry`, `rpcPrefix`) against `/^[A-Za-z_$][A-Za-z0-9_$]*$/` (and a path-safe variant allowing `/`) before interpolating into the generated client bundle. This prevents code injection via malicious export names or prefixes containing template literal interpolations (`${...}`), backticks, or `</script>` sequences.
107
122
  - **Body size limits**: Host frameworks cap parsed JSON bodies — Express (`express.json({ limit })`), Fastify (`bodyLimit`), Koa (`koa-body`), Hono (`hono/body-limit`). Rely on your framework's body parser middleware for size limits (see wiki/best-practices.md). The raw stream path in `readBody` does not impose a built-in limit — use framework middleware or a custom body limit handler for defense-in-depth.
108
- - **Generic 404 responses**: Error messages no longer echo the requested function name, preventing function enumeration
123
+ - **Generic 404 responses**: Error messages never echo the requested function name (no message-based function enumeration). Note the status code still distinguishes unknown (`404`) from known functions (`405`/`415`/`403`); function names ship in the client bundle so they are not secret — see `wiki/security.md`
109
124
  - **Auth is middleware's responsibility**: Authentication should be handled by middleware registered before `createRPCMiddleware()`. The middleware chain naturally composes — no built-in auth hook is needed.
110
125
  - **No client-side secrets or stack traces**: Error responses always return `"Internal Server Error"` regardless of the underlying error; `console.error(String(err))` is server-side only for debugging and does not surface internals to the client
111
126
 
@@ -129,16 +144,18 @@ The framework's security boundary is the **RPC prefix-gated HTTP endpoint**. Inp
129
144
  **Attackers are expected to**:
130
145
  - Be free to send as many requests as the host allows (no rate limiting — host's responsibility)
131
146
  - Be free to hit any URL (no auth — host's responsibility via prior middleware)
132
- - Be rejected with generic errors (no enumeration, no stack traces)
147
+ - Be rejected with generic error bodies (no function-name disclosure in messages, no stack traces); status-code differential still reveals existence — see `wiki/security.md`
133
148
 
134
149
  ## Documentation
135
150
 
136
151
  - `wiki/quickstart.md` — Rebuild the Express SSR example from `create-vite` in under a minute (copy-paste)
137
152
  - `wiki/getting-started.md` — Installation, project structure, auto-scanning, and your first function
138
153
  - `wiki/configuration.md` — Configuration reference (`rpc.config.ts`, `vite.config.ts`, options)
139
- - `wiki/server-functions.md` — `createServerFunction` API, methods, validation
154
+ - `wiki/server-functions.md` — `createServerFunction` API, methods, validation, **request context (`getRequestContext`/`provideRequestContext`)** for per-request data access across async call stacks
155
+ - `wiki/middleware.md` — universal adapter-agnostic middleware via the request context (`locals` bridge, `getRequestMeta`, `sendResponse`, `functionName`)
156
+ - `wiki/nojs-fallback.md` — native (no-JS) `<form>` fallback / progressive enhancement pattern
140
157
  - `wiki/client-usage.md` — Client-side usage, type safety, react-query integration
141
158
  - `wiki/wire-protocol.md` — HTTP contract, request/response bodies, curl debugging
142
- - `wiki/adapters.md` — Framework adapters (Express, Fastify, Hono, Koa)
159
+ - `wiki/adapters.md` — Framework adapters (Express, Fastify, Hono, Koa, h3)
143
160
  - `wiki/security.md` — Security hardening
144
161
  - `wiki/best-practices.md` — Production patterns (auth, rate limiting, body limits, CSRF)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,105 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.1] - 2026-08-10
4
+
5
+ ### Features
6
+
7
+ - **`send` on the request context**: `RequestEvent.send(status, body, headers?)` (plus the `sent` flag) lets middleware and server functions short-circuit the RPC dispatch with a full HTTP response instead of the `{ data }` envelope, mirroring the existing `redirect`/`redirected` pair. All five adapters bind it and skip their JSON send when `sent` is set: Express (via `getResponseDetails().sendResponse`), Fastify (`reply.header()` loop + `reply.status().send()`), Koa (`ctx.set()` loop + `ctx.status`/`ctx.body`), Hono (records only — the post-dispatch handler returns `c.body(JSON.stringify(body), status, { "content-type": "application/json", ...headers })`), and h3 (records only — the post-dispatch handler sets `event.res.status` and `event.res.headers` then returns the body)
8
+ - **`functionName` on the request context**: `RequestEvent.functionName` exposes the dispatched function name so universal middleware can branch per function (e.g. rate limits, per-function authorization)
9
+ - **`sendResponse` helper**: a `sendResponse(status, body, headers?)` context helper, the response counterpart to the `redirect` helper, delegating to the same adapter-bound write path
10
+ - **`getRequestMeta(event)`**: a normalized request reader returning `{ method, pathname, search, searchParams, headers, host, ip, protocol }`, duck-typed across all five adapter request shapes — feature-detects fetch-like `Headers` (h3/Hono) vs plain maps (Express/Fastify/Koa), resolves the URL from `originalUrl ?? url ?? path`, and derives `host`/`protocol`/`ip` with safe fallbacks
11
+
12
+ ### Examples
13
+
14
+ - **h3 example**: extract `middleware/bodyLimit.js` — delegates to h3's native `assertBodySize`, which swaps `event.req` for a bounded stream so the cap is enforced while streaming (never fully buffered) and the RPC `readBody` can still consume it afterwards; oversized bodies reject with `413` JSON. Extract `middleware/serveStatic.js` — aliases h3's `serveStatic` from `h3/node`, adds `Content-Length`, `Last-Modified`, and `Cache-Control: public, max-age=31536000, immutable`, and is registered **after** `createRPCMiddleware()` so asset requests never reach server functions; missing files fall through to the SSR handler
15
+ - **demo**: the prerender plugin now writes the app content between `<!-- app-content -->` markers and gains a `configurePreviewServer` middleware that re-renders just that region from the URL query — nojs form state (values + errors) is recovered in `vite preview`, where the baked `dist/index.html` shell otherwise skips `transformIndexHtml`
16
+ - **demo**: `body-limit.ts` stashes multipart bodies as `{ raw: body }` to mirror `@thednp/rpc/express`'s `readBody` streaming semantics; the render page and `getLibraryInfo` now count 9 examples and list the h3 adapter; the features grid grows to 9 cards (3×3) with request-context, no-JS form-fallback, and boundary-enforcement entries
17
+ - **fastify example**: switch the production server to `@fastify/compress` (gzip) for the RPC endpoint and static HTML. `@fastify/compress` attaches its per-route `onSend` hook via `onRoute`, which never fires for the RPC plugin's global `preHandler` handling, so the example registers a scoped `app.post("/_server/*")` catch-all route — the RPC `preHandler` short-circuits before the handler runs, letting compress's hook attach to RPC POSTs while non-RPC POSTs still get a 404. Verified with curl: HTML and RPC POST responses (200 and 404) compress (gzip) with byte-identical decompression (md5 match), and non-RPC POSTs keep their 404
18
+ - Sync all 9 examples to `@thednp/rpc ^0.2.0`
19
+
20
+ ### Docs
21
+
22
+ - **New `wiki/middleware.md`** — universal adapter-agnostic middleware via the request context: the `locals` bridge table (Express `res.locals`, Koa `ctx.state`, h3 `event.context`, Fastify/Hono `{}` with `decorateRequest`/`c.set` workarounds), `getRequestMeta`, `functionName`, `sendResponse` per-adapter mapping, and wrap recipes for official framework middleware (Express session, Koa `ctx.state`, h3 `event.context`, Fastify `decorateRequest`, Hono `c.set`/`c.get`)
23
+ - TOC sweep: `- [Middleware](./middleware.md)` entry added to all 11 wiki pages after Server Functions; cross-linked from `server-functions.md` (RequestEvent shape + "Writing Universal Middleware" section), `best-practices.md` (Authentication, Rate Limiting), and `adapters.md`; `wiki/index.md` updated
24
+ - Update `wiki/server-functions.md` RequestEvent reference for `send`/`sent`/`functionName` and re-point its "Next" pointer to `middleware.md`
25
+ - `wiki/nojs-fallback.md`: explain the form markup and detection rule without embedding the whole `createFormFallback` implementation (now links to `demo/src/lib/form-fallback.ts`)
26
+ - `wiki/adapters.md`: replace the h3 body-limit snippet with h3's native `assertBodySize` pattern (bounded stream, never buffered) and add a **Static Assets** section for the extracted `serveStatic` middleware; `wiki/best-practices.md` h3 body limit now recommends `assertBodySize` and warns against iterating `event.req` (which breaks the RPC `readBody` with "Body is unusable")
27
+ - Update `AGENTS.md` (9 examples, h3 adapter, test files table), `llms.txt`, and `README.md` (five adapters, 10 test files, `nojs-fallback` doc link, `pmpm` → `pnpm` typo) for the new adapter, examples, and context API
28
+ - Example READMEs: add the `wiki/middleware.md` resource link to all 9 example READMEs; rewrite `examples/solid-query/README.md` (was a copy-paste of the react-query README — now describes Solid's `createQuery`/`createMutation`, `renderToStringAsync` SSR, and the disabled-query gotcha); expand the h3 example README's production flow for the extracted `bodyLimit`/`serveStatic` middleware
29
+
30
+ ### Tests
31
+
32
+ - **100% coverage across all adapters**: 1001/1001 statements, 608/608 branches, 155/155 functions, 981/981 lines (412 tests)
33
+ - Add `send` short-circuit tests to the Express, Fastify, and Koa suites — both with headers (asserting the `headers` loop) and without (covering the `if (headers)` false branch that had dropped branch coverage below 100%)
34
+ - Add `tests/context.test.ts` suite: `sendResponse` delegation (with/without headers, outside-request throw), `getRequestMeta` normalization (Express-style, fetch-like `Headers`, `ip`/`protocol` derivation, bare request, plain-map headers, array-valued header, URL protocol fallback), and `functionName` passthrough
35
+ - Adapter suites assert `functionName` exposure via `getRequestContext()` and `send` short-circuits the JSON dispatch on all five adapters
36
+ - Add `?args=` non-array rejection tests (400 Bad Request) and bare-GET (no `?args=`) dispatch tests to all five adapter suites — **100/100% coverage, 412 tests**
37
+
38
+ ### Security
39
+
40
+ - **`safeURL` defensive URL parsing**: `getRequestDetails()` and every adapter's inline `new URL(rawUrl, ...)` call now parse through `safeURL` (`src/server-helpers.ts`), which never throws — a malformed request-target like `/\` or `//` used to trigger an unhandled `TypeError: Invalid URL` rejection outside the dispatch `try` block and **crash raw `node:http` hosts** (and Express 4). Malformed URLs now fall back to a safe root pathname that never matches the RPC prefix, so the request degrades to `next()`/404 instead of crashing the process (High)
41
+ - **GET `?args=` is now validated as a JSON array**: a non-array value (e.g. `?args={"a":1}`) previously spread into `handler(...args)` and threw a confusing `TypeError: object is not iterable` 500; all five adapters now reject it with `400 { error: "Bad Request" }` before dispatch
42
+ - **`wiki/security.md` enumeration claim corrected**: the "prevents function enumeration" wording was overstated — the status code still distinguishes unknown (`404`) from known (`405`/`415`/`403`) functions. Documented that enumeration is mitigated against *message* disclosure only; function names ship in the client bundle so they are not secret. `AGENTS.md` and `llms.txt` updated to match
43
+ - Full source security audit recorded in `SECURITY-AUDIT.md` (High crash finding fixed; remaining findings are LOW/INFO/design notes)
44
+
45
+ ### Chores
46
+
47
+ - Bump version to `0.2.1` (package.json + deno.json)
48
+ - Add `@thednp/rpc@0.2.0` to `minimumReleaseAgeExclude` in `pnpm-workspace.yaml`
49
+
50
+ ## [0.2.0] - 2026-08-09
51
+
52
+ ### Features
53
+
54
+ - **h3 adapter**: new `@thednp/rpc/h3` adapter with `createRPCMiddleware`, `viteMiddleware`, `attachRPC`, `attachVite`, `readBody`, `redirect` — fully typed, 100% test coverage, SSR example at `examples/h3`
55
+ - **Request Context API**: `provideRequestContext(init, cb)` / `getRequestContext()` — per-request `AsyncLocalStorage` context available to all server function code; `RequestEvent` includes `nativeEvent`, `locals`, adapter-bound `redirect`; works across Express, Fastify, Hono, Koa, and h3
56
+ - **`redirect` helper**: a new `redirect(res, location, status?)` server utility exported from `@thednp/rpc/server` and every adapter. It defaults to `303 See Other` for Post/Redirect/Get flows. The core version accepts an Express `Response` or a raw Node `ServerResponse`, delegating to native `res.redirect()` when available and otherwise writing the status code and `Location` header directly — the raw path is safe on Connect-compatible middlewares and serverless adapters (e.g. Netlify's `serverless-http` mock). Each adapter exports a redirect typed for its response object: Fastify `reply.redirect(location, status)` (v5 URL-first), Koa `ctx.redirect(location)` then `ctx.status = status` (setting status *after*, per koajs/koa#857), and Hono `return redirect(c, location, status)` (the `Response` is returned from the handler)
57
+
58
+ - **`application/x-www-form-urlencoded` content type**: the `contentType` option and `BodyResult` now include urlencoded. The generated client serializes the single object argument with `new URLSearchParams(args[0]).toString()`, so native HTML forms can POST straight to an RPC endpoint without client-side serialization. Adapters parse `key=value&key2=value2` into an object via `URLSearchParams` (every value arrives as a string; repeated keys collapse to the last value) and use the framework's pre-parsed body (`express.urlencoded()`, `@fastify/formbody`, `koa-body`) when one ran first
59
+ - **Content-type enforcement**: the RPC middleware now validates the request's `Content-Type` against the function's declared `contentType` before parsing the body, rejecting mismatches with `415 Unsupported Media Type` on all four adapters. JSON and text functions are enforced strictly (exact match wins after stripping `charset`/`boundary` parameters); form functions (`multipart/form-data` or `application/x-www-form-urlencoded`) are lenient between the two encodings so native urlencoded form submissions keep working on multipart-declared endpoints. Requests without a `Content-Type` header (curl, GET, legacy clients) are exempt from enforcement. The check lives in the new `hasContentTypeMismatch`/`isFormContentType` helpers exported from `@thednp/rpc/server`
60
+
61
+ ### Examples
62
+
63
+ - Add the `h3` example (`examples/h3`): SSR with [h3](https://h3.unjs.io/) using `createRPCMiddleware`, `serveStatic`, `toNodeListener`, and `viteMiddleware`. Dev mode bridges Vite's connect stack; prod serves static assets via `serveStatic` and falls through to SSR.
64
+ - The `demo` form-fallback middleware now uses the new `redirect` helper from `@thednp/rpc/express` instead of hand-writing the `303` + `Location` response
65
+ - Add a nojs fallback to the `demo` contact form (progressive enhancement): the form now carries native `action="/@demo/submit-contact" method="post"` attributes, and a small app-layer `createFormFallback` middleware (mounted before the RPC middleware) intercepts browser form navigations — recognized by `POST` + `application/x-www-form-urlencoded` + `Accept: text/html`, which the JS client never sends. Valid submissions get a `303` redirect to the pre-filled GitHub issue URL; invalid ones get a `303` redirect back to `/?name=..&errors=..`, where the server-rendered page (and hydration) recover the form values and show the same field-level error messages the RPC path would have returned. Validation, issue-URL building, and field lists are shared with the server function via the new `demo/src/lib/contact-form.ts`, and the server function now normalizes both multipart (`{ raw }`) and plain-object (urlencoded/JSON-with-fields) payloads
66
+ - Add the `react-query` example (Express + React 19 + `@tanstack/react-query` SSR): prefetches the greeting in `entry-server.tsx`, dehydrates it into `window.__REACT_QUERY_STATE__`, and hydrates on the client via `HydrationBoundary`
67
+ - Add the `solid-query` example (Express + Solid + `@tanstack/solid-query` SSR): prefetches the greeting, serializes it with `renderToStringAsync`, and documents the disabled-query SSR gotcha — a disabled `createQuery` hangs `renderToStringAsync` because the observer result carries a never-settling `promise` (from `experimental_prefetchInRender`) that seroval awaits forever; the example calls `queryClient.fetchQuery()` on submit instead
68
+
69
+ ### Docs
70
+
71
+ - Document Request Context API (`provideRequestContext`/`getRequestContext`) in `wiki/server-functions.md` — per-request `AsyncLocalStorage` context with `nativeEvent`, `locals`, adapter-bound `redirect` across all 5 adapters
72
+ - Document urlencoded content type in `wiki/server-functions.md` and `wiki/wire-protocol.md` (new `POST + application/x-www-form-urlencoded` section)
73
+ - Document the `415 Unsupported Media Type` response and content-type enforcement rules in `wiki/wire-protocol.md`
74
+ - Document the strict/lenient content-type behavior in `wiki/server-functions.md`
75
+ - Add a "Content-Type Enforcement" section to `wiki/security.md`
76
+ - Document the `redirect` helper in `wiki/server-functions.md` (new "Redirects (`redirect`)" section) and cross-reference it from `wiki/adapters.md`
77
+ - Add h3 adapter section to `wiki/adapters.md` (installation, usage, body limits)
78
+ - Add h3 body limits to `wiki/best-practices.md` (Content-Length fast path + streaming cap via `for await (const chunk of event.req.body)`)
79
+ - Update `llms.txt`, `AGENTS.md`, and `README.md` for the new adapter, examples, content type, enforcement behavior, redirect helper, and request context
80
+
81
+ ### Tests
82
+
83
+ - **100% coverage achieved** across all adapters (express, fastify, h3, hono, koa) — 920/920 statements, 559/559 branches, 144/144 functions, 898/898 lines
84
+ - Add h3 adapter test suite (`tests/h3.test.ts`): viteMiddleware (node/web runtime paths, settle-twice guard), createMiddleware (prefix/path filtering, name deduplication), createRPCMiddleware (dispatch, content-type enforcement, origin check, redirect default 303, cancel on close, error handling)
85
+ - Add redirect default-status tests to Express, Fastify, Hono, and Koa adapter suites (covers `status = 303` default param branch)
86
+ - Add urlencoded `readBody` tests for all four adapters (pre-parsed and raw stream paths), a urlencoded client-module codegen test in `plugin.test.ts`, and an end-to-end RPC middleware dispatch test
87
+ - Add content-type enforcement tests to the Express, Fastify, Hono, and Koa adapter suites (415 on json↔urlencoded/text↔json mismatch, lenient acceptance of urlencoded on multipart-declared functions) and unit tests for `hasContentTypeMismatch`/`isFormContentType` (parameter stripping, case insensitivity, no-header exemption, form leniency both directions)
88
+ - Add `redirect` tests: raw `ServerResponse` write path and native `.redirect()` delegation in `server-helpers.test.ts`, plus per-adapter suites asserting each framework API (Express `res.redirect(status, url)` vs raw write, Fastify URL-first `reply.redirect`, Koa status-after-`ctx.redirect`, Hono returning `c.redirect`'s `Response`)
89
+ - Fix `scripts/dev-test.js` to verify the query-framework examples' dynamically rendered greeting (`Hello Jane!`) instead of only the static `Hello World!` SSR marker; register the `solid-query` example prefix
90
+
91
+ ### Chores
92
+
93
+ - Bump version to `0.2.0`
94
+ - Sync `deno.json` version with `package.json`
95
+
96
+ ### Security
97
+
98
+ - **Content-type check moved before body read**: all four adapters now evaluate `hasContentTypeMismatch` *before* calling `readBody`, so mismatched requests are rejected with `415 Unsupported Media Type` without ever buffering/parsing the body (previously the body was read first, contradicting the documented behavior)
99
+ - **Streaming body-limit in the demo**: `demo/body-limit.ts` no longer buffers the entire body via `readBody` before checking the cap. It enforces the 1MB limit while the request stream is being read (dropping buffered chunks and destroying the request on overflow) and adds a `Content-Length` fast-path for obviously oversized requests — closing a memory-exhaustion gap on the demo's raw `node:http` and Netlify entry points
100
+ - **Netlify URL rewrite tightened**: `demo/netlify/functions/rpc.ts` now matches the `/.netlify/functions/rpc/` marker against the parsed `pathname` only (prefix match), instead of `indexOf` on the raw URL which could match inside a query string and rewrite unintended requests
101
+ - **`@hono/node-server` dedupe**: add a workspace `overrides: { '@hono/node-server': '>=2.0.5' }` in `pnpm-workspace.yaml` to collapse the vulnerable `1.19.17` transitive dependency (via `@hono/vite-dev-server`, which pins `^1.19.11`) onto the already-used patched `2.1.0`. `pnpm audit` is now clean
102
+
3
103
  ## [0.1.1] - 2026-08-07
4
104
 
5
105
  ### Security
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![JSR Version](https://img.shields.io/jsr/v/@thednp/rpc.svg)](https://jsr.io/@thednp/rpc)
7
7
  [![NPM Downloads](https://img.shields.io/npm/dm/@thednp/rpc.svg)](http://npm-stat.com/charts.html?package=@thednp/rpc)
8
8
 
9
- A Vite plugin for automatic RPC generation — simple, framework agnostic, and easy to use.
9
+ A Vite plugin for automatic RPC generation — write server functions, re-export them, call them. Simple and framework agnostic.
10
10
 
11
11
  ## Isomorphic Design
12
12
 
@@ -35,7 +35,7 @@ You define them in a file, import and call them where you need them. The plugin
35
35
  * `createFunction.ts` — server-side definition (wrapped handler with `AbortController`)
36
36
  * `getClientModules.ts` — build-time code generation (string template with validation)
37
37
  * `client-helpers.ts` — client-side runtime (thin `fetch` based modules)
38
- * `server-helpers.ts` — server-only utilities (`RPCError`, error formatting, glob file walking)
38
+ * `server-helpers.ts` — server-only utilities (`RPCError`, error formatting, `redirect`, glob file walking)
39
39
  * `scanForServerFiles.ts` — file discovery
40
40
  * **Adapters** — thin middleware wrappers
41
41
  </details>
@@ -78,7 +78,7 @@ Every server function call returns a handle with a `cancel()` helper. Under the
78
78
  <details>
79
79
  <summary><b>Your server framework is your business</b></summary>
80
80
 
81
- The core plugin doesn't care whether you're running Express, Fastify, Hono, or Koa. Adapters for all four are bundled with the package — you import the one you need, register it as middleware, and you're done. If you're building a plain SPA with no server framework at all, the Vite dev server handles RPC requests directly in development. No adapter needed.
81
+ The core plugin doesn't care whether you're running Express, Fastify, Hono, Koa, or h3. Adapters for all five are bundled with the package — you import the one you need, register it as middleware, and you're done. If you're building a plain SPA with no server framework at all, the Vite dev server handles RPC requests directly in development. No adapter needed.
82
82
  </details>
83
83
 
84
84
  <details>
@@ -90,7 +90,7 @@ Scan `src/api/` for classic `server.ts|js|mjs|mts` files, or switch to glob mode
90
90
  <details>
91
91
  <summary><b>Typed errors, safe by default</b></summary>
92
92
 
93
- Server errors return a generic `Internal Server Error` — no messages, codes, or stacks leak to clients, in any environment. Only `RPCError` payloads (developer-authored `message`/`code`/`data`) reach the client, and only in development, so you can debug instantly. `multipart/form-data` content type is supported for file uploads via your framework's multipart parser.
93
+ Server errors return a generic `Internal Server Error` — no messages, codes, or stacks leak to clients, in any environment. Only `RPCError` payloads (developer-authored `message`/`code`/`data`) reach the client, and only in development, so you can debug instantly. `multipart/form-data` content type is supported for file uploads via your framework's multipart parser, and json/text/urlencoded requests are validated against the function's declared content type (`415 Unsupported Media Type` on mismatch; form encodings are interchangeable for nojs form fallbacks).
94
94
  </details>
95
95
 
96
96
  <details>
@@ -99,29 +99,29 @@ Server errors return a generic `Internal Server Error` — no messages, codes, o
99
99
  Generic type inference flows from your server function's arguments and return type all the way to the client stub. You get autocomplete for function names, argument types, and return types without writing a single type annotation on the client side.
100
100
  </details>
101
101
 
102
- ## Demos
103
-
104
- | Example | Source Code | Try online |
105
- | -----------------| --------------------------------------------------------------------------------| ------------------------------------------------------------------------------------------|
106
- | SPA - node:http | [examples/spa](https://github.com/thednp/rpc/tree/master/examples/spa) | [StackBlitz](https://stackblitz.com/fork/github/thednp/rpc/tree/master/examples/spa) |
107
- | SSR - node:http | [examples/ssr](https://github.com/thednp/rpc/tree/master/examples/ssr) | [StackBlitz](https://stackblitz.com/fork/github/thednp/rpc/tree/master/examples/ssr) |
108
- | Express | [examples/express](https://github.com/thednp/rpc/tree/master/examples/express) | [StackBlitz](https://stackblitz.com/fork/github/thednp/rpc/tree/master/examples/express) |
109
- | Fastify | [examples/fastify](https://github.com/thednp/rpc/tree/master/examples/fastify) | [StackBlitz](https://stackblitz.com/fork/github/thednp/rpc/tree/master/examples/fastify) |
110
- | Hono | [examples/hono](https://github.com/thednp/rpc/tree/master/examples/hono) | [StackBlitz](https://stackblitz.com/fork/github/thednp/rpc/tree/master/examples/hono) |
111
- | Koa | [examples/koa](https://github.com/thednp/rpc/tree/master/examples/koa) | [StackBlitz](https://stackblitz.com/fork/github/thednp/rpc/tree/master/examples/koa) |
102
+ ## Examples
112
103
 
113
- > **NOTE**: Stackblitz is currently working on upgrading their platform. Demos may not work properly.
104
+ | Source | Demo | Clone |
105
+ | ----------------------------------------------------------------------------------------| ----------------------------------------------------------------------------------------------| ---------------------------------------------------------|
106
+ | [examples/spa](https://github.com/thednp/rpc/tree/master/examples/spa) | [StackBlitz](https://stackblitz.com/fork/github/thednp/rpc/tree/master/examples/spa) | `pnpm dlx degit thednp/rpc/examples/spa my-app` |
107
+ | [examples/ssr](https://github.com/thednp/rpc/tree/master/examples/ssr) | [StackBlitz](https://stackblitz.com/fork/github/thednp/rpc/tree/master/examples/ssr) | `pnpm dlx degit thednp/rpc/examples/ssr my-app` |
108
+ | [examples/express](https://github.com/thednp/rpc/tree/master/examples/express) | [StackBlitz](https://stackblitz.com/fork/github/thednp/rpc/tree/master/examples/express) | `pnpm dlx degit thednp/rpc/examples/express my-app` |
109
+ | [examples/fastify](https://github.com/thednp/rpc/tree/master/examples/fastify) | [StackBlitz](https://stackblitz.com/fork/github/thednp/rpc/tree/master/examples/fastify) | `pnpm dlx degit thednp/rpc/examples/fastify my-app` |
110
+ | [examples/h3](https://github.com/thednp/rpc/tree/master/examples/h3) | [StackBlitz](https://stackblitz.com/fork/github/thednp/rpc/tree/master/examples/h3) | `pnpm dlx degit thednp/rpc/examples/h3 my-app` |
111
+ | [examples/hono](https://github.com/thednp/rpc/tree/master/examples/hono) | [StackBlitz](https://stackblitz.com/fork/github/thednp/rpc/tree/master/examples/hono) | `pnpm dlx degit thednp/rpc/examples/hono my-app` |
112
+ | [examples/koa](https://github.com/thednp/rpc/tree/master/examples/koa) | [StackBlitz](https://stackblitz.com/fork/github/thednp/rpc/tree/master/examples/koa) | `pnpm dlx degit thednp/rpc/examples/koa my-app` |
113
+ | [examples/react-query](https://github.com/thednp/rpc/tree/master/examples/react-query) | [StackBlitz](https://stackblitz.com/fork/github/thednp/rpc/tree/master/examples/react-query) | `pnpm dlx degit thednp/rpc/examples/react-query my-app` |
114
+ | [examples/solid-query](https://github.com/thednp/rpc/tree/master/examples/solid-query) | [StackBlitz](https://stackblitz.com/fork/github/thednp/rpc/tree/master/examples/solid-query) | `pnpm dlx degit thednp/rpc/examples/solid-query my-app` |
114
115
 
115
- ## Examples
116
+ > **Clone an example**: `degit` scaffolds a fresh copy straight from the repo — no git history, ready to run:
116
117
 
117
- | Example | Adapter | Type | Run Command | RPC Approach |
118
- | ---------| -----------------------------------------------| ------| --------------------| ------------------------------------|
119
- | spa | Vite dev server (Connect, Express-compatible) | SPA | `pnpm dev` | Client stubs only |
120
- | express | Express | SSR | `pnpm dev:express` | Direct import (SSR) + client stubs |
121
- | fastify | Fastify | SSR | `pnpm dev:fastify` | Direct import (SSR) + client stubs |
122
- | hono | Hono | SSR | `pnpm dev:hono` | Direct import (SSR) + client stubs |
123
- | koa | Koa | SSR | `pnpm dev:koa` | Direct import (SSR) + client stubs |
124
- | ssr | Custom `node:http` (Express-compatible) | SSR | `pnpm dev:ssr` | Direct import (SSR) + client stubs |
118
+ ```bash
119
+ # Scaffold the Express example
120
+ pnpm dlx degit thednp/rpc/examples/express my-rpc-app
121
+ cd my-rpc-app
122
+ pnpm install
123
+ pnpm dev
124
+ ```
125
125
 
126
126
  SSR examples demonstrate isomorphic usage: server functions are imported directly during server-side rendering (`entry-server.ts`) and also called from the client via auto-generated fetch stubs. The SPA example uses only the client-side stubs.
127
127
 
@@ -249,7 +249,7 @@ pnpm test-ui # Run tests with UI
249
249
  pnpm test --run # Single run
250
250
  ```
251
251
 
252
- Tests use **Vitest** with **Istanbul** coverage — 8 test files covering the plugin, scanning, client/server helpers, and all four adapters, at 100% coverage.
252
+ Tests use **Vitest** with **Istanbul** coverage — 10 test files covering the plugin, scanning, server/client helpers, request context, and all five adapters, at 100% coverage.
253
253
 
254
254
  ### Live Testing
255
255
 
@@ -319,6 +319,12 @@ The `readBody` utility of each adapter doesn't cap raw request bodies by default
319
319
  Server functions only support `GET` and `POST` (default `POST`). RPC dispatch is not REST — `PUT`/`PATCH`/`DELETE` carry resource semantics that don't apply to function calls, and `OPTIONS` must stay reserved for CORS preflight. Every accepted method is another dispatch path to validate; keeping the surface minimal (and defaulting to `POST`) reduces CSRF and parsing attack surface. See [Server Functions Guide](./wiki/server-functions.md) for details.
320
320
  </details>
321
321
 
322
+ <details>
323
+ <summary><b>Content-type enforcement</b></summary>
324
+
325
+ Request bodies are validated against the function's declared `contentType` before parsing — mismatches get a `415 Unsupported Media Type`. JSON and text functions require an exact match (after stripping `charset`/`boundary` parameters); the two form encodings are interchangeable so native urlencoded `<form>` submissions keep working on multipart-declared endpoints (nojs progressive enhancement). Requests without a `Content-Type` header are exempt, so curl and `GET` keep working unchanged. See [Wire Protocol](./wiki/wire-protocol.md) for details.
326
+ </details>
327
+
322
328
  ---
323
329
  The full threat model, including edge cases and configuration options for tightening things further, is documented in [Security](./wiki/security.md).
324
330
 
@@ -327,13 +333,15 @@ The full threat model, including edge cases and configuration options for tighte
327
333
 
328
334
  - [Quick Start](./wiki/quickstart.md) — Rebuild the Express SSR example from `create-vite` in under a minute
329
335
  - [Getting Started](./wiki/getting-started.md) — Installation, project structure, and your first function
330
- - [Configuration](./wiki/configuration.md)
331
- - [Server Functions](./wiki/server-functions.md)
332
- - [Client Usage](./wiki/client-usage.md)
336
+ - [Configuration](./wiki/configuration.md) — Full configuration reference
337
+ - [Server Functions](./wiki/server-functions.md) — Creating server functions
338
+ - [Middleware](./wiki/middleware.md) — Universal middleware via the request context
339
+ - [Native Form Fallback](./wiki/nojs-fallback.md) — Making RPC endpoints work as a no-JS `<form>` action
340
+ - [Client Usage](./wiki/client-usage.md) — Client-side usage
333
341
  - [Wire Protocol](./wiki/wire-protocol.md) — The HTTP contract behind the generated clients (curl debugging)
334
- - [Adapters](./wiki/adapters.md)
335
- - [Best Practices](./wiki/best-practices.md)
336
- - [Security](./wiki/security.md)
342
+ - [Adapters](./wiki/adapters.md) — Framework adapters
343
+ - [Best Practices](./wiki/best-practices.md) — Tips and best practices
344
+ - [Security](./wiki/security.md) — Security hardening
337
345
 
338
346
  ## License
339
347
 
@@ -38,7 +38,7 @@ type ResponseDetails = {
38
38
  /** Sets the response status code */
39
39
  setStatusCode: (code: number) => void;
40
40
  /** Sends a JSON response with the given status code and output */
41
- sendResponse: (code: number, output: Record<string, JsonValue>) => void;
41
+ sendResponse: (code: number, output: JsonValue) => void;
42
42
  };
43
43
  /**
44
44
  * Normalized view of an incoming request: URL parts, headers, and method.
@@ -107,6 +107,18 @@ declare const isExpressRequest: (req: IncomingMessage | Request) => req is Reque
107
107
  * @returns True if the response is an Express Response
108
108
  */
109
109
  declare const isExpressResponse: (res: ServerResponse | Response) => res is Response;
110
+ /**
111
+ * Issues an HTTP redirect on an Express or raw Node ServerResponse.
112
+ * Uses Express's native `res.redirect(status, location)` when an Express
113
+ * Response is provided, otherwise writes the status code and `Location`
114
+ * header directly on the raw `ServerResponse` (safe for Connect-compatible
115
+ * middlewares and serverless adapters whose mock responses lack `.redirect`).
116
+ * Defaults to `303 See Other` for convention (Post/Redirect/Get).
117
+ * @param res - Express Response or raw Node ServerResponse
118
+ * @param location - The URL to redirect to
119
+ * @param status - HTTP status code, defaults to 303
120
+ */
121
+ declare const redirect: (res: ServerResponse | Response, location: string, status?: number) => void;
110
122
  /**
111
123
  * Type guard that checks whether a request has a pre-parsed body (`body` property).
112
124
  * Used to detect if a body-parser middleware already consumed the stream.
@@ -129,5 +141,5 @@ declare const getRequestDetails: (request: Request | IncomingMessage) => Request
129
141
  */
130
142
  declare const getResponseDetails: (response: Response | ServerResponse) => ResponseDetails;
131
143
  //#endregion
132
- export { type ExpressMiddlewareFn, type ExpressMiddlewareHooks, type ExpressMiddlewareOptions, type RequestDetails, type ResponseDetails, attachRPC, attachVite, createMiddleware, createRPCMiddleware, getRequestDetails, getResponseDetails, hasPreParsedBody, isExpressRequest, isExpressResponse, readBody };
144
+ export { type ExpressMiddlewareFn, type ExpressMiddlewareHooks, type ExpressMiddlewareOptions, type RequestDetails, type ResponseDetails, attachRPC, attachVite, createMiddleware, createRPCMiddleware, getRequestDetails, getResponseDetails, hasPreParsedBody, isExpressRequest, isExpressResponse, readBody, redirect };
133
145
  //# sourceMappingURL=express.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"express.d.mts","names":[],"sources":["../../src/express/types.d.ts","../../src/express/createMiddleware.ts","../../src/express/helpers.ts"],"mappings":";;;;;;;;KAgBY,2BAA2B;;;;;KAM3B,uBACV,UAAU,yCAEV,iBAAiB,QAAQ,8BACtB;;;;UAKY;;;;;;;EAOf,UACE,KAAK,kBAAkB,SACvB,KAAK,iBAAiB,UACtB,MAAM,QAAQ,eAAe,iBAC1B;;;;;;KAOK;;EAEV;;EAEA,YAAY,cAAc;;EAE1B;;EAEA,gBAAgB;;EAEhB,eAAe,cAAc,QAAQ,eAAe;;;;;KAM1C;;EAEV;;EAEA;;EAEA,cAAc;;EAEd,SAAS;;EAET;;;;;;;;;;;cCvCW,kBAAkB;;;;;;;;cAyElB,qBAAqB;;;;;;;;iBC3FZ,UAAU,KAAK,UAAO;;;;;;iBAc5B,WAAW,KAAK,SAAS,MAAM;;;;;;;;cAWlC,WAAQ,KACd,UAAiB,oBACrB,QAAQ;;;;;;cAyEE,mBAAgB,KACtB,kBAAkB,YACtB,OAAO;;;;;;cASG,oBAAiB,KACvB,iBAAiB,aACrB,OAAO;;;;;;;cAUG,mBAAgB,KACtB,kBAAkB,YACtB,OAAO;;;;;;;cAUG,oBAAiB,SACnB,UAAiB,oBACzB;;;;;;;cAqBU,qBAAkB,UACnB,WAAkB,mBAC3B"}
1
+ {"version":3,"file":"express.d.mts","names":[],"sources":["../../src/express/types.d.ts","../../src/express/createMiddleware.ts","../../src/express/helpers.ts"],"mappings":";;;;;;;;KAgBY,2BAA2B;;;;;KAM3B,uBACV,UAAU,yCAEV,iBAAiB,QAAQ,8BACtB;;;;UAKY;;;;;;;EAOf,UACE,KAAK,kBAAkB,SACvB,KAAK,iBAAiB,UACtB,MAAM,QAAQ,eAAe,iBAC1B;;;;;;KAOK;;EAEV;;EAEA,YAAY,cAAc;;EAE1B;;EAEA,gBAAgB;;EAEhB,eAAe,cAAc,QAAQ;;;;;KAM3B;;EAEV;;EAEA;;EAEA,cAAc;;EAEd,SAAS;;EAET;;;;;;;;;;;cC1BW,kBAAkB;;;;;;;;cAyElB,qBAAqB;;;;;;;;iBCvGZ,UAAU,KAAK,UAAO;;;;;;iBAc5B,WAAW,KAAK,SAAS,MAAM;;;;;;;;cAWlC,WAAQ,KACd,UAAiB,oBACrB,QAAQ;;;;;;cAqFE,mBAAgB,KACtB,kBAAkB,YACtB,OAAO;;;;;;cASG,oBAAiB,KACvB,iBAAiB,aACrB,OAAO;;;;;;;;;;;;cAeG,WAAQ,KACd,iBAAiB,UAAe,kBACrB;;;;;;;cAkBL,mBAAgB,KACtB,kBAAkB,YACtB,OAAO;;;;;;;cAUG,oBAAiB,SACnB,UAAiB,oBACzB;;;;;;;cAqBU,qBAAkB,UACnB,WAAkB,mBAC3B"}