@stacksjs/server 0.70.294 → 0.70.297

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/dist/proxy.d.ts CHANGED
@@ -1,7 +1,76 @@
1
- /**` prefix, or uses a verb that never matches a
2
- * static stx page render. Without the verb rule,
3
- * `route.post('/subscribe', ...)` declared at the root hits stx-serve
4
- * and 404s.
1
+ /**
2
+ * Normalise app configuration into the rules the predicate reads.
3
+ *
4
+ * `/api/` is always present: it is the prefix the framework's own generated
5
+ * routes use, and an app that dropped it would find its auto-CRUD endpoints
6
+ * unreachable with no indication why. Configuring `prefixes` ADDS to it.
7
+ *
8
+ * Resolve this once at boot rather than per request. `@stacksjs/config`
9
+ * populates `overrides` asynchronously, so a per-request read would answer
10
+ * differently depending on how far boot had progressed.
5
11
  */
6
- export declare function isApiBoundRequest(req: Request, pathname: string): boolean;
12
+ export declare function resolveApiProxyRules(input?: ApiProxyConfig): ApiProxyRules;
13
+ /**`
14
+ * prefix, or a mutating verb. Callers that have resolved the app's
15
+ * configuration pass it in.
16
+ */
17
+ export declare function isApiBoundRequest(req: Request, pathname: string, rules?: ApiProxyRules): boolean;
18
+ /**
19
+ * A one-line summary of what will be forwarded, for the dev server's boot
20
+ * output.
21
+ *
22
+ * Asked for directly in stacksjs/stacks#2230: a 404 on a route you know you
23
+ * registered is very hard to diagnose when the rule that swallowed it is
24
+ * invisible.
25
+ */
26
+ export declare function describeApiProxyRules(rules: ApiProxyRules): string;
7
27
  export declare function proxyToBackend(req: Request, backendBase: string, stripPrefix?: string): Promise<Response>;
28
+ /**` traffic behaves identically
29
+ * in both topologies (stacksjs/stacks#1950).
30
+ */
31
+ /** The prefix that is always API-bound, with or without configuration. */
32
+ export declare const DEFAULT_API_PREFIX: '/api/';
33
+ /**
34
+ * Verbs that never match a static stx page render, so they always belong to
35
+ * bun-router. Without this rule, `route.post('/subscribe', ...)` declared at
36
+ * the root hits stx-serve and 404s.
37
+ */
38
+ export declare const DEFAULT_API_METHODS: readonly string[];
39
+ /**
40
+ * What the views server forwards to the API process.
41
+ *
42
+ * ## Why this is configuration and not a route lookup
43
+ *
44
+ * The obvious fix for stacksjs/stacks#2230 is to ask the router:
45
+ * `route.getAllowedMethods(pathname)` already answers exactly the right
46
+ * question. It is not reachable from here. `isApiBoundRequest` is called from
47
+ * an `onRequest` hook inside the VIEWS process, and the route table is
48
+ * registered in the API process — a different process in `buddy dev`, and
49
+ * potentially a different host in production, where `production-server.ts`
50
+ * proxies to an `apiBase` URL. Importing the app's route files into the views
51
+ * process to consult them would execute route registration there, pulling the
52
+ * models and database in with it, and in a split deployment the views site may
53
+ * not ship that code at all.
54
+ *
55
+ * So the app declares it, the way Next's `rewrites()` does. Empty by default,
56
+ * so nothing changes for an app that does not set it.
57
+ *
58
+ * ## The cost of adding a path here
59
+ *
60
+ * stx's `onRequest` runs BEFORE static file serving, so any path this matches
61
+ * permanently shadows a `public/` file of the same name. Adding `/script.js`
62
+ * means `public/script.js` stops being reachable. That is a fine trade when
63
+ * the API really does own the path, and a confusing one otherwise, which is
64
+ * why `describeApiProxyRules` exists and the dev server prints it at boot.
65
+ */
66
+ export declare interface ApiProxyRules {
67
+ prefixes: string[]
68
+ paths: string[]
69
+ methods: Set<string>
70
+ }
71
+ /** The shape an app writes in `config/server.ts` under `proxy`. */
72
+ export declare interface ApiProxyConfig {
73
+ prefixes?: readonly string[]
74
+ paths?: readonly string[]
75
+ methods?: readonly string[]
76
+ }
package/dist/proxy.js CHANGED
@@ -1 +1 @@
1
- const API_METHODS=new Set(["POST","PUT","PATCH","DELETE"]);export function isApiBoundRequest(req,pathname){return pathname.startsWith("/api/")||API_METHODS.has(req.method)}export async function proxyToBackend(req,backendBase,stripPrefix){const incoming=new URL(req.url);let pathname=incoming.pathname;if(stripPrefix&&(pathname===stripPrefix||pathname.startsWith(`${stripPrefix}/`)))pathname=pathname.slice(stripPrefix.length)||"/";const target=`${backendBase}${pathname}${incoming.search}`,fwd=new Headers(req.headers);fwd.delete("host");fwd.delete("content-length");fwd.set("x-forwarded-host",incoming.host);fwd.set("x-forwarded-proto",incoming.protocol.replace(":",""));const body=req.method==="GET"||req.method==="HEAD"?void 0:await req.arrayBuffer(),upstream=await fetch(target,{method:req.method,headers:fwd,body,redirect:"manual"}),out=new Headers(upstream.headers);out.delete("content-length");out.delete("content-encoding");return new Response(upstream.body,{status:upstream.status,statusText:upstream.statusText,headers:out})}
1
+ export const DEFAULT_API_PREFIX="/api/",DEFAULT_API_METHODS=["POST","PUT","PATCH","DELETE"];export function resolveApiProxyRules(input={}){const prefixes=[DEFAULT_API_PREFIX];for(const raw of input.prefixes??[]){const prefix=String(raw).trim();if(!prefix||!prefix.startsWith("/"))continue;const normalized=prefix.endsWith("/")?prefix:`${prefix}/`;if(!prefixes.includes(normalized))prefixes.push(normalized)}const paths=[];for(const raw of input.paths??[]){const path=String(raw).trim();if(!path||!path.startsWith("/"))continue;const normalized=path.length>1&&path.endsWith("/")?path.slice(0,-1):path;if(!paths.includes(normalized))paths.push(normalized)}const methods=new Set((input.methods??DEFAULT_API_METHODS).map((method)=>String(method).toUpperCase()));return{prefixes,paths,methods}}function matchesPrefix(pathname,prefix){return pathname.startsWith(prefix)||pathname===prefix.slice(0,-1)}export function isApiBoundRequest(req,pathname,rules){if(!rules)return pathname.startsWith(DEFAULT_API_PREFIX)||DEFAULT_API_METHODS.includes(req.method);if(rules.methods.has(req.method))return!0;if(rules.paths.includes(pathname))return!0;return rules.prefixes.some((prefix)=>matchesPrefix(pathname,prefix))}export function describeApiProxyRules(rules){const parts=[`prefixes ${rules.prefixes.join(" ")}`,`methods ${[...rules.methods].sort().join("/")}`];if(rules.paths.length>0)parts.push(`paths ${rules.paths.join(" ")}`);return parts.join(", ")}export async function proxyToBackend(req,backendBase,stripPrefix){const incoming=new URL(req.url);let pathname=incoming.pathname;if(stripPrefix&&(pathname===stripPrefix||pathname.startsWith(`${stripPrefix}/`)))pathname=pathname.slice(stripPrefix.length)||"/";const target=`${backendBase}${pathname}${incoming.search}`,fwd=new Headers(req.headers);fwd.delete("host");fwd.delete("content-length");fwd.set("x-forwarded-host",incoming.host);fwd.set("x-forwarded-proto",incoming.protocol.replace(":",""));const body=req.method==="GET"||req.method==="HEAD"?void 0:await req.arrayBuffer(),upstream=await fetch(target,{method:req.method,headers:fwd,body,redirect:"manual"}),out=new Headers(upstream.headers);out.delete("content-length");out.delete("content-encoding");return new Response(upstream.body,{status:upstream.status,statusText:upstream.statusText,headers:out})}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/server",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.294",
5
+ "version": "0.70.297",
6
6
  "description": "Local development and production-ready.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -58,11 +58,11 @@
58
58
  "prepublishOnly": "bun run build"
59
59
  },
60
60
  "devDependencies": {
61
- "@stacksjs/config": "0.70.294",
61
+ "@stacksjs/config": "0.70.297",
62
62
  "better-dx": "^0.2.17",
63
- "@stacksjs/path": "0.70.294",
64
- "@stacksjs/router": "0.70.294",
65
- "@stacksjs/validation": "0.70.294"
63
+ "@stacksjs/path": "0.70.297",
64
+ "@stacksjs/router": "0.70.297",
65
+ "@stacksjs/validation": "0.70.297"
66
66
  },
67
67
  "dependencies": {
68
68
  "bun-plugin-auto-imports": "^0.4.0"