@tokenoftrust/storefront-runner 2.4.20 → 2.4.22
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/apps/storefront/src/lib/publication/mountedDiscovery.ts +20 -2
- package/apps/storefront/src/pages/feed.json.ts +2 -1
- package/apps/storefront/src/pages/llms.txt.ts +2 -1
- package/apps/storefront/src/pages/search-index.json.ts +2 -1
- package/apps/storefront/src/pages/sitemap.xml.ts +2 -1
- package/package.json +1 -1
- package/packages/public-runtime/src/blog-visibility.d.mts +1 -0
- package/packages/public-runtime/src/blog-visibility.mjs +17 -0
- package/packages/public-runtime/src/publication-space.ts +9 -0
|
@@ -25,7 +25,10 @@ import {
|
|
|
25
25
|
type PublicationSpaceManifestV1,
|
|
26
26
|
type RouteTierIndexV1,
|
|
27
27
|
type TenantConfig,
|
|
28
|
+
PUBLICATION_DISCOVERY_ENTRY_KINDS_V1,
|
|
29
|
+
type PublicationDiscoveryEntryKindV1,
|
|
28
30
|
} from "@tot/public-runtime";
|
|
31
|
+
import { rawPageCanonicalUrl } from "../rawMarketingHtml.js";
|
|
29
32
|
|
|
30
33
|
export type DiscoverySurfaceV1 = "sitemap" | "feeds" | "publicSearch" | "llms";
|
|
31
34
|
|
|
@@ -34,6 +37,8 @@ export interface MountedDiscoveryEntryV1 {
|
|
|
34
37
|
spaceId: string;
|
|
35
38
|
/** Tenant-relative canonical route (no trailing slash). */
|
|
36
39
|
canonicalPath: string;
|
|
40
|
+
/** The renderer that serves it; {@link mountedDiscoveryUrlV1} builds its URL from this. */
|
|
41
|
+
kind: PublicationDiscoveryEntryKindV1;
|
|
37
42
|
title: string;
|
|
38
43
|
summary: string;
|
|
39
44
|
searchText: string;
|
|
@@ -88,8 +93,9 @@ function boundedString(value: unknown, allowEmpty = true): value is string {
|
|
|
88
93
|
function parseEntry(value: unknown): MountedDiscoveryEntryV1 | null {
|
|
89
94
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
90
95
|
const raw = value as Record<string, unknown>;
|
|
91
|
-
if (!exactKeys(raw, ["spaceId", "canonicalPath", "title", "summary", "searchText", "surfaces"],
|
|
92
|
-
["publishedAt", "updatedAt"])
|
|
96
|
+
if (!exactKeys(raw, ["spaceId", "canonicalPath", "kind", "title", "summary", "searchText", "surfaces"],
|
|
97
|
+
["publishedAt", "updatedAt"]) ||
|
|
98
|
+
!(PUBLICATION_DISCOVERY_ENTRY_KINDS_V1 as readonly unknown[]).includes(raw.kind)) return null;
|
|
93
99
|
const surfaces = raw.surfaces;
|
|
94
100
|
if (!surfaces || typeof surfaces !== "object" || Array.isArray(surfaces) ||
|
|
95
101
|
!exactKeys(surfaces, SURFACES) ||
|
|
@@ -102,6 +108,7 @@ function parseEntry(value: unknown): MountedDiscoveryEntryV1 | null {
|
|
|
102
108
|
return {
|
|
103
109
|
spaceId: raw.spaceId,
|
|
104
110
|
canonicalPath: raw.canonicalPath,
|
|
111
|
+
kind: raw.kind as PublicationDiscoveryEntryKindV1,
|
|
105
112
|
title: raw.title,
|
|
106
113
|
summary: raw.summary,
|
|
107
114
|
searchText: raw.searchText,
|
|
@@ -180,6 +187,17 @@ export async function readMountedPublicationDiscoveryV1(input: {
|
|
|
180
187
|
left.canonicalPath < right.canonicalPath ? -1 : left.canonicalPath > right.canonicalPath ? 1 : 0);
|
|
181
188
|
}
|
|
182
189
|
|
|
190
|
+
/**
|
|
191
|
+
* The absolute URL every discovery surface lists for a mounted entry: the canonical the page
|
|
192
|
+
* itself declares. A raw-HTML page's comes from {@link rawPageCanonicalUrl}, the constructor its
|
|
193
|
+
* renderer (`pages/[...slug].astro`) calls, so the sitemap and the page cannot disagree.
|
|
194
|
+
*/
|
|
195
|
+
export function mountedDiscoveryUrlV1(canonicalBase: string, entry: Pick<MountedDiscoveryEntryV1,
|
|
196
|
+
"canonicalPath" | "kind">): string {
|
|
197
|
+
if (entry.kind === "raw-html") return rawPageCanonicalUrl(canonicalBase, entry.canonicalPath.slice(1));
|
|
198
|
+
return `${canonicalBase.replace(/\/$/, "")}${entry.canonicalPath}`;
|
|
199
|
+
}
|
|
200
|
+
|
|
183
201
|
/** An RFC 3339 instant for a feed, or undefined when the value is not a date. */
|
|
184
202
|
export function feedInstantV1(value: string | undefined): string | undefined {
|
|
185
203
|
if (!value) return undefined;
|
|
@@ -11,6 +11,7 @@ import { readKv } from "@/lib/runtimeEnv.js";
|
|
|
11
11
|
import {
|
|
12
12
|
declaresMountedSpacesV1,
|
|
13
13
|
feedInstantV1,
|
|
14
|
+
mountedDiscoveryUrlV1,
|
|
14
15
|
readMountedPublicationDiscoveryV1,
|
|
15
16
|
rootOwnsDiscoveryPathV1,
|
|
16
17
|
} from "@/lib/publication/mountedDiscovery.js";
|
|
@@ -53,7 +54,7 @@ export async function GET(context: APIContext): Promise<Response> {
|
|
|
53
54
|
const mounted = await readMountedPublicationDiscoveryV1({ tenant, store: await readKv("TENANT_CACHE"), index });
|
|
54
55
|
for (const entry of mounted) {
|
|
55
56
|
if (!entry.surfaces.feeds) continue;
|
|
56
|
-
items.push(item(
|
|
57
|
+
items.push(item(mountedDiscoveryUrlV1(canonicalBase, entry), entry.title, entry.summary, entry.searchText,
|
|
57
58
|
entry.publishedAt, entry.updatedAt));
|
|
58
59
|
}
|
|
59
60
|
}
|
|
@@ -16,6 +16,7 @@ import { compileRouteTierIndexV1 } from "@tot/public-runtime";
|
|
|
16
16
|
import { readKv } from "@/lib/runtimeEnv.js";
|
|
17
17
|
import {
|
|
18
18
|
declaresMountedSpacesV1,
|
|
19
|
+
mountedDiscoveryUrlV1,
|
|
19
20
|
readMountedPublicationDiscoveryV1,
|
|
20
21
|
rootOwnsDiscoveryPathV1,
|
|
21
22
|
} from "@/lib/publication/mountedDiscovery.js";
|
|
@@ -119,7 +120,7 @@ export async function GET(context: APIContext): Promise<Response> {
|
|
|
119
120
|
lines.push("## More content");
|
|
120
121
|
for (const entry of mounted) {
|
|
121
122
|
const desc = oneLine(entry.summary);
|
|
122
|
-
lines.push(`- [${oneLine(entry.title, 200) ?? entry.title}](${
|
|
123
|
+
lines.push(`- [${oneLine(entry.title, 200) ?? entry.title}](${mountedDiscoveryUrlV1(canonicalBase, entry)})${desc ? `: ${desc}` : ""}`);
|
|
123
124
|
}
|
|
124
125
|
lines.push("");
|
|
125
126
|
}
|
|
@@ -9,6 +9,7 @@ import { compileRouteTierIndexV1 } from "@tot/public-runtime";
|
|
|
9
9
|
import { readKv } from "@/lib/runtimeEnv.js";
|
|
10
10
|
import {
|
|
11
11
|
declaresMountedSpacesV1,
|
|
12
|
+
mountedDiscoveryUrlV1,
|
|
12
13
|
readMountedPublicationDiscoveryV1,
|
|
13
14
|
rootOwnsDiscoveryPathV1,
|
|
14
15
|
} from "@/lib/publication/mountedDiscovery.js";
|
|
@@ -35,7 +36,7 @@ export async function GET(context: APIContext): Promise<Response> {
|
|
|
35
36
|
const mounted = await readMountedPublicationDiscoveryV1({ tenant, store: await readKv("TENANT_CACHE"), index });
|
|
36
37
|
for (const entry of mounted) {
|
|
37
38
|
if (!entry.surfaces.publicSearch) continue;
|
|
38
|
-
entries.push({ canonicalPath: entry.canonicalPath, canonicalUrl:
|
|
39
|
+
entries.push({ canonicalPath: entry.canonicalPath, canonicalUrl: mountedDiscoveryUrlV1(canonicalBase, entry),
|
|
39
40
|
title: entry.title, summary: entry.summary, searchText: entry.searchText });
|
|
40
41
|
}
|
|
41
42
|
}
|
|
@@ -20,6 +20,7 @@ import { compileRouteTierIndexV1 } from "@tot/public-runtime";
|
|
|
20
20
|
import { readKv } from "@/lib/runtimeEnv.js";
|
|
21
21
|
import {
|
|
22
22
|
declaresMountedSpacesV1,
|
|
23
|
+
mountedDiscoveryUrlV1,
|
|
23
24
|
readMountedPublicationDiscoveryV1,
|
|
24
25
|
rootOwnsDiscoveryPathV1,
|
|
25
26
|
} from "@/lib/publication/mountedDiscovery.js";
|
|
@@ -137,7 +138,7 @@ export async function GET(context: APIContext): Promise<Response> {
|
|
|
137
138
|
const mounted = await readMountedPublicationDiscoveryV1({ tenant, store: await readKv("TENANT_CACHE"), index });
|
|
138
139
|
for (const entry of mounted) {
|
|
139
140
|
if (!entry.surfaces.sitemap) continue;
|
|
140
|
-
const loc =
|
|
141
|
+
const loc = mountedDiscoveryUrlV1(canonicalBase, entry);
|
|
141
142
|
if (listed.has(loc)) continue;
|
|
142
143
|
listed.add(loc);
|
|
143
144
|
const lastmod = entry.updatedAt ?? entry.publishedAt;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.22",
|
|
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",
|
|
@@ -4,5 +4,6 @@ export interface BlogVisibilityInput {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
export declare function exactPublicationTimestamp(value: unknown): string | null;
|
|
7
|
+
export declare function publicationAggregateClockV1(spaces: unknown): string | null;
|
|
7
8
|
export declare function isBlogPostVisibleOnLive(post: BlogVisibilityInput, now?: string | null): boolean;
|
|
8
9
|
export declare function isBlogPostListableOnLive(post: BlogVisibilityInput, now?: string | null): boolean;
|
|
@@ -10,6 +10,23 @@ export function exactPublicationTimestamp(value) {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* The clock of a publication-space aggregate: the latest of its spaces' own publication instants.
|
|
15
|
+
* Each space keeps its own instant in the vector; the aggregate's is derived from the vector alone,
|
|
16
|
+
* so every dispatch of the same vector, whichever space's activation sent it, names the same
|
|
17
|
+
* aggregate. Null when the vector is empty or any entry's instant is not exact.
|
|
18
|
+
* @param {unknown} spaces
|
|
19
|
+
* @returns {string | null}
|
|
20
|
+
*/
|
|
21
|
+
export function publicationAggregateClockV1(spaces) {
|
|
22
|
+
if (!spaces || typeof spaces !== "object" || Array.isArray(spaces)) return null;
|
|
23
|
+
const instants = Object.values(spaces).map((space) =>
|
|
24
|
+
space && typeof space === "object" ? exactPublicationTimestamp(/** @type {any} */ (space).publicationTimestamp) : null);
|
|
25
|
+
if (instants.length === 0 || instants.some((instant) => instant === null)) return null;
|
|
26
|
+
return /** @type {string[]} */ (instants).reduce((latest, instant) =>
|
|
27
|
+
Date.parse(instant) > Date.parse(latest) ? instant : latest);
|
|
28
|
+
}
|
|
29
|
+
|
|
13
30
|
/** @param {{status?: unknown, scheduledAt?: unknown}} post @param {unknown} now */
|
|
14
31
|
function scheduledIsDue(post, now) {
|
|
15
32
|
const publicationTimestamp = exactPublicationTimestamp(now);
|
|
@@ -27,6 +27,15 @@ export const PUBLICATION_MOUNT_ENTRY_VERSION = 1 as const;
|
|
|
27
27
|
export const PUBLICATION_SPACE_PATH_CASE_POLICY = "sensitive" as const;
|
|
28
28
|
export const PUBLICATION_SPACE_UNICODE_POLICY = "nfc" as const;
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Which renderer serves a discovery entry's route, so every discovery surface lists the canonical
|
|
32
|
+
* that page declares: `raw-html` pages (a mounted space's home and its pages-html) carry the
|
|
33
|
+
* raw-page trailing slash; `page`, `post` and `archive` routes are listed as they are.
|
|
34
|
+
*/
|
|
35
|
+
export type PublicationDiscoveryEntryKindV1 = "raw-html" | "page" | "post" | "archive";
|
|
36
|
+
export const PUBLICATION_DISCOVERY_ENTRY_KINDS_V1: readonly PublicationDiscoveryEntryKindV1[] =
|
|
37
|
+
["raw-html", "page", "post", "archive"];
|
|
38
|
+
|
|
30
39
|
/** Static public-discovery outputs generated from exact live publication versions. */
|
|
31
40
|
export const PUBLICATION_DISCOVERY_OUTPUT_PATHS_V1 = [
|
|
32
41
|
"/feed.json",
|