@tokenoftrust/storefront-runner 2.2.126 → 2.3.1
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/package.json +3 -1
- package/apps/storefront/public/shared/commerce-chrome.css +57 -15
- package/apps/storefront/public/shared/commerce-marketing.css +7 -7
- package/apps/storefront/src/components/Badge.astro +1 -1
- package/apps/storefront/src/components/Breadcrumbs.astro +8 -3
- package/apps/storefront/src/components/Section.astro +4 -1
- package/apps/storefront/src/components/Seo.astro +7 -5
- package/apps/storefront/src/components/blog/BlogIndexView.astro +22 -13
- package/apps/storefront/src/components/blog/ShareRow.astro +43 -0
- package/apps/storefront/src/components/chrome/AnnouncementBar.astro +1 -1
- package/apps/storefront/src/components/chrome/NavDropdown.astro +7 -7
- package/apps/storefront/src/components/chrome/SiteFooter.astro +17 -0
- package/apps/storefront/src/components/chrome/SiteHeader.astro +5 -5
- package/apps/storefront/src/components/commerce/TagFilterCloud.astro +1 -1
- package/apps/storefront/src/components/compliance/NicotineWarning.astro +12 -22
- package/apps/storefront/src/components/compliance/PactActNotice.astro +9 -10
- package/apps/storefront/src/components/content/ProseSections.astro +2 -2
- package/apps/storefront/src/components/home/Hero.astro +34 -12
- package/apps/storefront/src/components/islands/IsolatedAgeGate.tsx +39 -5
- package/apps/storefront/src/components/islands/VariantSelector.tsx +3 -3
- package/apps/storefront/src/components/marketing/CardGrid.astro +1 -1
- package/apps/storefront/src/components/marketing/CodeSample.astro +1 -1
- package/apps/storefront/src/components/marketing/CtaBand.astro +1 -1
- package/apps/storefront/src/components/marketing/Faq.astro +1 -1
- package/apps/storefront/src/components/marketing/Integrations.astro +1 -1
- package/apps/storefront/src/components/marketing/MarketingCtas.astro +1 -1
- package/apps/storefront/src/components/marketing/MarketingHero.astro +1 -1
- package/apps/storefront/src/components/marketing/ProofStrip.astro +1 -1
- package/apps/storefront/src/components/marketing/SplitCompare.astro +1 -1
- package/apps/storefront/src/components/marketing/Steps.astro +1 -1
- package/apps/storefront/src/components/marketing/Testimonials.astro +2 -2
- package/apps/storefront/src/components/marketing/TrustBar.astro +1 -1
- package/apps/storefront/src/components/membership/TierComparisonTable.astro +1 -1
- package/apps/storefront/src/components/plp/ActiveFilters.astro +1 -1
- package/apps/storefront/src/components/plp/FacetSidebar.astro +4 -4
- package/apps/storefront/src/components/plp/Pagination.astro +1 -1
- package/apps/storefront/src/components/plp/SortSelect.astro +1 -1
- package/apps/storefront/src/components/style-guide/StyleGuideNav.astro +18 -3
- package/apps/storefront/src/components/the-build/TheBuildIndexView.astro +9 -5
- package/apps/storefront/src/config/devTenantSeed.ts +23 -34
- package/apps/storefront/src/config/resolver.ts +0 -38
- package/apps/storefront/src/layouts/Layout.astro +8 -1
- package/apps/storefront/src/lib/assets/preview-version-cookie.ts +52 -5
- package/apps/storefront/src/lib/blog/pagination.ts +20 -10
- package/apps/storefront/src/lib/blog/presentation.ts +94 -0
- package/apps/storefront/src/lib/blog/rss.ts +1 -1
- package/apps/storefront/src/lib/blog/types.ts +24 -7
- package/apps/storefront/src/lib/breadcrumbs.ts +25 -0
- package/apps/storefront/src/lib/checkoutCommerce.ts +26 -3
- package/apps/storefront/src/lib/chrome/model.ts +4 -0
- package/apps/storefront/src/lib/cloudflare-workers.d.ts +53 -0
- package/apps/storefront/src/lib/compliance/rawComplianceNotices.ts +38 -18
- package/apps/storefront/src/lib/dashboard/delegateGate.ts +28 -0
- package/apps/storefront/src/lib/homeVisualParity.ts +8 -5
- package/apps/storefront/src/lib/jsonld.ts +5 -5
- package/apps/storefront/src/lib/pinnedRouteRewriteBoundary.ts +20 -2
- package/apps/storefront/src/lib/rawChrome.ts +74 -12
- package/apps/storefront/src/lib/runtimeEnv.ts +1 -1
- package/apps/storefront/src/lib/seo/documentTitle.ts +19 -0
- package/apps/storefront/src/lib/shared/addToRelease.ts +32 -0
- package/apps/storefront/src/lib/storyblok/content-model.ts +3 -0
- package/apps/storefront/src/lib/styleGuideThemes.ts +20 -22
- package/apps/storefront/src/pages/[...slug].astro +13 -8
- package/apps/storefront/src/pages/blog/[slug].astro +56 -24
- package/apps/storefront/src/pages/blog/author/[author].astro +17 -9
- package/apps/storefront/src/pages/blog/category/[category].astro +18 -10
- package/apps/storefront/src/pages/blog/index.astro +2 -0
- package/apps/storefront/src/pages/blog/page/[n].astro +2 -0
- package/apps/storefront/src/pages/blog/tag/[tag].astro +18 -10
- package/apps/storefront/src/pages/collections/[handle].astro +5 -4
- package/apps/storefront/src/pages/collections/index.astro +4 -3
- package/apps/storefront/src/pages/index.astro +34 -27
- package/apps/storefront/src/pages/products/[handle].astro +5 -4
- package/apps/storefront/src/pages/saved.astro +4 -3
- package/apps/storefront/src/pages/search.astro +4 -3
- package/apps/storefront/src/pages/style-guide/[tenant]/[theme].astro +129 -118
- package/apps/storefront/src/pages/style-guide/[tenant]/chrome/[theme].astro +19 -15
- package/apps/storefront/src/pages/style-guide/[tenant]/guide/[theme].astro +23 -27
- package/apps/storefront/src/pages/style-guide/[tenant]/index.astro +7 -14
- package/apps/storefront/src/pages/style-guide/index.astro +6 -5
- package/apps/storefront/src/pages/tenants/[id]/[...path].ts +32 -7
- package/apps/storefront/src/pages/the-build/[slug].astro +44 -20
- package/apps/storefront/src/pages/the-build/index.astro +2 -0
- package/apps/storefront/src/pages/the-build/page/[n].astro +2 -0
- package/apps/storefront/src/styles/admin.css +47 -0
- package/apps/storefront/src/styles/global.css +104 -56
- package/apps/storefront/src/themes/derivedTokens.ts +34 -0
- package/apps/storefront/src/themes/schema.ts +6 -0
- package/apps/storefront/test/pipeline/harness.ts +164 -0
- package/apps/storefront/test/pipeline/intake-worker.mjs +14 -0
- package/apps/storefront/test/pipeline/pipeline-host.ts +77 -0
- package/apps/storefront/test/pipeline/synthetic-job.ts +156 -0
- package/apps/storefront/vitest.workers.config.cts +67 -0
- package/docs/widget-library-guide.md +1496 -0
- package/package.json +1 -1
- package/packages/cli/src/declared-config.mjs +400 -5
- package/packages/public-runtime/package.json +1 -0
- package/packages/public-runtime/src/checkout.ts +13 -0
- package/packages/public-runtime/src/chrome.ts +93 -2
- package/packages/public-runtime/src/compliance-coherence.d.mts +17 -0
- package/packages/public-runtime/src/compliance-coherence.mjs +84 -0
- package/packages/public-runtime/src/declared-tenant-config.d.mts +1 -1
- package/packages/public-runtime/src/declared-tenant-config.mjs +6 -2
- package/packages/public-runtime/src/extension-contract-values.d.mts +4 -1
- package/packages/public-runtime/src/extension-contract-values.mjs +14 -1
- package/packages/public-runtime/src/extension-contract.ts +2 -0
- package/packages/public-runtime/src/tenant.ts +48 -4
- package/pnpm-lock.runner.yaml +469 -0
- package/scripts/tenant/validate.mjs +27 -1
|
@@ -14,15 +14,19 @@
|
|
|
14
14
|
* The tenant's identity IS its dir name (the colocation convention: dir ==
|
|
15
15
|
* tenant_id == appDomain == tot_data_scope). Content (content/home.html) and
|
|
16
16
|
* theme (theme.json) come straight from the checkout via the existing dir globs;
|
|
17
|
-
* catalog
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* catalog data it lacks resolves to an empty catalog, exactly as on the hosted
|
|
18
|
+
* Worker. Site type is never inferred here: like a provisioned tenant record the
|
|
19
|
+
* synthesized one leaves `siteType` undeclared (commerce), and the checkout's own
|
|
20
|
+
* `.tot/config.json` declaration — applied on top of this record on every surface
|
|
21
|
+
* (config/declaredTenantConfig.ts) — decides it. So `tot dev` renders a store's
|
|
22
|
+
* home.json with the same block vocabulary the hosted build renders.
|
|
20
23
|
*
|
|
21
24
|
* DEV ONLY: in prod the TENANT_CACHE KV drives the resolver and staticTenants is
|
|
22
25
|
* the seed; this path is never taken. Kept inside `src/config` per the
|
|
23
26
|
* tenancy-resolver-fence — the concrete registry stays behind the config boundary.
|
|
24
27
|
*/
|
|
25
28
|
import type { DeepPartial, TenantConfig, ThemeTokens } from "@tot/public-runtime";
|
|
29
|
+
import { SHARED_DEMO_CHECKOUT } from "@tot/public-runtime";
|
|
26
30
|
import { rootPublicationSpaceConfig } from "./publicationSpaceConfig";
|
|
27
31
|
import { loadTenantAnalyticsDeclarations } from "@/lib/integration/tenantAnalyticsDeclarations";
|
|
28
32
|
|
|
@@ -35,13 +39,6 @@ export interface DevTenantHints {
|
|
|
35
39
|
* Undefined ⇒ the colocation convention (dir == appDomain == scope).
|
|
36
40
|
*/
|
|
37
41
|
appDomain?: string;
|
|
38
|
-
/**
|
|
39
|
-
* True when the checkout has a catalog: its own `content/catalog/products/` files,
|
|
40
|
-
* or a grandfathered in-repo snapshot for its data scope. When true we serve a real
|
|
41
|
-
* COMMERCE tenant so /collections + /products render from it; when false we degrade
|
|
42
|
-
* to a marketing home (no catalog).
|
|
43
|
-
*/
|
|
44
|
-
hasCatalog?: boolean;
|
|
45
42
|
/**
|
|
46
43
|
* The tenant's own `tenants/<id>/analytics.json` content (already-parsed JSON),
|
|
47
44
|
* or undefined when the tenant declares none. Materialized into `integrations`
|
|
@@ -56,10 +53,9 @@ export interface DevTenantHints {
|
|
|
56
53
|
* colocated dir name (dir == id == appDomain == tot_data_scope); a slug tenant can
|
|
57
54
|
* override the resolvable appDomain via `hints.appDomain`. No Storyblok space
|
|
58
55
|
* (a "PLACEHOLDER" token → LocalContentProvider serves the checkout's content/).
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* Theme tokens come from the checkout's theme.json.
|
|
56
|
+
* `siteType` stays undeclared, as on a provisioned record, and the demo checkout
|
|
57
|
+
* store is wired so add-to-cart works on localhost. Theme tokens come from the
|
|
58
|
+
* checkout's theme.json.
|
|
63
59
|
*/
|
|
64
60
|
export function synthesizeDevTenant(
|
|
65
61
|
id: string,
|
|
@@ -98,27 +94,22 @@ export function synthesizeDevTenant(
|
|
|
98
94
|
...(integrations.length > 0 ? { integrations } : {}),
|
|
99
95
|
...(euUkVisitors ? { euUkVisitors } : {}),
|
|
100
96
|
};
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
// No catalog on disk → serve the raw marketing home and degrade commerce to
|
|
112
|
-
// empty rather than 404 the tenant root.
|
|
113
|
-
return { ...base, siteType: "marketing" };
|
|
97
|
+
// Cart wires to the shared ToT demo checkout store (test mode, no secret), the
|
|
98
|
+
// same as the bundled demo tenants — and the SAME constant the hosted QA/test
|
|
99
|
+
// tier's demo-checkout fallback reuses (checkoutCommerce.ts), so both surfaces
|
|
100
|
+
// stay wired to one definition. A marketing tenant never resolves it (the
|
|
101
|
+
// render chain gates on features.catalog); a commerce one gets its cart
|
|
102
|
+
// affordance and /cart on localhost whether or not it has catalog files yet.
|
|
103
|
+
return {
|
|
104
|
+
...base,
|
|
105
|
+
commerce: { checkout: SHARED_DEMO_CHECKOUT },
|
|
106
|
+
};
|
|
114
107
|
}
|
|
115
108
|
|
|
116
109
|
/** Per-dir disk facts the resolver supplies so synthesis can go beyond colocation. */
|
|
117
110
|
export interface DevTenantSeedOptions {
|
|
118
111
|
/** Resolved appDomain for a slug tenant (from `.tot/config.json` scope). */
|
|
119
112
|
scopeFor?: (id: string) => string | undefined;
|
|
120
|
-
/** Whether the checkout `id` (data scope `scope`) has a catalog on disk. */
|
|
121
|
-
hasCatalog?: (scope: string, id: string) => boolean;
|
|
122
113
|
/** The tenant's parsed `tenants/<id>/analytics.json`, or undefined when it has none. */
|
|
123
114
|
analyticsFor?: (id: string) => unknown;
|
|
124
115
|
}
|
|
@@ -127,12 +118,12 @@ export interface DevTenantSeedOptions {
|
|
|
127
118
|
* The dev tenants to seed ALONGSIDE staticTenants: one synthesized config per
|
|
128
119
|
* local tenant dir that isn't already a static tenant (those keep their richer
|
|
129
120
|
* hardcoded config). `themeFor` supplies each dir's theme tokens; `opts` carries
|
|
130
|
-
* the per-dir disk facts (
|
|
121
|
+
* the per-dir disk facts (slug scope, analytics) the resolver reads.
|
|
131
122
|
*
|
|
132
123
|
* @param localDirIds dir names discovered under `tenants/*` (dir == tenant id)
|
|
133
124
|
* @param staticIds tenant_ids already in the static registry (skipped)
|
|
134
125
|
* @param themeFor theme-token lookup by dir/tenant id
|
|
135
|
-
* @param opts optional per-dir hints (scopeFor,
|
|
126
|
+
* @param opts optional per-dir hints (scopeFor, analyticsFor)
|
|
136
127
|
*/
|
|
137
128
|
export function devTenantsToSeed(
|
|
138
129
|
localDirIds: Iterable<string>,
|
|
@@ -146,10 +137,8 @@ export function devTenantsToSeed(
|
|
|
146
137
|
if (!id || staticIds.has(id) || seen.has(id)) continue;
|
|
147
138
|
seen.add(id);
|
|
148
139
|
const appDomain = opts.scopeFor?.(id);
|
|
149
|
-
const scope = appDomain ?? id;
|
|
150
|
-
const hasCatalog = opts.hasCatalog?.(scope, id) ?? false;
|
|
151
140
|
const analyticsRaw = opts.analyticsFor?.(id);
|
|
152
|
-
out.push(synthesizeDevTenant(id, themeFor(id), { appDomain,
|
|
141
|
+
out.push(synthesizeDevTenant(id, themeFor(id), { appDomain, analyticsRaw }));
|
|
153
142
|
}
|
|
154
143
|
return out;
|
|
155
144
|
}
|
|
@@ -64,41 +64,6 @@ function localTenantDirIds(): string[] {
|
|
|
64
64
|
return [...ids];
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
// Catalog fixtures committed on disk, keyed by data scope (== out/catalog dir
|
|
68
|
-
// name). A dev tenant whose scope has fixtures is served as a real COMMERCE tenant
|
|
69
|
-
// (see devTenantSeed); one without them degrades to a marketing home. Keys only —
|
|
70
|
-
// the same fixtures the FixtureToTClient reads. Repo root is four up from config/.
|
|
71
|
-
const catalogFixtureFiles = import.meta.glob(
|
|
72
|
-
"../../../../packages/migration/out/catalog/*/products.json",
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
// A checkout's own catalog files (ADR 0033): `tot dev` grafts the store's
|
|
76
|
-
// `content/catalog/` here, and the catalog reader serves them.
|
|
77
|
-
const storeCatalogFiles = import.meta.glob([
|
|
78
|
-
"../../../../tenants/*/content/catalog/products/*.json",
|
|
79
|
-
"../../../../tenants/e2e/*/content/catalog/products/*.json",
|
|
80
|
-
]);
|
|
81
|
-
|
|
82
|
-
/** Tenant dir ids whose checkout carries its own catalog files. */
|
|
83
|
-
function storeCatalogIds(): Set<string> {
|
|
84
|
-
const ids = new Set<string>();
|
|
85
|
-
for (const key of Object.keys(storeCatalogFiles)) {
|
|
86
|
-
const id = tenantIdFromPath(key);
|
|
87
|
-
if (id) ids.add(id);
|
|
88
|
-
}
|
|
89
|
-
return ids;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** Data scopes (== out/catalog dir names) that have committed catalog fixtures. */
|
|
93
|
-
function catalogScopes(): Set<string> {
|
|
94
|
-
const scopes = new Set<string>();
|
|
95
|
-
for (const key of Object.keys(catalogFixtureFiles)) {
|
|
96
|
-
const scope = /\/out\/catalog\/([^/]+)\//.exec(key)?.[1];
|
|
97
|
-
if (scope) scopes.add(scope);
|
|
98
|
-
}
|
|
99
|
-
return scopes;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
67
|
// A slug tenant (dir name != its domain) declares its resolvable appDomain in
|
|
103
68
|
// the checkout's `.tot/config.json` `scope`. Read eagerly (tiny JSON) so synthesis
|
|
104
69
|
// can resolve it at `/<domain>/`; trusted only when it looks like a hostname (has a
|
|
@@ -136,15 +101,12 @@ function slugScopes(): Map<string, string> {
|
|
|
136
101
|
function devFallbackResolver(): TenantResolver {
|
|
137
102
|
if (!import.meta.env.DEV) return staticResolver;
|
|
138
103
|
const staticIds = new Set(staticTenants.map((t) => t.tenant_id));
|
|
139
|
-
const withCatalog = catalogScopes();
|
|
140
|
-
const withStoreCatalog = storeCatalogIds();
|
|
141
104
|
const scopes = slugScopes();
|
|
142
105
|
// The SAME tenant-owned analytics read the static registry uses; here it serves only
|
|
143
106
|
// the tenants that have NO registry entry (see config/tenantAnalyticsFiles.ts).
|
|
144
107
|
const analytics = tenantAnalyticsRawById();
|
|
145
108
|
const dev = devTenantsToSeed(localTenantDirIds(), staticIds, getTenantTheme, {
|
|
146
109
|
scopeFor: (id) => scopes.get(id),
|
|
147
|
-
hasCatalog: (scope, id) => withStoreCatalog.has(id) || withCatalog.has(scope),
|
|
148
110
|
analyticsFor: (id) => analytics.get(id),
|
|
149
111
|
});
|
|
150
112
|
if (dev.length === 0) return staticResolver;
|
|
@@ -16,6 +16,7 @@ import QuickView from "@/components/islands/QuickView.tsx";
|
|
|
16
16
|
import CheckoutLoader from "@/components/commerce/CheckoutLoader.astro";
|
|
17
17
|
import CartDrawerStyles from "@/components/commerce/CartDrawerStyles.astro";
|
|
18
18
|
import NicotineWarning from "@/components/compliance/NicotineWarning.astro";
|
|
19
|
+
import PactActNotice from "@/components/compliance/PactActNotice.astro";
|
|
19
20
|
import ShippingRestrictionNotice from "@/components/compliance/ShippingRestrictionNotice.astro";
|
|
20
21
|
import IsolatedAgeGate from "@/components/islands/IsolatedAgeGate.tsx";
|
|
21
22
|
import AuthBadge from "@/components/auth/AuthBadge.astro";
|
|
@@ -38,6 +39,8 @@ import { renderWidgetSlot } from "@/lib/apps/widgets/renderSlot.js";
|
|
|
38
39
|
|
|
39
40
|
interface Props {
|
|
40
41
|
title: string;
|
|
42
|
+
/** The page's own complete `<title>`, rendered verbatim (threaded to <Seo>). */
|
|
43
|
+
seoTitle?: string;
|
|
41
44
|
description?: string;
|
|
42
45
|
path: string;
|
|
43
46
|
ogType?: "website" | "product" | "article";
|
|
@@ -80,6 +83,7 @@ interface Props {
|
|
|
80
83
|
|
|
81
84
|
const {
|
|
82
85
|
title,
|
|
86
|
+
seoTitle,
|
|
83
87
|
description,
|
|
84
88
|
path,
|
|
85
89
|
ogType,
|
|
@@ -298,6 +302,7 @@ const footerWidgets = await renderWidgetSlot(
|
|
|
298
302
|
|
|
299
303
|
<Seo
|
|
300
304
|
title={title}
|
|
305
|
+
seoTitle={seoTitle}
|
|
301
306
|
description={description}
|
|
302
307
|
path={path}
|
|
303
308
|
ogType={ogType}
|
|
@@ -331,7 +336,7 @@ const footerWidgets = await renderWidgetSlot(
|
|
|
331
336
|
|
|
332
337
|
<AuthBadge />
|
|
333
338
|
|
|
334
|
-
{compliance?.nicotineWarning && <NicotineWarning variant="bar" />}
|
|
339
|
+
{compliance?.nicotineWarning && <NicotineWarning variant="bar" declaration={compliance.nicotineWarning} />}
|
|
335
340
|
|
|
336
341
|
{sharedChrome ? (
|
|
337
342
|
<Fragment set:html={sharedChrome.header} />
|
|
@@ -369,6 +374,7 @@ const footerWidgets = await renderWidgetSlot(
|
|
|
369
374
|
<slot />
|
|
370
375
|
</main>
|
|
371
376
|
|
|
377
|
+
<PactActNotice enabled={compliance?.pactAct} minAge={compliance?.minAge} variant="footer" />
|
|
372
378
|
<ShippingRestrictionNotice text={compliance?.shippingRestriction} />
|
|
373
379
|
|
|
374
380
|
{sharedChrome ? (
|
|
@@ -400,6 +406,7 @@ const footerWidgets = await renderWidgetSlot(
|
|
|
400
406
|
}
|
|
401
407
|
payments={chrome.footer.payments}
|
|
402
408
|
social={chrome.footer.social}
|
|
409
|
+
disclaimer={chrome.footer.disclaimer}
|
|
403
410
|
compliance={footerCompliance}
|
|
404
411
|
legal={legalLine}
|
|
405
412
|
bottomLinks={bottomLinks}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Resolve the
|
|
2
|
+
* Resolve the asset version a preview subresource reads.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* `
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* `resolvePreviewVersionFromCookies` is the candidate/revision pin: candidate and
|
|
5
|
+
* revision renderers set `tot_preview_version` (the exact sha) and `tot_preview`
|
|
6
|
+
* (capability token). Subresource GETs (img `/_a/…`, `/tenants/…`) carry those
|
|
7
|
+
* cookies but not `__tot_candidate_pin`, so this is the revision authority for
|
|
8
|
+
* revision-bound captured originals and preview assets.
|
|
9
|
+
*
|
|
10
|
+
* `resolveSharedPreviewAssetVersion` covers the shared preview, which sets no
|
|
11
|
+
* version cookie: it reads the version the preview channel serves.
|
|
8
12
|
*/
|
|
9
13
|
import { verifyPreviewToken } from "@tot/public-runtime";
|
|
10
14
|
|
|
@@ -29,3 +33,46 @@ export async function resolvePreviewVersionFromCookies(input: {
|
|
|
29
33
|
if (!verified.ok || verified.claims.tenantId !== input.tenantId) return null;
|
|
30
34
|
return versionId;
|
|
31
35
|
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Which asset snapshot a SHARED-preview subresource reads.
|
|
39
|
+
*
|
|
40
|
+
* The shared preview document renders the content `preview` channel's version — set
|
|
41
|
+
* by `reconcilePreview` and by the aggregate reconcile alike, and both ingest that
|
|
42
|
+
* version's `public/` subtree under the SAME versionId. So a subresource the preview
|
|
43
|
+
* document references must read that exact version too. It is NOT the assets `test`
|
|
44
|
+
* pointer: the aggregate reconcile ingests ingest-only and never moves it, so after
|
|
45
|
+
* "Add to release" that pointer names an older snapshot or nothing, and every tenant
|
|
46
|
+
* asset 404s under a page that rendered fine.
|
|
47
|
+
*
|
|
48
|
+
* A request is a shared-preview read when it is on the preview host (`previewHost`)
|
|
49
|
+
* or carries a valid `tot_preview` token for THIS tenant — the cookie the preview
|
|
50
|
+
* document sets from its `?__preview=` link, or the token on the asset URL itself.
|
|
51
|
+
* `versionId` is null for a non-preview read, or when the preview channel names no
|
|
52
|
+
* version yet (the caller then falls back to the assets `test` pointer).
|
|
53
|
+
*/
|
|
54
|
+
export async function resolveSharedPreviewAssetVersion(input: {
|
|
55
|
+
tenantId: string;
|
|
56
|
+
previewHost: boolean;
|
|
57
|
+
previewToken: string | null | undefined;
|
|
58
|
+
secret: string | null | undefined;
|
|
59
|
+
readPreviewChannelVersion: () => Promise<string | null>;
|
|
60
|
+
nowSeconds?: number;
|
|
61
|
+
}): Promise<{ preview: boolean; versionId: string | null }> {
|
|
62
|
+
let preview = input.previewHost;
|
|
63
|
+
const token = input.previewToken?.trim() || null;
|
|
64
|
+
if (!preview && token && input.secret && input.tenantId) {
|
|
65
|
+
const verified = await verifyPreviewToken(
|
|
66
|
+
input.secret,
|
|
67
|
+
token,
|
|
68
|
+
input.nowSeconds ?? Math.floor(Date.now() / 1000),
|
|
69
|
+
);
|
|
70
|
+
preview = verified.ok && verified.claims.tenantId === input.tenantId;
|
|
71
|
+
}
|
|
72
|
+
if (!preview) return { preview: false, versionId: null };
|
|
73
|
+
const versionId = await input.readPreviewChannelVersion();
|
|
74
|
+
return {
|
|
75
|
+
preview: true,
|
|
76
|
+
versionId: versionId && SAFE_VERSION_ID.test(versionId) && !versionId.includes("..") ? versionId : null,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -8,41 +8,51 @@ export const DEFAULT_BLOG_PAGE_SIZE = 12;
|
|
|
8
8
|
* an absent one falls back to a NEUTRAL default derived from the tenant's own
|
|
9
9
|
* `displayName` — never Token of Trust's own copy (fb-1791301493691-brzl6f). A
|
|
10
10
|
* tenant that wants ToT's current copy (tokenoftrust.com included) declares it
|
|
11
|
-
* in its own `content/blog/settings.json`.
|
|
11
|
+
* in its own `content/blog/settings.json`. `eyebrow`/`tagline` are null when the
|
|
12
|
+
* tenant declared them empty: the index renders no element for them.
|
|
12
13
|
*/
|
|
13
14
|
export interface BlogIndexCopy {
|
|
14
15
|
title: string;
|
|
15
|
-
eyebrow: string;
|
|
16
|
-
tagline: string;
|
|
16
|
+
eyebrow: string | null;
|
|
17
|
+
tagline: string | null;
|
|
17
18
|
description: string;
|
|
18
19
|
pageSize: number;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
function trimmed(value: string | undefined): string | undefined {
|
|
22
|
+
function trimmed(value: string | null | undefined): string | undefined {
|
|
22
23
|
const v = value?.trim();
|
|
23
24
|
return v ? v : undefined;
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
/**
|
|
28
|
+
* An optional line of copy: declared text wins, a declared `""`/`null` suppresses it, and only an
|
|
29
|
+
* ABSENT field takes the default — so a migrated index that has no eyebrow or tagline can say so.
|
|
30
|
+
*/
|
|
31
|
+
function optionalCopy(value: string | null | undefined, fallback: string): string | null {
|
|
32
|
+
if (value === undefined) return fallback;
|
|
33
|
+
return trimmed(value) ?? null;
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
/**
|
|
27
37
|
* Generic engine behind `resolveBlogIndexCopy` and every other per-tenant
|
|
28
38
|
* collection index (e.g. `resolveTheBuildIndexCopy` in `../the-build/pagination.js`):
|
|
29
39
|
* a declared settings field wins; an absent one falls back to the caller's own
|
|
30
40
|
* NEUTRAL, tenant-derived default — never a Token of Trust-branded one
|
|
31
|
-
* (fb-1791301493691-brzl6f, and its the-build sibling).
|
|
32
|
-
* also the description fallback, matching every collection's settings schema.
|
|
41
|
+
* (fb-1791301493691-brzl6f, and its the-build sibling). The tagline (declared or
|
|
42
|
+
* default) is also the description fallback, matching every collection's settings schema.
|
|
33
43
|
*/
|
|
34
44
|
export function resolveIndexCopy(
|
|
35
|
-
settings:
|
|
45
|
+
settings: Pick<BlogSettings, "title" | "eyebrow" | "tagline" | "description" | "pageSize"> | null | undefined,
|
|
36
46
|
defaults: { title: string; eyebrow: string; tagline: string; description?: string },
|
|
37
47
|
defaultPageSize: number,
|
|
38
48
|
): BlogIndexCopy {
|
|
39
|
-
const tagline =
|
|
49
|
+
const tagline = optionalCopy(settings?.tagline, defaults.tagline);
|
|
40
50
|
const pageSize = settings?.pageSize;
|
|
41
51
|
return {
|
|
42
52
|
title: trimmed(settings?.title) ?? defaults.title,
|
|
43
|
-
eyebrow:
|
|
53
|
+
eyebrow: optionalCopy(settings?.eyebrow, defaults.eyebrow),
|
|
44
54
|
tagline,
|
|
45
|
-
description: trimmed(settings?.description) ?? defaults.description ?? tagline,
|
|
55
|
+
description: trimmed(settings?.description) ?? defaults.description ?? tagline ?? defaults.tagline,
|
|
46
56
|
pageSize: Number.isInteger(pageSize) && pageSize! > 0 ? pageSize! : defaultPageSize,
|
|
47
57
|
};
|
|
48
58
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { BlogSettings } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Which parts of the blog templates render, resolved from a collection's `settings.json`. Every
|
|
5
|
+
* part defaults ON; a tenant turns one off by declaring `false` (or `share: []`), so a migrated
|
|
6
|
+
* blog can match its source without hiding platform markup with CSS.
|
|
7
|
+
*/
|
|
8
|
+
export interface BlogPresentation {
|
|
9
|
+
hero: boolean;
|
|
10
|
+
topics: boolean;
|
|
11
|
+
postEyebrow: boolean;
|
|
12
|
+
dek: boolean;
|
|
13
|
+
aside: boolean;
|
|
14
|
+
reactions: boolean;
|
|
15
|
+
share: ShareTarget[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Every share action the post template knows how to render. */
|
|
19
|
+
export const SHARE_TARGETS = ["facebook", "email", "print", "x", "pinterest"] as const;
|
|
20
|
+
export type ShareTarget = (typeof SHARE_TARGETS)[number];
|
|
21
|
+
|
|
22
|
+
/** The share row a post renders when its collection declares no `share`. */
|
|
23
|
+
export const DEFAULT_SHARE_TARGETS: readonly ShareTarget[] = SHARE_TARGETS;
|
|
24
|
+
|
|
25
|
+
function isShareTarget(value: unknown): value is ShareTarget {
|
|
26
|
+
return typeof value === "string" && (SHARE_TARGETS as readonly string[]).includes(value);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Only an explicit `false` turns a part off; anything else keeps the platform default. */
|
|
30
|
+
function on(value: unknown): boolean {
|
|
31
|
+
return value !== false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function resolveBlogPresentation(settings: BlogSettings | null | undefined): BlogPresentation {
|
|
35
|
+
const share = Array.isArray(settings?.share)
|
|
36
|
+
? [...new Set(settings.share.filter(isShareTarget))]
|
|
37
|
+
: [...DEFAULT_SHARE_TARGETS];
|
|
38
|
+
return {
|
|
39
|
+
hero: on(settings?.hero),
|
|
40
|
+
topics: on(settings?.topics),
|
|
41
|
+
postEyebrow: on(settings?.postEyebrow),
|
|
42
|
+
dek: on(settings?.dek),
|
|
43
|
+
aside: on(settings?.aside),
|
|
44
|
+
reactions: on(settings?.reactions),
|
|
45
|
+
share,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The byline name, or null when the post names no author. A post without an author renders no
|
|
51
|
+
* byline at all: the platform never attributes a post to anyone its content does not name.
|
|
52
|
+
*/
|
|
53
|
+
export function bylineAuthor(author: string | null | undefined): string | null {
|
|
54
|
+
const name = author?.trim();
|
|
55
|
+
return name ? name : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ShareLink {
|
|
59
|
+
target: ShareTarget;
|
|
60
|
+
/** Visible text. */
|
|
61
|
+
text: string;
|
|
62
|
+
/** Accessible name. */
|
|
63
|
+
label: string;
|
|
64
|
+
/** Null for `print`, which is a button the page script wires to `window.print()`. */
|
|
65
|
+
href: string | null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const SHARE_COPY: Record<ShareTarget, { text: string; label: string }> = {
|
|
69
|
+
facebook: { text: "Facebook", label: "Share on Facebook" },
|
|
70
|
+
email: { text: "Email", label: "Share by email" },
|
|
71
|
+
print: { text: "Print", label: "Print this article" },
|
|
72
|
+
x: { text: "X", label: "Share on X" },
|
|
73
|
+
pinterest: { text: "Pinterest", label: "Pin on Pinterest" },
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The share row for one post. `url` is the post's canonical URL, so a share from a preview host
|
|
78
|
+
* still points readers at the live article.
|
|
79
|
+
*/
|
|
80
|
+
export function shareLinks(
|
|
81
|
+
targets: readonly ShareTarget[],
|
|
82
|
+
post: { url: string; title: string; image?: string },
|
|
83
|
+
): ShareLink[] {
|
|
84
|
+
const u = encodeURIComponent(post.url);
|
|
85
|
+
const t = encodeURIComponent(post.title);
|
|
86
|
+
const href: Record<ShareTarget, string | null> = {
|
|
87
|
+
facebook: `https://www.facebook.com/sharer/sharer.php?u=${u}`,
|
|
88
|
+
email: `mailto:?subject=${t}&body=${u}`,
|
|
89
|
+
print: null,
|
|
90
|
+
x: `https://x.com/intent/tweet?url=${u}&text=${t}`,
|
|
91
|
+
pinterest: `https://pinterest.com/pin/create/button/?url=${u}&description=${t}${post.image ? `&media=${encodeURIComponent(post.image)}` : ""}`,
|
|
92
|
+
};
|
|
93
|
+
return targets.map((target) => ({ target, ...SHARE_COPY[target], href: href[target] }));
|
|
94
|
+
}
|
|
@@ -68,7 +68,7 @@ export function renderRssFeed(input: RssFeedInput): string {
|
|
|
68
68
|
` <link>${escapeXml(link)}</link>`,
|
|
69
69
|
` <guid isPermaLink="true">${escapeXml(link)}</guid>`,
|
|
70
70
|
pubDate ? ` <pubDate>${pubDate}</pubDate>` : "",
|
|
71
|
-
` <dc:creator>${escapeXml(post.author)}</dc:creator
|
|
71
|
+
post.author.trim() ? ` <dc:creator>${escapeXml(post.author.trim())}</dc:creator>` : "",
|
|
72
72
|
` <description>${escapeXml(post.excerpt)}</description>`,
|
|
73
73
|
contentEncoded,
|
|
74
74
|
categories,
|
|
@@ -45,21 +45,38 @@ export interface BlogPost extends BlogPostSummary {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
|
-
* Optional per-tenant blog
|
|
49
|
-
* `tenants/<id>/content
|
|
50
|
-
* field, or an absent file, resolves to a NEUTRAL default derived from the
|
|
48
|
+
* Optional per-tenant blog copy, page size and presentation, sourced from
|
|
49
|
+
* `tenants/<id>/content/<collection>/settings.json`. Every field is optional — an
|
|
50
|
+
* absent field, or an absent file, resolves to a NEUTRAL default derived from the
|
|
51
51
|
* tenant's own `displayName` (see `resolveBlogIndexCopy` in `./pagination.ts`),
|
|
52
|
-
* never Token of Trust's own copy
|
|
53
|
-
*
|
|
52
|
+
* never Token of Trust's own copy, and to the full platform presentation (see
|
|
53
|
+
* `resolveBlogPresentation` in `./presentation.ts`). A tenant that wants its own
|
|
54
|
+
* branding (tokenoftrust.com included) declares it here.
|
|
54
55
|
*/
|
|
55
56
|
export interface BlogSettings {
|
|
56
57
|
title?: string;
|
|
57
|
-
eyebrow
|
|
58
|
-
|
|
58
|
+
/** Index eyebrow. `""` or `null` renders none; absent uses the platform default. */
|
|
59
|
+
eyebrow?: string | null;
|
|
60
|
+
/** Index tagline. `""` or `null` renders none; absent uses the platform default. */
|
|
61
|
+
tagline?: string | null;
|
|
59
62
|
description?: string;
|
|
60
63
|
/** Posts per `/blog/` page. Must be a positive integer; anything else falls
|
|
61
64
|
* back to the platform default (`DEFAULT_BLOG_PAGE_SIZE`). */
|
|
62
65
|
pageSize?: number;
|
|
66
|
+
/** Coloured band behind the index and tag/category/author archive headings. Default true. */
|
|
67
|
+
hero?: boolean;
|
|
68
|
+
/** "Explore topics" tag list beside the index grid. Default true. */
|
|
69
|
+
topics?: boolean;
|
|
70
|
+
/** Post eyebrow (the post's first tag above its title). Default true. */
|
|
71
|
+
postEyebrow?: boolean;
|
|
72
|
+
/** Post dek (the excerpt repeated under the title). Default true. */
|
|
73
|
+
dek?: boolean;
|
|
74
|
+
/** Post side column ("← All articles" + tag links). Default true; when false the tags follow the body. */
|
|
75
|
+
aside?: boolean;
|
|
76
|
+
/** Reader reaction bar under the post. Default true. */
|
|
77
|
+
reactions?: boolean;
|
|
78
|
+
/** Share row after the post's tags, in this order. `[]` renders none; absent uses `DEFAULT_SHARE_TARGETS`. */
|
|
79
|
+
share?: string[];
|
|
63
80
|
}
|
|
64
81
|
|
|
65
82
|
/**
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ChromeConfig } from "./chrome/model.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One breadcrumb trail per page. A page builds it ONCE with `siteTrail` and hands the same array
|
|
5
|
+
* to the visible `<Breadcrumbs>` and to `breadcrumbLd`, so what a reader sees and what search
|
|
6
|
+
* engines read cannot disagree.
|
|
7
|
+
*/
|
|
8
|
+
export interface Crumb {
|
|
9
|
+
label: string;
|
|
10
|
+
/** Tenant-relative path; `<Breadcrumbs>` adds the base path, `breadcrumbLd` the canonical origin. */
|
|
11
|
+
href: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** The first crumb's label when the tenant's chrome declares none. */
|
|
15
|
+
export const DEFAULT_HOME_CRUMB_LABEL = "Home";
|
|
16
|
+
|
|
17
|
+
/** The home crumb's label: the tenant's declared `chrome.json` `breadcrumbs.homeLabel`, else "Home". */
|
|
18
|
+
export function homeCrumbLabel(chrome: Pick<ChromeConfig, "breadcrumbs"> | null | undefined): string {
|
|
19
|
+
return chrome?.breadcrumbs?.homeLabel?.trim() || DEFAULT_HOME_CRUMB_LABEL;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The full trail: the home crumb, then `crumbs` in order. The last crumb is the current page. */
|
|
23
|
+
export function siteTrail(homeLabel: string, ...crumbs: Crumb[]): Crumb[] {
|
|
24
|
+
return [{ label: homeLabel, href: "/" }, ...crumbs];
|
|
25
|
+
}
|
|
@@ -11,19 +11,31 @@
|
|
|
11
11
|
* - `FOXY_CART_SECRET` is the cart-validation secret used only in `signed`
|
|
12
12
|
* mode; the `unsigned-demo` mode never reads it (validation is OFF), which
|
|
13
13
|
* is what lets a developer run the whole cart locally with zero secrets.
|
|
14
|
+
*
|
|
15
|
+
* `COMMERCE_DEMO_CHECKOUT_TIER` gates a FALLBACK for a commerce tenant that has
|
|
16
|
+
* no `commerce.checkout` of its own (e.g. a staff-provisioned store —
|
|
17
|
+
* buildStaffTenantConfig projects a minimal record with none). Declared-tier
|
|
18
|
+
* config, not a tenant check: "test" reuses the exact SHARED_DEMO_CHECKOUT the
|
|
19
|
+
* local dev loop wires (synthesizeDevTenant), so a staff-provisioned store's
|
|
20
|
+
* cart renders on hosted QA preview the same as it does under `tot dev`. Any
|
|
21
|
+
* other value — including unset, the PRODUCTION default — fails CLOSED to no
|
|
22
|
+
* cart: an unpaid production store's preview IS its public site, so a demo
|
|
23
|
+
* checkout must never appear there. A tenant's own declared checkout always
|
|
24
|
+
* wins; this only fills in when `commerce.checkout` is absent.
|
|
14
25
|
*/
|
|
15
26
|
import type { TenantConfig, ResolvedManagedCheckout } from "@tot/public-runtime";
|
|
16
|
-
import { resolveManagedCheckout } from "@tot/public-runtime";
|
|
27
|
+
import { resolveManagedCheckout, SHARED_DEMO_CHECKOUT } from "@tot/public-runtime";
|
|
17
28
|
import { readEnv } from "@/lib/runtimeEnv";
|
|
18
29
|
|
|
19
30
|
/**
|
|
20
31
|
* Resolve the effective checkout facts for a tenant, or null when the tenant has
|
|
21
|
-
* no store configured
|
|
32
|
+
* no store configured (its own, or the QA/test-tier demo fallback). Applies the
|
|
33
|
+
* checkout env overrides.
|
|
22
34
|
*/
|
|
23
35
|
export async function resolveTenantCommerce(
|
|
24
36
|
tenant: TenantConfig,
|
|
25
37
|
): Promise<ResolvedManagedCheckout | null> {
|
|
26
|
-
const checkout = tenant.commerce?.checkout;
|
|
38
|
+
const checkout = tenant.commerce?.checkout ?? (await demoCheckoutFallback());
|
|
27
39
|
if (!checkout) return null;
|
|
28
40
|
// Per-environment overrides so the SAME tenant can point at different stores /
|
|
29
41
|
// checkout hosts by env: FOXY_STORE_SUBDOMAIN picks the checkout store;
|
|
@@ -38,6 +50,17 @@ export async function resolveTenantCommerce(
|
|
|
38
50
|
return resolveManagedCheckout({ ...checkout, storeSubDomain, storeDomain });
|
|
39
51
|
}
|
|
40
52
|
|
|
53
|
+
/**
|
|
54
|
+
* The QA/test-tier-only demo-checkout fallback, or undefined when this
|
|
55
|
+
* environment hasn't declared itself eligible. Fails closed: only the literal
|
|
56
|
+
* value "test" enables it, so an unset var (today's only production-shaped
|
|
57
|
+
* state — there is no `[env.production]` block yet) never does.
|
|
58
|
+
*/
|
|
59
|
+
async function demoCheckoutFallback() {
|
|
60
|
+
const tier = await readEnv("COMMERCE_DEMO_CHECKOUT_TIER");
|
|
61
|
+
return tier === "test" ? SHARED_DEMO_CHECKOUT : undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
41
64
|
/**
|
|
42
65
|
* The cart-validation secret for signed mode. Returns undefined in
|
|
43
66
|
* `unsigned-demo` mode (never needed) or when unset. A signed-mode tenant with
|
|
@@ -23,6 +23,10 @@ export {
|
|
|
23
23
|
type FooterNewsletter,
|
|
24
24
|
type ChromeBrand,
|
|
25
25
|
type ChromeSearch,
|
|
26
|
+
HEADER_ACTION_KINDS,
|
|
27
|
+
type HeaderActionKind,
|
|
28
|
+
type ChromeHeaderActionLink,
|
|
29
|
+
type ChromeHeaderMobile,
|
|
26
30
|
type ChromeUtilityNav,
|
|
27
31
|
type ChromeAnnouncement,
|
|
28
32
|
type ChromeAgeGate,
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The slice of the Workers runtime module `cloudflare:workers` this app uses.
|
|
3
|
+
*
|
|
4
|
+
* The storefront does not depend on @cloudflare/workers-types (its globals collide with the DOM
|
|
5
|
+
* lib Astro compiles against), so the Durable Object surface the pipeline needs is declared here.
|
|
6
|
+
* The module only resolves inside workerd; under Node it is imported lazily and guarded
|
|
7
|
+
* (`lib/runtimeEnv.ts`).
|
|
8
|
+
*/
|
|
9
|
+
declare module "cloudflare:workers" {
|
|
10
|
+
export type SqlStorageValue = ArrayBuffer | string | number | null;
|
|
11
|
+
|
|
12
|
+
export interface SqlStorageCursor<T extends Record<string, SqlStorageValue>> extends Iterable<T> {
|
|
13
|
+
toArray(): T[];
|
|
14
|
+
one(): T;
|
|
15
|
+
readonly rowsRead: number;
|
|
16
|
+
readonly rowsWritten: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SqlStorage {
|
|
20
|
+
exec<T extends Record<string, SqlStorageValue>>(query: string, ...bindings: unknown[]): SqlStorageCursor<T>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DurableObjectStorage {
|
|
24
|
+
readonly sql: SqlStorage;
|
|
25
|
+
transactionSync<T>(closure: () => T): T;
|
|
26
|
+
getAlarm(): Promise<number | null>;
|
|
27
|
+
setAlarm(scheduledTime: number | Date): Promise<void>;
|
|
28
|
+
deleteAlarm(): Promise<void>;
|
|
29
|
+
sync(): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface DurableObjectId {
|
|
33
|
+
toString(): string;
|
|
34
|
+
readonly name?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface DurableObjectState {
|
|
38
|
+
readonly id: DurableObjectId;
|
|
39
|
+
readonly storage: DurableObjectStorage;
|
|
40
|
+
blockConcurrencyWhile<T>(callback: () => Promise<T>): Promise<T>;
|
|
41
|
+
waitUntil(promise: Promise<unknown>): void;
|
|
42
|
+
abort(reason?: string): void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export abstract class DurableObject<Env = unknown> {
|
|
46
|
+
protected readonly ctx: DurableObjectState;
|
|
47
|
+
protected readonly env: Env;
|
|
48
|
+
constructor(ctx: DurableObjectState, env: Env);
|
|
49
|
+
alarm?(alarmInfo?: { retryCount: number; isRetry: boolean }): void | Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const env: Record<string, unknown>;
|
|
53
|
+
}
|