@volter/world-core 3.0.33 → 3.0.34

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.
@@ -36,7 +36,7 @@ export { VendorUnreachableError } from './vendor-call.js';
36
36
  export { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER } from './request-scope.js';
37
37
  export { serveHttp, serveStream, type HttpHandler, type HttpServer, type ServeHttpOptions } from './serve-http.js';
38
38
  export type { TwinStream, TwinStreamConnection, TwinStreamSink } from './twin-fetch.js';
39
- export { siteLabel, siteUrlOf, twinSiteUrl } from './twin-fetch.js';
39
+ export { siteLabel, siteUrlOf, twinSiteUrl, twinVendorUrl } from './twin-fetch.js';
40
40
  export { statefulTwinManifest, twinManifest } from './scenario.js';
41
41
  export { assetContentType, packAsset } from './pack-assets.js';
42
42
  export { bytesResponse, contentTypeOf, fileResponse } from './file-response.js';
package/dist/src/index.js CHANGED
@@ -29,7 +29,7 @@ export { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER } from "./request-scope.js"
29
29
  // ── the serve factory ──────────────────────────────────────────────────────────────────────────
30
30
  export { serveHttp, serveStream } from "./serve-http.js";
31
31
  // where a page links a hostname its twin answers, inside the World that shows it
32
- export { siteLabel, siteUrlOf, twinSiteUrl } from "./twin-fetch.js";
32
+ export { siteLabel, siteUrlOf, twinSiteUrl, twinVendorUrl } from "./twin-fetch.js";
33
33
  export { statefulTwinManifest, twinManifest } from "./scenario.js";
34
34
  export { assetContentType, packAsset } from "./pack-assets.js";
35
35
  export { bytesResponse, contentTypeOf, fileResponse } from "./file-response.js";
@@ -4,7 +4,7 @@ export { parseScenarioDocument, scenarioFaultResult, ScenarioEngine, ScenarioErr
4
4
  export { WORLD_CLOCK_ENV, worldNow } from './world-clock.js';
5
5
  export { WORLD_ENV_NAMES_ENV, worldEnvValue } from './world-env.js';
6
6
  export { ORIGINAL_PATH_HEADER } from './sigv4.js';
7
- export { createTwinFetchFromHandler, placedCookies, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, siteLabel, siteUrlOf, twinPublicBase, twinSiteUrl, withRequestScopes } from './twin-fetch.js';
7
+ export { createTwinFetchFromHandler, placedCookies, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, TWIN_SITE_HEADER, siteLabel, siteUrlOf, twinPublicBase, twinSiteUrl, twinVendorUrl, withRequestScopes } from './twin-fetch.js';
8
8
  export { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER, ReadOnlyRequestError, runAsReadOnlyRequest, runAsVendorMove, writesRefused } from './request-scope.js';
9
9
  export { compileSurface, createDerivedFetch, matchOperation } from './derived.js';
10
10
  export { currentTraceparent, deliveryTraceHeaders, newTraceparent, parseTraceparent, runWithRequestTrace, runWithTraceparent, TRACEPARENT_HEADER, traceparentForDelivery, validTraceparent } from './trace-context.js';
@@ -12,7 +12,7 @@ export { parseScenarioDocument, scenarioFaultResult, ScenarioEngine, ScenarioErr
12
12
  export { WORLD_CLOCK_ENV, worldNow } from "./world-clock.js";
13
13
  export { WORLD_ENV_NAMES_ENV, worldEnvValue } from "./world-env.js";
14
14
  export { ORIGINAL_PATH_HEADER } from "./sigv4.js";
15
- export { createTwinFetchFromHandler, placedCookies, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, siteLabel, siteUrlOf, twinPublicBase, twinSiteUrl, withRequestScopes } from "./twin-fetch.js";
15
+ export { createTwinFetchFromHandler, placedCookies, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, TWIN_SITE_HEADER, siteLabel, siteUrlOf, twinPublicBase, twinSiteUrl, twinVendorUrl, withRequestScopes } from "./twin-fetch.js";
16
16
  export { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER, ReadOnlyRequestError, runAsReadOnlyRequest, runAsVendorMove, writesRefused } from "./request-scope.js";
17
17
  export { compileSurface, createDerivedFetch, matchOperation } from "./derived.js";
18
18
  export { currentTraceparent, deliveryTraceHeaders, newTraceparent, parseTraceparent, runWithRequestTrace, runWithTraceparent, TRACEPARENT_HEADER, traceparentForDelivery, validTraceparent } from "./trace-context.js";
@@ -13,6 +13,8 @@ export declare function twinPublicBase(request: Request): string;
13
13
  * where one wildcard certificate covers the host's names (`https://{site}--<world>--<org>.volterdev.com`). A World that
14
14
  * routes its sites to a browser sends the template on every request it forwards to a twin. */
15
15
  export declare const TWIN_SITES_HEADER = "x-volter-world-sites";
16
+ /** The real host of the page whose hosted-site session owns this request, including a /_host/ request. */
17
+ export declare const TWIN_SITE_HEADER = "x-volter-world-site";
16
18
  /** A hostname as one DNS label: its dots as hyphens (`www.volter.ai` is `www-volter-ai`). */
17
19
  export declare const siteLabel: (hostname: string) => string;
18
20
  /** The address a sites template gives `hostname`, or null where the template is not one or the name makes no address
@@ -22,6 +24,10 @@ export declare function siteUrlOf(template: string, hostname: string, browser?:
22
24
  * hostname itself (reached through the World's proxy, or the vendor). A pack's screen links a site from this, as it
23
25
  * mints its own URLs from twinPublicBase. */
24
26
  export declare function twinSiteUrl(request: Request, hostname: string): string;
27
+ /** A URL on the page's vendor: hosted sites keep it on the page's origin, with another of the vendor's hosts under
28
+ * /_host/. The World checks that target's vendor and applies the page's session; a pack supplies only its own
29
+ * vendor URLs. Without hosted-site headers (the local proxy or a path-mounted page), the URL is unchanged. */
30
+ export declare function twinVendorUrl(request: Request, vendorUrl: string): string;
25
31
  /** A twin's BYTE-STREAM door: a pack whose clients speak a TCP protocol (SMTP, the MySQL wire) serves
26
32
  * it over whatever carries the bytes — a local listener's socket, or a WebSocket to a hosted World.
27
33
  * The pack owns framing and protocol; the host owns the transport. `open` greets through the sink;
@@ -47,6 +47,8 @@ export function twinPublicBase(request) {
47
47
  * where one wildcard certificate covers the host's names (`https://{site}--<world>--<org>.volterdev.com`). A World that
48
48
  * routes its sites to a browser sends the template on every request it forwards to a twin. */
49
49
  export const TWIN_SITES_HEADER = 'x-volter-world-sites';
50
+ /** The real host of the page whose hosted-site session owns this request, including a /_host/ request. */
51
+ export const TWIN_SITE_HEADER = 'x-volter-world-site';
50
52
  /** A hostname as one DNS label: its dots as hyphens (`www.volter.ai` is `www-volter-ai`). */
51
53
  export const siteLabel = (hostname) => hostname.toLowerCase().replace(/\./g, '-');
52
54
  /** The address a sites template gives `hostname`, or null where the template is not one or the name makes no address
@@ -90,6 +92,29 @@ export function twinSiteUrl(request, hostname) {
90
92
  }
91
93
  return (at ? siteUrlOf(at, hostname, browser) : null) ?? `https://${hostname.toLowerCase()}`;
92
94
  }
95
+ /** A URL on the page's vendor: hosted sites keep it on the page's origin, with another of the vendor's hosts under
96
+ * /_host/. The World checks that target's vendor and applies the page's session; a pack supplies only its own
97
+ * vendor URLs. Without hosted-site headers (the local proxy or a path-mounted page), the URL is unchanged. */
98
+ export function twinVendorUrl(request, vendorUrl) {
99
+ const site = request.headers.get(TWIN_SITE_HEADER);
100
+ if (!site || !request.headers.has(TWIN_SITES_HEADER))
101
+ return vendorUrl;
102
+ let target;
103
+ try {
104
+ target = new URL(vendorUrl);
105
+ }
106
+ catch {
107
+ return vendorUrl;
108
+ }
109
+ if (target.protocol !== 'https:' || target.username || target.password || target.port)
110
+ return vendorUrl;
111
+ const forwarded = request.headers.get('x-forwarded-host');
112
+ const proto = request.headers.get('x-forwarded-proto');
113
+ const origin = forwarded && /^[A-Za-z0-9.-]+(:\d+)?$/.test(forwarded) && (proto === 'https' || proto === 'http')
114
+ ? `${proto}://${forwarded}` : new URL(request.url).origin;
115
+ const prefix = target.hostname === site.toLowerCase() ? '' : `/_host/${target.hostname}`;
116
+ return `${origin}${prefix}${target.pathname}${target.search}${target.hash}`;
117
+ }
93
118
  /** The request scopes a pack wrapped in `withRequestScopes` enforces, advertised on its `GET /twin` as
94
119
  * `requestScopes`: `read` — a request carrying `x-volter-read-only: 1` (request-scope.ts) has every
95
120
  * write it attempts refused at the kernel's write seam, whatever operation it names. A World's doors
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/world-core",
3
- "version": "3.0.33",
3
+ "version": "3.0.34",
4
4
  "description": "The kernel of Volter World: one log per twin, branches as pointers, checkpoints, the fold that keeps a twin current, the head that performs a write against the vendor, references, and the git plane. A twin package builds on it; the runtime serves it.",
5
5
  "keywords": [
6
6
  "twin",
package/src/index.ts CHANGED
@@ -52,7 +52,7 @@ export { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER } from './request-scope.ts'
52
52
  export { serveHttp, serveStream, type HttpHandler, type HttpServer, type ServeHttpOptions } from './serve-http.ts';
53
53
  export type { TwinStream, TwinStreamConnection, TwinStreamSink } from './twin-fetch.ts';
54
54
  // where a page links a hostname its twin answers, inside the World that shows it
55
- export { siteLabel, siteUrlOf, twinSiteUrl } from './twin-fetch.ts';
55
+ export { siteLabel, siteUrlOf, twinSiteUrl, twinVendorUrl } from './twin-fetch.ts';
56
56
  export { statefulTwinManifest, twinManifest } from './scenario.ts';
57
57
  export { assetContentType, packAsset } from './pack-assets.ts';
58
58
  export { bytesResponse, contentTypeOf, fileResponse } from './file-response.ts';
package/src/runtime.ts CHANGED
@@ -13,7 +13,7 @@ export { parseScenarioDocument, scenarioFaultResult, ScenarioEngine, ScenarioErr
13
13
  export { WORLD_CLOCK_ENV, worldNow } from './world-clock.ts';
14
14
  export { WORLD_ENV_NAMES_ENV, worldEnvValue } from './world-env.ts';
15
15
  export { ORIGINAL_PATH_HEADER } from './sigv4.ts';
16
- export { createTwinFetchFromHandler, placedCookies, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, siteLabel, siteUrlOf, twinPublicBase, twinSiteUrl, withRequestScopes } from './twin-fetch.ts';
16
+ export { createTwinFetchFromHandler, placedCookies, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, TWIN_SITE_HEADER, siteLabel, siteUrlOf, twinPublicBase, twinSiteUrl, twinVendorUrl, withRequestScopes } from './twin-fetch.ts';
17
17
  export { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER, ReadOnlyRequestError, runAsReadOnlyRequest, runAsVendorMove, writesRefused } from './request-scope.ts';
18
18
  export { compileSurface, createDerivedFetch, matchOperation } from './derived.ts';
19
19
  export { currentTraceparent, deliveryTraceHeaders, newTraceparent, parseTraceparent, runWithRequestTrace, runWithTraceparent, TRACEPARENT_HEADER, traceparentForDelivery, validTraceparent } from './trace-context.ts';
package/src/twin-fetch.ts CHANGED
@@ -50,6 +50,8 @@ export function twinPublicBase(request: Request): string {
50
50
  * where one wildcard certificate covers the host's names (`https://{site}--<world>--<org>.volterdev.com`). A World that
51
51
  * routes its sites to a browser sends the template on every request it forwards to a twin. */
52
52
  export const TWIN_SITES_HEADER = 'x-volter-world-sites';
53
+ /** The real host of the page whose hosted-site session owns this request, including a /_host/ request. */
54
+ export const TWIN_SITE_HEADER = 'x-volter-world-site';
53
55
 
54
56
  /** A hostname as one DNS label: its dots as hyphens (`www.volter.ai` is `www-volter-ai`). */
55
57
  export const siteLabel = (hostname: string): string => hostname.toLowerCase().replace(/\./g, '-');
@@ -77,6 +79,23 @@ export function twinSiteUrl(request: Request, hostname: string): string {
77
79
  return (at ? siteUrlOf(at, hostname, browser) : null) ?? `https://${hostname.toLowerCase()}`;
78
80
  }
79
81
 
82
+ /** A URL on the page's vendor: hosted sites keep it on the page's origin, with another of the vendor's hosts under
83
+ * /_host/. The World checks that target's vendor and applies the page's session; a pack supplies only its own
84
+ * vendor URLs. Without hosted-site headers (the local proxy or a path-mounted page), the URL is unchanged. */
85
+ export function twinVendorUrl(request: Request, vendorUrl: string): string {
86
+ const site = request.headers.get(TWIN_SITE_HEADER);
87
+ if (!site || !request.headers.has(TWIN_SITES_HEADER)) return vendorUrl;
88
+ let target: URL;
89
+ try { target = new URL(vendorUrl); } catch { return vendorUrl; }
90
+ if (target.protocol !== 'https:' || target.username || target.password || target.port) return vendorUrl;
91
+ const forwarded = request.headers.get('x-forwarded-host');
92
+ const proto = request.headers.get('x-forwarded-proto');
93
+ const origin = forwarded && /^[A-Za-z0-9.-]+(:\d+)?$/.test(forwarded) && (proto === 'https' || proto === 'http')
94
+ ? `${proto}://${forwarded}` : new URL(request.url).origin;
95
+ const prefix = target.hostname === site.toLowerCase() ? '' : `/_host/${target.hostname}`;
96
+ return `${origin}${prefix}${target.pathname}${target.search}${target.hash}`;
97
+ }
98
+
80
99
  /** A twin's BYTE-STREAM door: a pack whose clients speak a TCP protocol (SMTP, the MySQL wire) serves
81
100
  * it over whatever carries the bytes — a local listener's socket, or a WebSocket to a hosted World.
82
101
  * The pack owns framing and protocol; the host owns the transport. `open` greets through the sink;