@textjs/app 0.0.0-stage → 1.0.2

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 (335) hide show
  1. package/README.md +34 -2
  2. package/bin/text.js +29 -0
  3. package/bin/text.ts +13 -0
  4. package/dist/.tsbuildinfo +1 -0
  5. package/dist/cli/build.d.ts +39 -0
  6. package/dist/cli/build.d.ts.map +1 -0
  7. package/dist/cli/build.js +157 -0
  8. package/dist/cli/build.js.map +1 -0
  9. package/dist/cli/config.d.ts +22 -0
  10. package/dist/cli/config.d.ts.map +1 -0
  11. package/dist/cli/config.js +73 -0
  12. package/dist/cli/config.js.map +1 -0
  13. package/dist/cli/dev.d.ts +12 -0
  14. package/dist/cli/dev.d.ts.map +1 -0
  15. package/dist/cli/dev.js +49 -0
  16. package/dist/cli/dev.js.map +1 -0
  17. package/dist/cli/index.d.ts +26 -0
  18. package/dist/cli/index.d.ts.map +1 -0
  19. package/dist/cli/index.js +192 -0
  20. package/dist/cli/index.js.map +1 -0
  21. package/dist/cli/run.d.ts +31 -0
  22. package/dist/cli/run.d.ts.map +1 -0
  23. package/dist/cli/run.js +111 -0
  24. package/dist/cli/run.js.map +1 -0
  25. package/dist/cli/start.d.ts +14 -0
  26. package/dist/cli/start.d.ts.map +1 -0
  27. package/dist/cli/start.js +183 -0
  28. package/dist/cli/start.js.map +1 -0
  29. package/dist/cli/ui.d.ts +35 -0
  30. package/dist/cli/ui.d.ts.map +1 -0
  31. package/dist/cli/ui.js +73 -0
  32. package/dist/cli/ui.js.map +1 -0
  33. package/dist/client.d.ts +34 -0
  34. package/dist/client.d.ts.map +1 -0
  35. package/dist/client.js +93 -0
  36. package/dist/client.js.map +1 -0
  37. package/dist/config.d.ts +143 -0
  38. package/dist/config.d.ts.map +1 -0
  39. package/dist/config.js +28 -0
  40. package/dist/config.js.map +1 -0
  41. package/dist/context.d.ts +44 -0
  42. package/dist/context.d.ts.map +1 -0
  43. package/dist/context.js +26 -0
  44. package/dist/context.js.map +1 -0
  45. package/dist/dev-errors.d.ts +68 -0
  46. package/dist/dev-errors.d.ts.map +1 -0
  47. package/dist/dev-errors.js +121 -0
  48. package/dist/dev-errors.js.map +1 -0
  49. package/dist/devtools.d.ts +64 -0
  50. package/dist/devtools.d.ts.map +1 -0
  51. package/dist/devtools.js +186 -0
  52. package/dist/devtools.js.map +1 -0
  53. package/dist/frame-mirror.d.ts +19 -0
  54. package/dist/frame-mirror.d.ts.map +1 -0
  55. package/dist/frame-mirror.js +141 -0
  56. package/dist/frame-mirror.js.map +1 -0
  57. package/dist/frame-protocol.d.ts +175 -0
  58. package/dist/frame-protocol.d.ts.map +1 -0
  59. package/dist/frame-protocol.js +369 -0
  60. package/dist/frame-protocol.js.map +1 -0
  61. package/dist/history.d.ts +54 -0
  62. package/dist/history.d.ts.map +1 -0
  63. package/dist/history.js +152 -0
  64. package/dist/history.js.map +1 -0
  65. package/dist/index.d.ts +9 -0
  66. package/dist/index.d.ts.map +1 -0
  67. package/dist/index.js +8 -0
  68. package/dist/index.js.map +1 -0
  69. package/dist/link.d.ts +32 -0
  70. package/dist/link.d.ts.map +1 -0
  71. package/dist/link.js +54 -0
  72. package/dist/link.js.map +1 -0
  73. package/dist/live-player.d.ts +83 -0
  74. package/dist/live-player.d.ts.map +1 -0
  75. package/dist/live-player.js +413 -0
  76. package/dist/live-player.js.map +1 -0
  77. package/dist/live-protocol.d.ts +132 -0
  78. package/dist/live-protocol.d.ts.map +1 -0
  79. package/dist/live-protocol.js +278 -0
  80. package/dist/live-protocol.js.map +1 -0
  81. package/dist/live-stub.d.ts +10 -0
  82. package/dist/live-stub.d.ts.map +1 -0
  83. package/dist/live-stub.js +32 -0
  84. package/dist/live-stub.js.map +1 -0
  85. package/dist/mcp/cli.d.ts +22 -0
  86. package/dist/mcp/cli.d.ts.map +1 -0
  87. package/dist/mcp/cli.js +177 -0
  88. package/dist/mcp/cli.js.map +1 -0
  89. package/dist/mcp/frame.d.ts +58 -0
  90. package/dist/mcp/frame.d.ts.map +1 -0
  91. package/dist/mcp/frame.js +169 -0
  92. package/dist/mcp/frame.js.map +1 -0
  93. package/dist/mcp/index.d.ts +15 -0
  94. package/dist/mcp/index.d.ts.map +1 -0
  95. package/dist/mcp/index.js +15 -0
  96. package/dist/mcp/index.js.map +1 -0
  97. package/dist/mcp/inspect.d.ts +95 -0
  98. package/dist/mcp/inspect.d.ts.map +1 -0
  99. package/dist/mcp/inspect.js +386 -0
  100. package/dist/mcp/inspect.js.map +1 -0
  101. package/dist/mcp/keys.d.ts +20 -0
  102. package/dist/mcp/keys.d.ts.map +1 -0
  103. package/dist/mcp/keys.js +87 -0
  104. package/dist/mcp/keys.js.map +1 -0
  105. package/dist/mcp/logs.d.ts +63 -0
  106. package/dist/mcp/logs.d.ts.map +1 -0
  107. package/dist/mcp/logs.js +217 -0
  108. package/dist/mcp/logs.js.map +1 -0
  109. package/dist/mcp/server.d.ts +64 -0
  110. package/dist/mcp/server.d.ts.map +1 -0
  111. package/dist/mcp/server.js +485 -0
  112. package/dist/mcp/server.js.map +1 -0
  113. package/dist/mcp/session.d.ts +230 -0
  114. package/dist/mcp/session.d.ts.map +1 -0
  115. package/dist/mcp/session.js +742 -0
  116. package/dist/mcp/session.js.map +1 -0
  117. package/dist/navigation-errors.d.ts +28 -0
  118. package/dist/navigation-errors.d.ts.map +1 -0
  119. package/dist/navigation-errors.js +43 -0
  120. package/dist/navigation-errors.js.map +1 -0
  121. package/dist/navigation.d.ts +21 -0
  122. package/dist/navigation.d.ts.map +1 -0
  123. package/dist/navigation.js +33 -0
  124. package/dist/navigation.js.map +1 -0
  125. package/dist/router.d.ts +76 -0
  126. package/dist/router.d.ts.map +1 -0
  127. package/dist/router.js +296 -0
  128. package/dist/router.js.map +1 -0
  129. package/dist/routing.d.ts +101 -0
  130. package/dist/routing.d.ts.map +1 -0
  131. package/dist/routing.js +286 -0
  132. package/dist/routing.js.map +1 -0
  133. package/dist/server/adapters/build-step.d.ts +34 -0
  134. package/dist/server/adapters/build-step.d.ts.map +1 -0
  135. package/dist/server/adapters/build-step.js +112 -0
  136. package/dist/server/adapters/build-step.js.map +1 -0
  137. package/dist/server/adapters/bun.d.ts +8 -0
  138. package/dist/server/adapters/bun.d.ts.map +1 -0
  139. package/dist/server/adapters/bun.js +89 -0
  140. package/dist/server/adapters/bun.js.map +1 -0
  141. package/dist/server/adapters/bundle.d.ts +21 -0
  142. package/dist/server/adapters/bundle.d.ts.map +1 -0
  143. package/dist/server/adapters/bundle.js +86 -0
  144. package/dist/server/adapters/bundle.js.map +1 -0
  145. package/dist/server/adapters/cloudflare.d.ts +29 -0
  146. package/dist/server/adapters/cloudflare.d.ts.map +1 -0
  147. package/dist/server/adapters/cloudflare.js +105 -0
  148. package/dist/server/adapters/cloudflare.js.map +1 -0
  149. package/dist/server/adapters/index.d.ts +10 -0
  150. package/dist/server/adapters/index.d.ts.map +1 -0
  151. package/dist/server/adapters/index.js +24 -0
  152. package/dist/server/adapters/index.js.map +1 -0
  153. package/dist/server/adapters/runtime-node.d.ts +16 -0
  154. package/dist/server/adapters/runtime-node.d.ts.map +1 -0
  155. package/dist/server/adapters/runtime-node.js +29 -0
  156. package/dist/server/adapters/runtime-node.js.map +1 -0
  157. package/dist/server/adapters/runtime.d.ts +42 -0
  158. package/dist/server/adapters/runtime.d.ts.map +1 -0
  159. package/dist/server/adapters/runtime.js +115 -0
  160. package/dist/server/adapters/runtime.js.map +1 -0
  161. package/dist/server/adapters/shared.d.ts +66 -0
  162. package/dist/server/adapters/shared.d.ts.map +1 -0
  163. package/dist/server/adapters/shared.js +124 -0
  164. package/dist/server/adapters/shared.js.map +1 -0
  165. package/dist/server/adapters/static.d.ts +9 -0
  166. package/dist/server/adapters/static.d.ts.map +1 -0
  167. package/dist/server/adapters/static.js +70 -0
  168. package/dist/server/adapters/static.js.map +1 -0
  169. package/dist/server/adapters/vercel.d.ts +43 -0
  170. package/dist/server/adapters/vercel.d.ts.map +1 -0
  171. package/dist/server/adapters/vercel.js +119 -0
  172. package/dist/server/adapters/vercel.js.map +1 -0
  173. package/dist/server/bun-jsx.d.ts +18 -0
  174. package/dist/server/bun-jsx.d.ts.map +1 -0
  175. package/dist/server/bun-jsx.js +56 -0
  176. package/dist/server/bun-jsx.js.map +1 -0
  177. package/dist/server/cache-route.d.ts +38 -0
  178. package/dist/server/cache-route.d.ts.map +1 -0
  179. package/dist/server/cache-route.js +109 -0
  180. package/dist/server/cache-route.js.map +1 -0
  181. package/dist/server/cache.d.ts +131 -0
  182. package/dist/server/cache.d.ts.map +1 -0
  183. package/dist/server/cache.js +409 -0
  184. package/dist/server/cache.js.map +1 -0
  185. package/dist/server/frames.d.ts +45 -0
  186. package/dist/server/frames.d.ts.map +1 -0
  187. package/dist/server/frames.js +294 -0
  188. package/dist/server/frames.js.map +1 -0
  189. package/dist/server/http.d.ts +80 -0
  190. package/dist/server/http.d.ts.map +1 -0
  191. package/dist/server/http.js +453 -0
  192. package/dist/server/http.js.map +1 -0
  193. package/dist/server/jsx-dev-runtime.d.ts +5 -0
  194. package/dist/server/jsx-dev-runtime.d.ts.map +1 -0
  195. package/dist/server/jsx-dev-runtime.js +6 -0
  196. package/dist/server/jsx-dev-runtime.js.map +1 -0
  197. package/dist/server/jsx-runtime.d.ts +10 -0
  198. package/dist/server/jsx-runtime.d.ts.map +1 -0
  199. package/dist/server/jsx-runtime.js +11 -0
  200. package/dist/server/jsx-runtime.js.map +1 -0
  201. package/dist/server/live-app.d.ts +87 -0
  202. package/dist/server/live-app.d.ts.map +1 -0
  203. package/dist/server/live-app.js +307 -0
  204. package/dist/server/live-app.js.map +1 -0
  205. package/dist/server/live-build.d.ts +70 -0
  206. package/dist/server/live-build.d.ts.map +1 -0
  207. package/dist/server/live-build.js +222 -0
  208. package/dist/server/live-build.js.map +1 -0
  209. package/dist/server/live-dev.d.ts +62 -0
  210. package/dist/server/live-dev.d.ts.map +1 -0
  211. package/dist/server/live-dev.js +313 -0
  212. package/dist/server/live-dev.js.map +1 -0
  213. package/dist/server/live-manifest.d.ts +23 -0
  214. package/dist/server/live-manifest.d.ts.map +1 -0
  215. package/dist/server/live-manifest.js +7 -0
  216. package/dist/server/live-manifest.js.map +1 -0
  217. package/dist/server/live-prod.d.ts +30 -0
  218. package/dist/server/live-prod.d.ts.map +1 -0
  219. package/dist/server/live-prod.js +97 -0
  220. package/dist/server/live-prod.js.map +1 -0
  221. package/dist/server/live-routes.d.ts +72 -0
  222. package/dist/server/live-routes.d.ts.map +1 -0
  223. package/dist/server/live-routes.js +274 -0
  224. package/dist/server/live-routes.js.map +1 -0
  225. package/dist/server/live-web.d.ts +16 -0
  226. package/dist/server/live-web.d.ts.map +1 -0
  227. package/dist/server/live-web.js +159 -0
  228. package/dist/server/live-web.js.map +1 -0
  229. package/dist/server/live.d.ts +138 -0
  230. package/dist/server/live.d.ts.map +1 -0
  231. package/dist/server/live.js +542 -0
  232. package/dist/server/live.js.map +1 -0
  233. package/dist/server/middleware-node.d.ts +48 -0
  234. package/dist/server/middleware-node.d.ts.map +1 -0
  235. package/dist/server/middleware-node.js +167 -0
  236. package/dist/server/middleware-node.js.map +1 -0
  237. package/dist/server/middleware-runtime.d.ts +97 -0
  238. package/dist/server/middleware-runtime.d.ts.map +1 -0
  239. package/dist/server/middleware-runtime.js +447 -0
  240. package/dist/server/middleware-runtime.js.map +1 -0
  241. package/dist/server/middleware.d.ts +145 -0
  242. package/dist/server/middleware.d.ts.map +1 -0
  243. package/dist/server/middleware.js +365 -0
  244. package/dist/server/middleware.js.map +1 -0
  245. package/dist/server/ppr.d.ts +22 -0
  246. package/dist/server/ppr.d.ts.map +1 -0
  247. package/dist/server/ppr.js +58 -0
  248. package/dist/server/ppr.js.map +1 -0
  249. package/dist/server/scan.d.ts +48 -0
  250. package/dist/server/scan.d.ts.map +1 -0
  251. package/dist/server/scan.js +161 -0
  252. package/dist/server/scan.js.map +1 -0
  253. package/dist/server/server-components.d.ts +65 -0
  254. package/dist/server/server-components.d.ts.map +1 -0
  255. package/dist/server/server-components.js +215 -0
  256. package/dist/server/server-components.js.map +1 -0
  257. package/dist/terminal.d.ts +5 -0
  258. package/dist/terminal.d.ts.map +1 -0
  259. package/dist/terminal.js +5 -0
  260. package/dist/terminal.js.map +1 -0
  261. package/dist/testing.d.ts +5 -0
  262. package/dist/testing.d.ts.map +1 -0
  263. package/dist/testing.js +5 -0
  264. package/dist/testing.js.map +1 -0
  265. package/dist/vite-plugin.d.ts +51 -0
  266. package/dist/vite-plugin.d.ts.map +1 -0
  267. package/dist/vite-plugin.js +403 -0
  268. package/dist/vite-plugin.js.map +1 -0
  269. package/package.json +130 -4
  270. package/src/cli/build.ts +182 -0
  271. package/src/cli/config.ts +72 -0
  272. package/src/cli/dev.ts +54 -0
  273. package/src/cli/index.ts +185 -0
  274. package/src/cli/run.ts +124 -0
  275. package/src/cli/start.ts +194 -0
  276. package/src/cli/ui.ts +93 -0
  277. package/src/client.tsx +122 -0
  278. package/src/config.ts +155 -0
  279. package/src/context.ts +69 -0
  280. package/src/dev-errors.ts +167 -0
  281. package/src/devtools.tsx +419 -0
  282. package/src/frame-mirror.ts +145 -0
  283. package/src/frame-protocol.ts +435 -0
  284. package/src/history.ts +201 -0
  285. package/src/index.ts +9 -0
  286. package/src/link.tsx +108 -0
  287. package/src/live-player.ts +449 -0
  288. package/src/live-protocol.ts +317 -0
  289. package/src/live-stub.tsx +32 -0
  290. package/src/mcp/cli.ts +188 -0
  291. package/src/mcp/frame.ts +224 -0
  292. package/src/mcp/index.ts +15 -0
  293. package/src/mcp/inspect.ts +445 -0
  294. package/src/mcp/keys.ts +86 -0
  295. package/src/mcp/logs.ts +225 -0
  296. package/src/mcp/server.ts +595 -0
  297. package/src/mcp/session.ts +821 -0
  298. package/src/navigation-errors.ts +60 -0
  299. package/src/navigation.ts +44 -0
  300. package/src/router.tsx +497 -0
  301. package/src/routing.ts +344 -0
  302. package/src/server/adapters/build-step.ts +135 -0
  303. package/src/server/adapters/bun.ts +92 -0
  304. package/src/server/adapters/bundle.ts +97 -0
  305. package/src/server/adapters/cloudflare.ts +111 -0
  306. package/src/server/adapters/index.ts +29 -0
  307. package/src/server/adapters/runtime-node.ts +34 -0
  308. package/src/server/adapters/runtime.ts +145 -0
  309. package/src/server/adapters/shared.ts +159 -0
  310. package/src/server/adapters/static.ts +68 -0
  311. package/src/server/adapters/vercel.ts +149 -0
  312. package/src/server/bun-jsx.ts +65 -0
  313. package/src/server/cache-route.ts +139 -0
  314. package/src/server/cache.ts +498 -0
  315. package/src/server/frames.ts +326 -0
  316. package/src/server/http.ts +467 -0
  317. package/src/server/jsx-dev-runtime.ts +9 -0
  318. package/src/server/jsx-runtime.ts +13 -0
  319. package/src/server/live-app.tsx +374 -0
  320. package/src/server/live-build.ts +258 -0
  321. package/src/server/live-dev.ts +357 -0
  322. package/src/server/live-manifest.ts +25 -0
  323. package/src/server/live-prod.ts +120 -0
  324. package/src/server/live-routes.ts +298 -0
  325. package/src/server/live-web.ts +170 -0
  326. package/src/server/live.ts +551 -0
  327. package/src/server/middleware-node.ts +168 -0
  328. package/src/server/middleware-runtime.ts +478 -0
  329. package/src/server/middleware.ts +451 -0
  330. package/src/server/ppr.ts +62 -0
  331. package/src/server/scan.ts +190 -0
  332. package/src/server/server-components.ts +224 -0
  333. package/src/terminal.ts +5 -0
  334. package/src/testing.ts +5 -0
  335. package/src/vite-plugin.ts +413 -0
@@ -0,0 +1,467 @@
1
+ /**
2
+ * Node HTTP helpers shared by `text dev` middleware and `text start`:
3
+ * Web `Request` / `Response` bridging, route handlers, rewrites and proxying.
4
+ */
5
+
6
+ import type { IncomingMessage, ServerResponse } from "node:http";
7
+ import { txError } from "@textjs/core";
8
+ import type { Rewrite } from "../config";
9
+ import type { RouteParams } from "../routing";
10
+
11
+ export const HTTP_METHODS = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] as const;
12
+ export type HttpMethod = (typeof HTTP_METHODS)[number];
13
+
14
+ export type RouteHandler = (request: Request, context: { params: RouteParams }) => Response | Promise<Response>;
15
+ export type RouteHandlerModule = Partial<Record<HttpMethod, RouteHandler>> & Record<string, unknown>;
16
+
17
+ /** Set by `middleware.ts` handling (see `server/middleware-node.ts`). */
18
+ const WEB_REQUEST = Symbol.for("textjs.webRequest");
19
+
20
+ /**
21
+ * The request target as a path and query with exactly one leading slash.
22
+ * `//evil.com/x` and `/\evil.com/x` would otherwise resolve to another host
23
+ * against the origin (`request.url` → `http://evil.com/x`); absolute-form
24
+ * targets (`GET http://evil.com/x`) keep only their path and query.
25
+ */
26
+ export function normalizeRequestTarget(raw: string | undefined): string {
27
+ let target = raw ?? "/";
28
+ if (!target.startsWith("/")) {
29
+ try {
30
+ const u = new URL(target);
31
+ target = `${u.pathname}${u.search}`;
32
+ } catch {
33
+ return "/";
34
+ }
35
+ }
36
+ return target.replace(/^[/\\]+/, "/");
37
+ }
38
+
39
+ /** Resolve a request target against `origin`, never leaving it. */
40
+ export function requestUrl(raw: string | undefined, origin: string): URL {
41
+ const base = new URL(origin);
42
+ const url = new URL(normalizeRequestTarget(raw), base);
43
+ return url.origin === base.origin ? url : new URL("/", base);
44
+ }
45
+
46
+ const HOST_HEADER = /^(?:[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)*\.?|\[[0-9A-Fa-f:.]+\])(?::\d{1,5})?$/;
47
+ const IPV4 = /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/;
48
+
49
+ /** A syntactically valid `Host` header (`name[:port]` or `[ipv6][:port]`). */
50
+ export function isValidHostHeader(host: string | undefined): host is string {
51
+ return typeof host === "string" && host.length <= 255 && HOST_HEADER.test(host);
52
+ }
53
+
54
+ /** `localhost`, `*.localhost`, IP literals and `allowed` (a leading `.` allows subdomains). */
55
+ export function isAllowedHost(hostHeader: string | undefined, allowed: readonly string[] | true = []): boolean {
56
+ if (allowed === true) return true;
57
+ if (!isValidHostHeader(hostHeader)) return false;
58
+ const host = hostHeader.trim().toLowerCase();
59
+ if (host.startsWith("[")) return true;
60
+ const hostname = host.replace(/:\d+$/, "").replace(/\.$/, "");
61
+ if (IPV4.test(hostname) || hostname === "localhost" || hostname.endsWith(".localhost")) return true;
62
+ for (const a of allowed) {
63
+ const name = a.toLowerCase();
64
+ if (name === hostname) return true;
65
+ if (name.startsWith(".") && (name.slice(1) === hostname || hostname.endsWith(name))) return true;
66
+ }
67
+ return false;
68
+ }
69
+
70
+ function firstHeader(value: string | string[] | undefined): string | undefined {
71
+ const v = Array.isArray(value) ? value[0] : value;
72
+ return v?.split(",")[0]?.trim() || undefined;
73
+ }
74
+
75
+ export interface OriginOptions {
76
+ /** Fixed public origin (wins over headers). */
77
+ origin?: string;
78
+ /** Read `X-Forwarded-Host` / `X-Forwarded-Proto`. */
79
+ trustProxy?: boolean;
80
+ /** Protocol when not behind a trusted proxy (default `http`). */
81
+ protocol?: "http" | "https";
82
+ }
83
+
84
+ /** The origin a Node request was addressed to (`http://localhost:3000`), from validated headers only. */
85
+ export function requestOrigin(req: Pick<IncomingMessage, "headers">, options: OriginOptions = {}): string {
86
+ if (options.origin) return new URL(options.origin).origin;
87
+ let host = firstHeader(req.headers.host);
88
+ let proto = options.protocol ?? "http";
89
+ if (options.trustProxy) {
90
+ const fwdHost = firstHeader(req.headers["x-forwarded-host"]);
91
+ if (isValidHostHeader(fwdHost)) host = fwdHost;
92
+ const fwdProto = firstHeader(req.headers["x-forwarded-proto"])?.toLowerCase();
93
+ if (fwdProto === "http" || fwdProto === "https") proto = fwdProto;
94
+ }
95
+ return `${proto}://${isValidHostHeader(host) ? host : "localhost"}`;
96
+ }
97
+
98
+ /** A Web `ReadableStream` over a Node request body with backpressure; safe against late chunks after cancel. */
99
+ function nodeBodyStream(req: IncomingMessage): ReadableStream<Uint8Array> {
100
+ let finished = false;
101
+ let onData: (chunk: Buffer | string) => void = () => undefined;
102
+ let onEnd: () => void = () => undefined;
103
+ let onError: (error: Error) => void = () => undefined;
104
+ const detach = (): void => {
105
+ finished = true;
106
+ req.off("data", onData);
107
+ req.off("end", onEnd);
108
+ req.off("error", onError);
109
+ req.off("aborted", onAborted);
110
+ };
111
+ const onAborted = (): void => onError(new Error("The client aborted the request."));
112
+ return new ReadableStream<Uint8Array>(
113
+ {
114
+ start(controller) {
115
+ onData = (chunk) => {
116
+ if (finished) return;
117
+ try {
118
+ controller.enqueue(typeof chunk === "string" ? new TextEncoder().encode(chunk) : new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));
119
+ } catch {
120
+ detach();
121
+ req.resume();
122
+ return;
123
+ }
124
+ if ((controller.desiredSize ?? 1) <= 0) req.pause();
125
+ };
126
+ onEnd = () => {
127
+ if (finished) return;
128
+ detach();
129
+ try {
130
+ controller.close();
131
+ } catch {
132
+ // already closed or errored
133
+ }
134
+ };
135
+ onError = (error) => {
136
+ if (finished) return;
137
+ detach();
138
+ try {
139
+ controller.error(error);
140
+ } catch {
141
+ // already closed or errored
142
+ }
143
+ };
144
+ req.on("data", onData);
145
+ req.on("end", onEnd);
146
+ req.on("error", onError);
147
+ req.on("aborted", onAborted);
148
+ },
149
+ pull() {
150
+ if (!finished && req.isPaused()) req.resume();
151
+ },
152
+ cancel() {
153
+ detach();
154
+ // Discard the rest of the upload so the response can still be written.
155
+ if (!req.destroyed) req.resume();
156
+ },
157
+ },
158
+ { highWaterMark: 64 * 1024, size: (chunk) => chunk?.byteLength ?? 0 },
159
+ );
160
+ }
161
+
162
+ /**
163
+ * Read a request body as text, at most `max` bytes (`null` when larger).
164
+ * Prefers the body `middleware.ts` already took over from the Node stream.
165
+ */
166
+ export async function readRequestBody(req: IncomingMessage, max: number): Promise<string | null> {
167
+ const stash = req as unknown as Record<symbol, Request | undefined>;
168
+ const forwarded = stash[WEB_REQUEST];
169
+ if (forwarded) delete stash[WEB_REQUEST];
170
+ const stream = forwarded ? forwarded.body : nodeBodyStream(req);
171
+ if (!stream) return "";
172
+ const reader = stream.getReader();
173
+ const chunks: Uint8Array[] = [];
174
+ let size = 0;
175
+ for (;;) {
176
+ const { done, value } = await reader.read();
177
+ if (done) break;
178
+ size += value.byteLength;
179
+ if (size > max) {
180
+ await reader.cancel().catch(() => undefined);
181
+ return null;
182
+ }
183
+ chunks.push(value);
184
+ }
185
+ const out = new Uint8Array(size);
186
+ let offset = 0;
187
+ for (const c of chunks) {
188
+ out.set(c, offset);
189
+ offset += c.byteLength;
190
+ }
191
+ return new TextDecoder().decode(out);
192
+ }
193
+
194
+ /**
195
+ * Bridge a Node request into a Web `Request`. Its `signal` aborts as soon as the
196
+ * client disconnects, so streaming handlers (SSE) can clean up immediately.
197
+ */
198
+ export function toWebRequest(req: IncomingMessage, origin: string, res?: ServerResponse): Request {
199
+ const url = requestUrl(req.url, origin);
200
+ // `middleware.ts` already bridged (and possibly re-headered) this request; reuse its body stream.
201
+ const forwarded = (req as unknown as Record<symbol, Request | undefined>)[WEB_REQUEST];
202
+ if (forwarded) {
203
+ delete (req as unknown as Record<symbol, Request | undefined>)[WEB_REQUEST];
204
+ const init: RequestInit & { duplex?: "half" } = { method: forwarded.method, headers: forwarded.headers, signal: forwarded.signal };
205
+ if (forwarded.body) {
206
+ init.body = forwarded.body;
207
+ init.duplex = "half";
208
+ }
209
+ return new Request(url, init);
210
+ }
211
+ const controller = new AbortController();
212
+ const abort = (): void => {
213
+ if (!controller.signal.aborted) controller.abort();
214
+ };
215
+ req.on("aborted", abort);
216
+ // `close` before `end()` means the client disconnected (tab closed, EventSource dropped).
217
+ res?.on("close", () => {
218
+ if (!res.writableEnded) abort();
219
+ });
220
+ const headers = new Headers();
221
+ for (const [k, v] of Object.entries(req.headers)) {
222
+ if (v === undefined) continue;
223
+ if (Array.isArray(v)) for (const item of v) headers.append(k, item);
224
+ else headers.set(k, v);
225
+ }
226
+ const method = (req.method ?? "GET").toUpperCase();
227
+ const hasBody = method !== "GET" && method !== "HEAD";
228
+ const init: RequestInit & { duplex?: "half" } = { method, headers, signal: controller.signal };
229
+ if (hasBody) {
230
+ init.body = nodeBodyStream(req);
231
+ init.duplex = "half";
232
+ }
233
+ return new Request(url, init);
234
+ }
235
+
236
+ /** `Cache-Control` for responses that must not be stored by shared caches. */
237
+ export const PRIVATE_CACHE_CONTROL = "private, no-cache, no-store, max-age=0, must-revalidate";
238
+
239
+ /** Responses whose `Cache-Control` the route cache generated (not set by the handler). */
240
+ const generatedCacheControl = new WeakSet<Response>();
241
+
242
+ export function markGeneratedCacheControl(response: Response): Response {
243
+ generatedCacheControl.add(response);
244
+ return response;
245
+ }
246
+
247
+ export function hasGeneratedCacheControl(response: Response): boolean {
248
+ return generatedCacheControl.has(response);
249
+ }
250
+
251
+ const MIDDLEWARE_STATE = Symbol.for("textjs.middlewareState");
252
+
253
+ /** What `middleware.ts` decided for a Node response that is completed later (see `sendWebResponse`). */
254
+ export interface MiddlewareResponseState {
255
+ /** Middleware set cookies, rewrote or changed request headers: never let shared caches store the result. */
256
+ private?: boolean;
257
+ /** `NextResponse.next({ status })` / `rewrite(url, { status })`. */
258
+ status?: number;
259
+ }
260
+
261
+ export function setMiddlewareState(res: ServerResponse, state: MiddlewareResponseState): void {
262
+ (res as unknown as Record<symbol, MiddlewareResponseState>)[MIDDLEWARE_STATE] = state;
263
+ }
264
+
265
+ function middlewareState(res: ServerResponse): MiddlewareResponseState | undefined {
266
+ return (res as unknown as Record<symbol, MiddlewareResponseState | undefined>)[MIDDLEWARE_STATE];
267
+ }
268
+
269
+ export async function sendWebResponse(res: ServerResponse, response: Response, method = "GET"): Promise<void> {
270
+ const state = middlewareState(res);
271
+ res.statusCode = state?.status ?? response.status;
272
+ if (response.statusText && state?.status === undefined) res.statusMessage = response.statusText;
273
+ const cookies = typeof response.headers.getSetCookie === "function" ? response.headers.getSetCookie() : [];
274
+ response.headers.forEach((value, key) => {
275
+ if (key === "set-cookie") return;
276
+ res.setHeader(key, value);
277
+ });
278
+ if (state?.private && hasGeneratedCacheControl(response)) res.setHeader("cache-control", PRIVATE_CACHE_CONTROL);
279
+ if (cookies.length > 0) {
280
+ // Keep cookies `middleware.ts` already set on this response.
281
+ const prior = res.getHeader("set-cookie");
282
+ res.setHeader("set-cookie", [...(prior === undefined ? [] : Array.isArray(prior) ? prior : [String(prior)]), ...cookies]);
283
+ }
284
+ if (method === "HEAD" || !response.body) {
285
+ res.end();
286
+ return;
287
+ }
288
+ const reader = response.body.getReader();
289
+ const cancel = (): void => {
290
+ reader.cancel().catch(() => undefined);
291
+ };
292
+ res.on("close", cancel);
293
+ try {
294
+ for (;;) {
295
+ if (res.writableEnded || res.destroyed) {
296
+ cancel();
297
+ return;
298
+ }
299
+ const { done, value } = await reader.read();
300
+ if (done) break;
301
+ if (res.writableEnded || res.destroyed) {
302
+ cancel();
303
+ return;
304
+ }
305
+ res.write(value);
306
+ }
307
+ } catch {
308
+ // The client went away mid-stream (the reader was cancelled).
309
+ return;
310
+ } finally {
311
+ res.off("close", cancel);
312
+ }
313
+ res.end();
314
+ }
315
+
316
+ export function allowedMethods(mod: RouteHandlerModule): string[] {
317
+ const out = HTTP_METHODS.filter((m) => typeof mod[m] === "function") as string[];
318
+ if (out.includes("GET") && !out.includes("HEAD")) out.push("HEAD");
319
+ if (!out.includes("OPTIONS")) out.push("OPTIONS");
320
+ return out;
321
+ }
322
+
323
+ /** Invoke an `app/**\/route.ts` handler with Next.js semantics (HEAD → GET, auto OPTIONS, 405). */
324
+ export async function runRouteHandler(mod: RouteHandlerModule, request: Request, params: RouteParams): Promise<Response> {
325
+ const method = request.method.toUpperCase() as HttpMethod;
326
+ let handler = mod[method];
327
+ if (!handler && method === "HEAD") handler = mod.GET;
328
+ if (!handler) {
329
+ const allow = allowedMethods(mod).join(", ");
330
+ if (method === "OPTIONS") return new Response(null, { status: 204, headers: { Allow: allow } });
331
+ return new Response(null, { status: 405, headers: { Allow: allow } });
332
+ }
333
+ const result = await handler(request, { params });
334
+ if (!(result instanceof Response)) {
335
+ throw txError("TX016", method, new URL(request.url).pathname);
336
+ }
337
+ return result;
338
+ }
339
+
340
+ // ---------------------------------------------------------------------------
341
+ // Rewrites
342
+ // ---------------------------------------------------------------------------
343
+
344
+ export interface CompiledRewrite {
345
+ rewrite: Rewrite;
346
+ match(pathname: string): Record<string, string> | null;
347
+ destination(params: Record<string, string>, search: string): string;
348
+ }
349
+
350
+ function escapeRe(s: string): string {
351
+ return s.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
352
+ }
353
+
354
+ /** Compile `/docs/:slug`, `/blog/:path*`, `/a/:rest+` source patterns. */
355
+ export function compileRewrite(rewrite: Rewrite): CompiledRewrite {
356
+ const names: string[] = [];
357
+ let re = "^";
358
+ for (const part of rewrite.source.split("/").filter(Boolean)) {
359
+ const m = /^:([A-Za-z_][\w]*)([*+?])?$/.exec(part);
360
+ if (!m) {
361
+ re += `/${escapeRe(part)}`;
362
+ continue;
363
+ }
364
+ names.push(m[1] as string);
365
+ const mod = m[2];
366
+ if (mod === "*") re += "(?:/(.*))?";
367
+ else if (mod === "+") re += "/(.+)";
368
+ else if (mod === "?") re += "(?:/([^/]+))?";
369
+ else re += "/([^/]+)";
370
+ }
371
+ re += "/?$";
372
+ const regex = new RegExp(re || "^/$");
373
+ return {
374
+ rewrite,
375
+ match(pathname) {
376
+ const m = regex.exec(pathname);
377
+ if (!m) return null;
378
+ const out: Record<string, string> = {};
379
+ names.forEach((n, i) => {
380
+ out[n] = m[i + 1] ?? "";
381
+ });
382
+ return out;
383
+ },
384
+ destination(params, search) {
385
+ let dest = rewrite.destination.replace(/:([A-Za-z_][\w]*)[*+?]?/g, (_, n: string) => params[n] ?? "");
386
+ if (!/^[a-z]+:\/\//i.test(dest)) dest = dest.replace(/\/{2,}/g, "/");
387
+ if (search && !dest.includes("?")) dest += search;
388
+ return dest;
389
+ },
390
+ };
391
+ }
392
+
393
+ export function applyRewrites(rewrites: readonly CompiledRewrite[], url: string): string | null {
394
+ const u = new URL(url, "http://text.local");
395
+ for (const r of rewrites) {
396
+ const params = r.match(u.pathname);
397
+ if (params) return r.destination(params, u.search);
398
+ }
399
+ return null;
400
+ }
401
+
402
+ const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
403
+
404
+ export function isLoopbackUrl(url: string): boolean {
405
+ try {
406
+ return LOOPBACK_HOSTS.has(new URL(url).hostname);
407
+ } catch {
408
+ return false;
409
+ }
410
+ }
411
+
412
+ const HOP_BY_HOP = ["connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "proxy-connection", "te", "trailer", "transfer-encoding", "upgrade"];
413
+
414
+ /** Remove hop-by-hop headers (and any named by `Connection`) before forwarding a message. */
415
+ export function stripHopByHop(headers: Headers): Headers {
416
+ for (const name of (headers.get("connection") ?? "").split(",")) {
417
+ const n = name.trim().toLowerCase();
418
+ if (n) headers.delete(n);
419
+ }
420
+ for (const h of HOP_BY_HOP) headers.delete(h);
421
+ return headers;
422
+ }
423
+
424
+ /** Forward `request` to an absolute URL (a middleware or config rewrite). Credentials are stripped for third-party origins. */
425
+ export async function proxyFetch(request: Request, target: string | URL, options: { stripCredentials?: boolean } = {}): Promise<Response> {
426
+ const headers = stripHopByHop(new Headers(request.headers));
427
+ headers.delete("host");
428
+ headers.delete("content-length");
429
+ if (options.stripCredentials !== false) {
430
+ headers.delete("cookie");
431
+ headers.delete("authorization");
432
+ }
433
+ const init: RequestInit & { duplex?: "half" } = { method: request.method, headers, redirect: "manual", signal: request.signal };
434
+ if (request.body && request.method !== "GET" && request.method !== "HEAD") {
435
+ init.body = request.body;
436
+ init.duplex = "half";
437
+ }
438
+ try {
439
+ const upstream = await fetch(target, init);
440
+ const out = stripHopByHop(new Headers(upstream.headers));
441
+ out.delete("content-encoding");
442
+ out.delete("content-length");
443
+ // An upstream must never steer this server's own routing.
444
+ for (const key of [...out.keys()]) if (key.startsWith("x-middleware-")) out.delete(key);
445
+ return new Response(upstream.body, { status: upstream.status, statusText: upstream.statusText, headers: out });
446
+ } catch (error) {
447
+ if (request.signal.aborted) throw error;
448
+ return new Response(`▼ Text.js proxy error: ${String(target)}\n${error instanceof Error ? error.message : String(error)}`, {
449
+ status: 502,
450
+ headers: { "content-type": "text/plain; charset=utf-8" },
451
+ });
452
+ }
453
+ }
454
+
455
+ /** Forward a request to an absolute URL and pipe the response back. */
456
+ export async function proxyRequest(req: IncomingMessage, res: ServerResponse, target: string, origin: string): Promise<void> {
457
+ const incoming = toWebRequest(req, origin, res);
458
+ let response: Response;
459
+ try {
460
+ // Never forward the visitor's credentials to a third-party origin.
461
+ response = await proxyFetch(incoming, target, { stripCredentials: !isLoopbackUrl(target) });
462
+ } catch {
463
+ res.destroy();
464
+ return;
465
+ }
466
+ await sendWebResponse(res, response, incoming.method);
467
+ }
@@ -0,0 +1,9 @@
1
+ /** Development twin of `./jsx-runtime` (aliases `react/jsx-dev-runtime`). */
2
+
3
+ import { Fragment, jsxDEV as reactJsxDEV } from "react/jsx-dev-runtime";
4
+ import { resolveServerType } from "./server-components";
5
+
6
+ export { Fragment };
7
+
8
+ export const jsxDEV: typeof reactJsxDEV = (type, props, key, isStatic, source, self) =>
9
+ reactJsxDEV(resolveServerType(type), props, key, isStatic, source, self);
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Server-side `react/jsx-runtime` used for server-rendered console routes:
3
+ * identical to React's, except `async function` components are wrapped by
4
+ * {@link serverComponent} so they can suspend on the console reconciler.
5
+ */
6
+
7
+ import { Fragment, jsx as reactJsx, jsxs as reactJsxs } from "react/jsx-runtime";
8
+ import { resolveServerType } from "./server-components";
9
+
10
+ export { Fragment };
11
+
12
+ export const jsx: typeof reactJsx = (type, props, key) => reactJsx(resolveServerType(type), props, key);
13
+ export const jsxs: typeof reactJsxs = (type, props, key) => reactJsxs(resolveServerType(type), props, key);