@volter/world-core 3.0.17 → 3.0.18

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 { twinSiteUrl } from './twin-fetch.js';
39
+ export { siteLabel, siteUrlOf, twinSiteUrl } 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 { twinSiteUrl } from "./twin-fetch.js";
32
+ export { siteLabel, siteUrlOf, twinSiteUrl } 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, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, twinPublicBase, twinSiteUrl, withRequestScopes } from './twin-fetch.js';
7
+ export { createTwinFetchFromHandler, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, siteLabel, siteUrlOf, twinPublicBase, twinSiteUrl, 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, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, twinPublicBase, twinSiteUrl, withRequestScopes } from "./twin-fetch.js";
15
+ export { createTwinFetchFromHandler, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, siteLabel, siteUrlOf, twinPublicBase, twinSiteUrl, 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";
@@ -8,9 +8,16 @@ export declare const TWIN_PREFIX_HEADER = "x-forwarded-prefix";
8
8
  * callback) from this, never from the bare origin, so the URL works behind a served World. */
9
9
  export declare function twinPublicBase(request: Request): string;
10
10
  /** Where a World shows a hostname its twins answer (a Workers Custom Domain, an R2 public domain): the address a person
11
- * opens it at, `{host}` standing for the hostname (`http://{host}.<world>--<org>.localhost:<port>`). A World that routes
12
- * its sites to a browser sends it on every request it forwards to a twin. */
11
+ * opens it at, from the World's sites template. `{host}` stands for the hostname as it is, where the host's names nest
12
+ * (`http://{host}.<world>--<org>.localhost:<port>`); `{site}` for the hostname as one DNS label, its dots as hyphens,
13
+ * where one wildcard certificate covers the host's names (`https://{site}--<world>--<org>.volterdev.com`). A World that
14
+ * routes its sites to a browser sends the template on every request it forwards to a twin. */
13
15
  export declare const TWIN_SITES_HEADER = "x-volter-world-sites";
16
+ /** A hostname as one DNS label: its dots as hyphens (`www.volter.ai` is `www-volter-ai`). */
17
+ export declare const siteLabel: (hostname: string) => string;
18
+ /** The address a sites template gives `hostname`, or null where the template is not one or the name makes no address
19
+ * (a label over 63 characters). */
20
+ export declare function siteUrlOf(template: string, hostname: string): string | null;
14
21
  /** Where a page links a hostname the twin answers: the World's address for it when the World shows sites, else the
15
22
  * hostname itself (reached through the World's proxy, or the vendor). A pack's screen links a site from this, as it
16
23
  * mints its own URLs from twinPublicBase. */
@@ -41,16 +41,35 @@ export function twinPublicBase(request) {
41
41
  return `${origin}${prefix.replace(/\/+$/, '')}`;
42
42
  }
43
43
  /** Where a World shows a hostname its twins answer (a Workers Custom Domain, an R2 public domain): the address a person
44
- * opens it at, `{host}` standing for the hostname (`http://{host}.<world>--<org>.localhost:<port>`). A World that routes
45
- * its sites to a browser sends it on every request it forwards to a twin. */
44
+ * opens it at, from the World's sites template. `{host}` stands for the hostname as it is, where the host's names nest
45
+ * (`http://{host}.<world>--<org>.localhost:<port>`); `{site}` for the hostname as one DNS label, its dots as hyphens,
46
+ * where one wildcard certificate covers the host's names (`https://{site}--<world>--<org>.volterdev.com`). A World that
47
+ * routes its sites to a browser sends the template on every request it forwards to a twin. */
46
48
  export const TWIN_SITES_HEADER = 'x-volter-world-sites';
49
+ /** A hostname as one DNS label: its dots as hyphens (`www.volter.ai` is `www-volter-ai`). */
50
+ export const siteLabel = (hostname) => hostname.toLowerCase().replace(/\./g, '-');
51
+ /** The address a sites template gives `hostname`, or null where the template is not one or the name makes no address
52
+ * (a label over 63 characters). */
53
+ export function siteUrlOf(template, hostname) {
54
+ const host = hostname.toLowerCase();
55
+ if (!/^[a-z0-9.-]+$/.test(host))
56
+ return null;
57
+ // a one-label site is never an IDN's (`xn--…` would be read as punycode, and is no address)
58
+ const filled = /^https?:\/\/\{host\}\.[A-Za-z0-9.-]+(:\d+)?$/.test(template) ? template.replace('{host}', host)
59
+ : /^https?:\/\/\{site\}--[A-Za-z0-9.-]+(:\d+)?$/.test(template) && !host.startsWith('xn--') ? template.replace('{site}', siteLabel(host)) : null;
60
+ try {
61
+ return filled && new URL(filled).hostname.split('.').every((l) => l.length > 0 && l.length <= 63) ? filled : null;
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ }
47
67
  /** Where a page links a hostname the twin answers: the World's address for it when the World shows sites, else the
48
68
  * hostname itself (reached through the World's proxy, or the vendor). A pack's screen links a site from this, as it
49
69
  * mints its own URLs from twinPublicBase. */
50
70
  export function twinSiteUrl(request, hostname) {
51
71
  const at = request.headers.get(TWIN_SITES_HEADER);
52
- const host = hostname.toLowerCase();
53
- return at && /^https?:\/\/\{host\}\.[A-Za-z0-9.-]+(:\d+)?$/.test(at) && /^[a-z0-9.-]+$/.test(host) ? at.replace('{host}', host) : `https://${host}`;
72
+ return (at ? siteUrlOf(at, hostname) : null) ?? `https://${hostname.toLowerCase()}`;
54
73
  }
55
74
  /** The request scopes a pack wrapped in `withRequestScopes` enforces, advertised on its `GET /twin` as
56
75
  * `requestScopes`: `read` — a request carrying `x-volter-read-only: 1` (request-scope.ts) has every
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/world-core",
3
- "version": "3.0.17",
3
+ "version": "3.0.18",
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 { twinSiteUrl } from './twin-fetch.ts';
55
+ export { siteLabel, siteUrlOf, twinSiteUrl } 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, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, twinPublicBase, twinSiteUrl, withRequestScopes } from './twin-fetch.ts';
16
+ export { createTwinFetchFromHandler, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, siteLabel, siteUrlOf, twinPublicBase, twinSiteUrl, 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
@@ -44,17 +44,32 @@ export function twinPublicBase(request: Request): string {
44
44
  }
45
45
 
46
46
  /** Where a World shows a hostname its twins answer (a Workers Custom Domain, an R2 public domain): the address a person
47
- * opens it at, `{host}` standing for the hostname (`http://{host}.<world>--<org>.localhost:<port>`). A World that routes
48
- * its sites to a browser sends it on every request it forwards to a twin. */
47
+ * opens it at, from the World's sites template. `{host}` stands for the hostname as it is, where the host's names nest
48
+ * (`http://{host}.<world>--<org>.localhost:<port>`); `{site}` for the hostname as one DNS label, its dots as hyphens,
49
+ * where one wildcard certificate covers the host's names (`https://{site}--<world>--<org>.volterdev.com`). A World that
50
+ * routes its sites to a browser sends the template on every request it forwards to a twin. */
49
51
  export const TWIN_SITES_HEADER = 'x-volter-world-sites';
50
52
 
53
+ /** A hostname as one DNS label: its dots as hyphens (`www.volter.ai` is `www-volter-ai`). */
54
+ export const siteLabel = (hostname: string): string => hostname.toLowerCase().replace(/\./g, '-');
55
+
56
+ /** The address a sites template gives `hostname`, or null where the template is not one or the name makes no address
57
+ * (a label over 63 characters). */
58
+ export function siteUrlOf(template: string, hostname: string): string | null {
59
+ const host = hostname.toLowerCase();
60
+ if (!/^[a-z0-9.-]+$/.test(host)) return null;
61
+ // a one-label site is never an IDN's (`xn--…` would be read as punycode, and is no address)
62
+ const filled = /^https?:\/\/\{host\}\.[A-Za-z0-9.-]+(:\d+)?$/.test(template) ? template.replace('{host}', host)
63
+ : /^https?:\/\/\{site\}--[A-Za-z0-9.-]+(:\d+)?$/.test(template) && !host.startsWith('xn--') ? template.replace('{site}', siteLabel(host)) : null;
64
+ try { return filled && new URL(filled).hostname.split('.').every((l) => l.length > 0 && l.length <= 63) ? filled : null; } catch { return null; }
65
+ }
66
+
51
67
  /** Where a page links a hostname the twin answers: the World's address for it when the World shows sites, else the
52
68
  * hostname itself (reached through the World's proxy, or the vendor). A pack's screen links a site from this, as it
53
69
  * mints its own URLs from twinPublicBase. */
54
70
  export function twinSiteUrl(request: Request, hostname: string): string {
55
71
  const at = request.headers.get(TWIN_SITES_HEADER);
56
- const host = hostname.toLowerCase();
57
- return at && /^https?:\/\/\{host\}\.[A-Za-z0-9.-]+(:\d+)?$/.test(at) && /^[a-z0-9.-]+$/.test(host) ? at.replace('{host}', host) : `https://${host}`;
72
+ return (at ? siteUrlOf(at, hostname) : null) ?? `https://${hostname.toLowerCase()}`;
58
73
  }
59
74
 
60
75
  /** A twin's BYTE-STREAM door: a pack whose clients speak a TCP protocol (SMTP, the MySQL wire) serves