@tokenoftrust/storefront-runner 3.3.3 → 3.3.5
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.
|
@@ -2,9 +2,14 @@
|
|
|
2
2
|
* The document's server response time for the evidence panel: the `total` the Worker stamps in
|
|
3
3
|
* `Server-Timing` (`lib/observability/shared/serverTiming.ts`) next to the time this self-fetch
|
|
4
4
|
* waited for the response headers (TTFB as seen from the Worker). Advisory — never gates promotion.
|
|
5
|
+
*
|
|
6
|
+
* The self-fetch carries the viewer's preview grant so it measures the render a reviewer gets. A
|
|
7
|
+
* gate page or any non-200 answer is not that render, so it is reported as not measured rather
|
|
8
|
+
* than as a number.
|
|
5
9
|
*/
|
|
6
10
|
|
|
7
11
|
export interface ServerResponseTime {
|
|
12
|
+
measured: true;
|
|
8
13
|
/** `total;dur` from the document's `Server-Timing`, or null when the header is absent. */
|
|
9
14
|
serverTotalMs: number | null;
|
|
10
15
|
/** Wall time until the response headers arrived. */
|
|
@@ -13,6 +18,11 @@ export interface ServerResponseTime {
|
|
|
13
18
|
cold: boolean;
|
|
14
19
|
}
|
|
15
20
|
|
|
21
|
+
export interface ServerResponseTimeUnmeasured {
|
|
22
|
+
measured: false;
|
|
23
|
+
reason: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
16
26
|
/** Pull `total;dur` and the cold marker out of a `Server-Timing` header value. */
|
|
17
27
|
export function parseServerTiming(header: string | null | undefined): Pick<ServerResponseTime, "serverTotalMs" | "cold"> {
|
|
18
28
|
if (!header) return { serverTotalMs: null, cold: false };
|
|
@@ -24,18 +34,35 @@ export function parseServerTiming(header: string | null | undefined): Pick<Serve
|
|
|
24
34
|
};
|
|
25
35
|
}
|
|
26
36
|
|
|
27
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* The URL and headers that make a self-fetch of `path` the viewer's own render: the request's
|
|
39
|
+
* `__preview` grant (query) and cookies are forwarded.
|
|
40
|
+
*/
|
|
41
|
+
export function viewerRenderRequest(requestUrl: string, path: string, cookie: string | null): { url: URL; headers: Headers } {
|
|
42
|
+
const source = new URL(requestUrl);
|
|
43
|
+
const url = new URL(path, source.origin);
|
|
44
|
+
const grant = source.searchParams.get("__preview");
|
|
45
|
+
if (grant) url.searchParams.set("__preview", grant);
|
|
46
|
+
const headers = new Headers();
|
|
47
|
+
if (cookie) headers.set("cookie", cookie);
|
|
48
|
+
return { url, headers };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Self-fetch and report the server response time, or why it could not be measured. */
|
|
28
52
|
export async function measureServerResponseTime(
|
|
29
|
-
url: URL,
|
|
53
|
+
request: { url: URL; headers: Headers },
|
|
30
54
|
now: () => number = () => performance.now(),
|
|
31
|
-
): Promise<ServerResponseTime |
|
|
55
|
+
): Promise<ServerResponseTime | ServerResponseTimeUnmeasured> {
|
|
32
56
|
try {
|
|
33
57
|
const started = now();
|
|
34
|
-
const res = await fetch(url, { redirect: "manual" });
|
|
58
|
+
const res = await fetch(request.url, { headers: request.headers, redirect: "manual" });
|
|
35
59
|
const ttfbMs = now() - started;
|
|
36
60
|
await res.body?.cancel();
|
|
37
|
-
|
|
61
|
+
const gate = res.headers.get("x-tot-gate");
|
|
62
|
+
if (gate) return { measured: false, reason: `the fetch was answered by the access gate (${gate}), not the store render` };
|
|
63
|
+
if (res.status !== 200) return { measured: false, reason: `the home document answered ${res.status}, not a render` };
|
|
64
|
+
return { measured: true, ...parseServerTiming(res.headers.get("server-timing")), ttfbMs: Math.round(ttfbMs) };
|
|
38
65
|
} catch {
|
|
39
|
-
return
|
|
66
|
+
return { measured: false, reason: "the home document could not be fetched" };
|
|
40
67
|
}
|
|
41
68
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.5",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"description": "World-shareable storefront runner: multi-tenant renderer on Astro/Cloudflare. No control plane.",
|
|
6
6
|
"packageManager": "pnpm@11.9.0",
|
|
@@ -1,13 +1,92 @@
|
|
|
1
|
+
// Renders packages/public-runtime/schemas/managed-analytics/v1/*.schema.json from
|
|
2
|
+
// MANAGED_ANALYTICS_API_V1_JSON_SCHEMAS. The output is a declared derived mirror
|
|
3
|
+
// (.tot/derived-mirror-steps.json), re-derived on the umbrella tip.
|
|
4
|
+
//
|
|
5
|
+
// (no flag) write every schema file
|
|
6
|
+
// --check refuse a stale, missing or orphaned file. Inside a fold gate (TOT_FOLD_GATE=1) a
|
|
7
|
+
// stale or missing file passes with a STALE line, since the tip re-derives it; an
|
|
8
|
+
// orphan still refuses, because no derivation deletes a file.
|
|
9
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
1
10
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { MANAGED_ANALYTICS_API_V1_JSON_SCHEMAS } from "../src/integration/managed-analytics-api-v1.schemas.ts";
|
|
11
|
+
import { dirname, relative, resolve } from "node:path";
|
|
12
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
13
|
|
|
6
14
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const
|
|
8
|
-
|
|
15
|
+
const repoRoot = resolve(here, "../../..");
|
|
16
|
+
export const OUTPUT_DIR = relative(repoRoot, resolve(here, "../schemas/managed-analytics/v1"));
|
|
9
17
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Every schema file, as repo-relative path -> text.
|
|
20
|
+
* @returns {Promise<Map<string, string>>}
|
|
21
|
+
*/
|
|
22
|
+
export async function renderManagedAnalyticsSchemas() {
|
|
23
|
+
const { MANAGED_ANALYTICS_API_V1_JSON_SCHEMAS } = await import("../src/integration/managed-analytics-api-v1.schemas.ts");
|
|
24
|
+
const files = new Map();
|
|
25
|
+
for (const schema of Object.values(MANAGED_ANALYTICS_API_V1_JSON_SCHEMAS)) {
|
|
26
|
+
const name = new URL(schema.$id).pathname.split("/").at(-1);
|
|
27
|
+
files.set(`${OUTPUT_DIR}/${name}`, `${JSON.stringify(schema, null, 2)}\n`);
|
|
28
|
+
}
|
|
29
|
+
return files;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The committed files under `root` against a fresh render: `stale` differ or are missing, `orphans`
|
|
34
|
+
* sit in the output directory with no schema rendering them.
|
|
35
|
+
* @param {string} [root]
|
|
36
|
+
*/
|
|
37
|
+
export async function findSchemaDrift(root = repoRoot) {
|
|
38
|
+
const files = await renderManagedAnalyticsSchemas();
|
|
39
|
+
/** @param {string} path */
|
|
40
|
+
const committed = (path) => {
|
|
41
|
+
try {
|
|
42
|
+
return readFileSync(resolve(root, path), "utf8");
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
let present = [];
|
|
48
|
+
try {
|
|
49
|
+
present = readdirSync(resolve(root, OUTPUT_DIR));
|
|
50
|
+
} catch {
|
|
51
|
+
present = [];
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
files,
|
|
55
|
+
stale: [...files].filter(([path, text]) => committed(path) !== text).map(([path]) => path),
|
|
56
|
+
orphans: present.map((name) => `${OUTPUT_DIR}/${name}`).filter((path) => !files.has(path)),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The `--check` verdict. A fold gate tolerates stale files, which the tip re-derives; an orphan is
|
|
62
|
+
* always red, since no derivation deletes a file.
|
|
63
|
+
* @param {{ count: number, stale: string[], orphans: string[], foldGate: boolean }} drift
|
|
64
|
+
* @returns {{ ok: boolean, lines: string[] }}
|
|
65
|
+
*/
|
|
66
|
+
export function schemaCheckVerdict({ count, stale, orphans, foldGate }) {
|
|
67
|
+
const lines = [
|
|
68
|
+
...orphans.map((path) => `managed-analytics-schemas-orphan: ${path} is not rendered by any schema; delete it`),
|
|
69
|
+
...stale.map((path) => `${foldGate ? "STALE" : "managed-analytics-schemas-stale"}: ${path}`),
|
|
70
|
+
];
|
|
71
|
+
const ok = orphans.length === 0 && (stale.length === 0 || foldGate);
|
|
72
|
+
if (!ok && stale.length > 0) lines.push(`re-derive: node ${relative(repoRoot, fileURLToPath(import.meta.url))}`);
|
|
73
|
+
if (ok) lines.push(`${count} managed analytics v1 schemas are ${stale.length > 0 ? "stale; the tip re-derives them" : "current"}`);
|
|
74
|
+
return { ok, lines };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function check() {
|
|
78
|
+
const { files, stale, orphans } = await findSchemaDrift();
|
|
79
|
+
const verdict = schemaCheckVerdict({ count: files.size, stale, orphans, foldGate: process.env.TOT_FOLD_GATE === "1" });
|
|
80
|
+
for (const line of verdict.lines) (verdict.ok ? console.log : console.error)(line);
|
|
81
|
+
if (!verdict.ok) process.exitCode = 1;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function write() {
|
|
85
|
+
const files = await renderManagedAnalyticsSchemas();
|
|
86
|
+
await mkdir(resolve(repoRoot, OUTPUT_DIR), { recursive: true });
|
|
87
|
+
for (const [path, text] of files) await writeFile(resolve(repoRoot, path), text, "utf8");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
|
91
|
+
await (process.argv.includes("--check") ? check() : write());
|
|
13
92
|
}
|