bun-plugin-stx 0.2.410 → 0.2.411

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.
@@ -14,12 +14,23 @@ export declare function matchDomain(host: string, route: DomainRoute): Record<st
14
14
  * the root).
15
15
  */
16
16
  export declare function resolveDomainRoute(host: string, pathname: string, routes: DomainRoute[]): string | null;
17
+ /**
18
+ * Where a request for another page on a routed host belongs, or null to
19
+ * answer it in place.
20
+ *
21
+ * Only for a route that names `elsewhere`, only for a host that matches it,
22
+ * only for paths other than the root, and only for page requests: `isAsset`
23
+ * is the server's own test for scripts, styles, `/api` and `public/` files,
24
+ * which stay where the page at the root can load them.
25
+ */
26
+ export declare function resolveDomainElsewhere(host: string, pathname: string, search: string, routes: DomainRoute[], isAsset: (pathname: string) => boolean): string | null;
17
27
  /** One line for boot output: `{username}.example.com/ → /{username}`. */
18
28
  export declare function describeDomainRoutes(routes: DomainRoute[]): string;
19
29
  export declare interface DomainRoute {
20
30
  pattern: string
21
31
  labels: Label[]
22
32
  to: string
33
+ elsewhere?: string
23
34
  literals: number
24
35
  }
25
36
  /**
@@ -45,6 +56,21 @@ export declare interface DomainRoute {
45
56
  * site's own, answered as it would be on the main domain, so one page can be
46
57
  * given a domain without the rest of the site breaking under it.
47
58
  *
59
+ * Unless the route says where the rest of the site lives:
60
+ *
61
+ * ```ts
62
+ * domains: {
63
+ * '{username}.example.com': { to: '/{username}', elsewhere: 'https://example.com' },
64
+ * }
65
+ * ```
66
+ *
67
+ * Then a request for any other PAGE on that host is redirected there
68
+ * (`chris.example.com/pricing` → `example.com/pricing`), while its assets,
69
+ * `/api` and `public/` files are still answered in place, because the page
70
+ * at its root needs them. A browser keeps a session per origin: without
71
+ * this, signing in from a profile's own domain signs in to that domain, and
72
+ * the visitor ends up using the whole app under someone else's name.
73
+ *
48
74
  * A pattern is a hostname whose labels are either literal or `{name}`, which
49
75
  * matches exactly one DNS label (`{username}.example.com` matches
50
76
  * `chris.example.com`, not `a.b.example.com` and not the bare apex). Literal
@@ -59,4 +85,4 @@ export declare interface DomainRoute {
59
85
  */
60
86
  /** One label of a compiled pattern. */
61
87
  declare type Label = { literal: string } | { param: string }
62
- export type DomainRoutesOption = Record<string, string>;
88
+ export type DomainRoutesOption = Record<string, string | { to: string, elsewhere?: string }>;
package/dist/serve.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { serve as bunServe } from 'bun';
2
- import { compileDomainRoutes, describeDomainRoutes, requestHost, resolveDomainRoute } from './domain-routes';
2
+ import { compileDomainRoutes, describeDomainRoutes, requestHost, resolveDomainElsewhere, resolveDomainRoute } from './domain-routes';
3
3
  import type { DomainRoutesOption } from './domain-routes';
4
4
  import type { MiddlewareContext, MiddlewareHandler, MiddlewareRequest, PageMiddleware, PrepareMiddlewareRequest } from './page-middleware';
5
5
  export type { DomainRoute, DomainRoutesOption } from './domain-routes';
@@ -392,4 +392,4 @@ export declare interface ServeOptions {
392
392
  declare type CssEngine = Pick<typeof import('@stacksjs/ts-css/engine'), 'CSSGenerator' | 'config'>;
393
393
  /** The Bun server `serve()` started - handed to `onRequest` beside each request. */
394
394
  export type ServeServer = ReturnType<typeof bunServe>;
395
- export { compileDomainRoutes, describeDomainRoutes, matchDomain, requestHost, resolveDomainRoute } from './domain-routes';
395
+ export { compileDomainRoutes, describeDomainRoutes, matchDomain, requestHost, resolveDomainElsewhere, resolveDomainRoute } from './domain-routes';
package/dist/serve.js CHANGED
@@ -8728,9 +8728,22 @@ var LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
8728
8728
  var PARAM = /^\{([A-Za-z_]\w*)\}$/;
8729
8729
  function compileDomainRoutes(input, warn = () => {}) {
8730
8730
  const routes = [];
8731
- for (const [rawPattern, rawTo] of Object.entries(input ?? {})) {
8731
+ for (const [rawPattern, target] of Object.entries(input ?? {})) {
8732
8732
  const pattern = String(rawPattern).trim().toLowerCase().replace(/\.$/, "");
8733
+ const rawTo = typeof target === "object" && target ? target.to : target;
8733
8734
  const to = String(rawTo ?? "").trim();
8735
+ let elsewhere;
8736
+ if (typeof target === "object" && target?.elsewhere) {
8737
+ try {
8738
+ const url = new URL(String(target.elsewhere));
8739
+ if (url.protocol === "https:" || url.protocol === "http:")
8740
+ elsewhere = url.origin;
8741
+ } catch {}
8742
+ if (!elsewhere) {
8743
+ warn(`[stx] domain route "${rawPattern}" ignored: elsewhere "${target.elsewhere}" is not an http(s) origin`);
8744
+ continue;
8745
+ }
8746
+ }
8734
8747
  const parts = pattern.split(".");
8735
8748
  if (parts.length < 2 || !to.startsWith("/")) {
8736
8749
  warn(`[stx] domain route "${rawPattern}" -> "${rawTo}" ignored: a route is a hostname with a dot and a path starting with "/"`);
@@ -8755,7 +8768,7 @@ function compileDomainRoutes(input, warn = () => {}) {
8755
8768
  warn(`[stx] domain route "${rawPattern}" -> "${rawTo}" ignored: ${!valid ? "a label is neither a hostname label nor {name}" : `the path uses {${missing.join("}, {")}} which the hostname does not capture`}`);
8756
8769
  continue;
8757
8770
  }
8758
- routes.push({ pattern, labels, to, literals: labels.filter((label) => ("literal" in label)).length });
8771
+ routes.push({ pattern, labels, to, ...elsewhere ? { elsewhere } : {}, literals: labels.filter((label) => ("literal" in label)).length });
8759
8772
  }
8760
8773
  return routes.sort((a, b) => b.literals - a.literals || b.labels.length - a.labels.length);
8761
8774
  }
@@ -8796,8 +8809,20 @@ function resolveDomainRoute(host, pathname, routes) {
8796
8809
  }
8797
8810
  return null;
8798
8811
  }
8812
+ function resolveDomainElsewhere(host, pathname, search, routes, isAsset) {
8813
+ if (!routes.length || pathname === "/" || pathname === "" || pathname === "/index")
8814
+ return null;
8815
+ for (const route of routes) {
8816
+ if (!matchDomain(host, route))
8817
+ continue;
8818
+ if (!route.elsewhere || isAsset(pathname))
8819
+ return null;
8820
+ return `${route.elsewhere}${pathname}${search}`;
8821
+ }
8822
+ return null;
8823
+ }
8799
8824
  function describeDomainRoutes(routes) {
8800
- return routes.map((route) => `${route.pattern}/ \u2192 ${route.to}`).join(", ");
8825
+ return routes.map((route) => `${route.pattern}/ \u2192 ${route.to}${route.elsewhere ? ` (other pages: ${route.elsewhere})` : ""}`).join(", ");
8801
8826
  }
8802
8827
  // src/page-middleware.ts
8803
8828
  var DEFAULT_PRIORITY = 10;
@@ -10577,6 +10602,10 @@ ${block}`;
10577
10602
  const headers = new Headers(req.headers);
10578
10603
  headers.set("x-stx-domain-route", requestHost(req));
10579
10604
  req = new Request(original, { headers, method: req.method, body: req.body, redirect: req.redirect, duplex: "half" });
10605
+ } else if (req.method === "GET" || req.method === "HEAD") {
10606
+ const location = resolveDomainElsewhere(requestHost(req), original.pathname, original.search, domainRoutes, (pathname) => pathname.startsWith("/api/") || pathname.startsWith("/_stx/") || pathname.startsWith("/.well-known/") || isStaticAssetPath(pathname) || publicFileExists(pathname, publicDir));
10607
+ if (location)
10608
+ return new Response(null, { status: 301, headers: { Location: location } });
10580
10609
  }
10581
10610
  }
10582
10611
  const url = new URL(req.url);
@@ -11468,6 +11497,7 @@ export {
11468
11497
  resolveServeOpenUrl,
11469
11498
  resolveImageWarmupBindBudget,
11470
11499
  resolveDomainRoute,
11500
+ resolveDomainElsewhere,
11471
11501
  requestHost,
11472
11502
  render404Page,
11473
11503
  publicFileExists,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bun-plugin-stx",
3
3
  "type": "module",
4
- "version": "0.2.410",
4
+ "version": "0.2.411",
5
5
  "description": "A Bun plugin that allows for using Laravel Blade-like syntax.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",
@@ -74,7 +74,7 @@
74
74
  "typecheck": "bun tsc --noEmit"
75
75
  },
76
76
  "dependencies": {
77
- "@stacksjs/stx": "0.2.410",
78
- "stx-router": "0.2.410"
77
+ "@stacksjs/stx": "0.2.411",
78
+ "stx-router": "0.2.411"
79
79
  }
80
80
  }