@fayz-ai/storefront 0.84.0 → 0.84.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.
Files changed (55) hide show
  1. package/dist/CheckoutPage-DDY4WLFJ.js +12 -0
  2. package/dist/CheckoutPage-DDY4WLFJ.js.map +1 -0
  3. package/dist/MyPurchasesPage-IHCB5QRR.js +11 -0
  4. package/dist/MyPurchasesPage-IHCB5QRR.js.map +1 -0
  5. package/dist/OrderConfirmationPage-SAKAGOCC.js +10 -0
  6. package/dist/OrderConfirmationPage-SAKAGOCC.js.map +1 -0
  7. package/dist/app/createStorefrontApp.d.ts.map +1 -1
  8. package/dist/chunk-45R7CIEA.js +234 -0
  9. package/dist/chunk-45R7CIEA.js.map +1 -0
  10. package/dist/chunk-BKO2AQ3Q.js +329 -0
  11. package/dist/chunk-BKO2AQ3Q.js.map +1 -0
  12. package/dist/chunk-CV6RYJIC.js +454 -0
  13. package/dist/chunk-CV6RYJIC.js.map +1 -0
  14. package/dist/chunk-D5C5U2SG.js +267 -0
  15. package/dist/chunk-D5C5U2SG.js.map +1 -0
  16. package/dist/chunk-DRMDBXMT.js +2123 -0
  17. package/dist/chunk-DRMDBXMT.js.map +1 -0
  18. package/dist/chunk-DVQHKNNX.js +29 -0
  19. package/dist/chunk-DVQHKNNX.js.map +1 -0
  20. package/dist/chunk-FCPOPWG3.js +31 -0
  21. package/dist/chunk-FCPOPWG3.js.map +1 -0
  22. package/dist/chunk-GQ3TCJVX.js +216 -0
  23. package/dist/chunk-GQ3TCJVX.js.map +1 -0
  24. package/dist/{chunk-7N5HKQBM.js → chunk-GVZ2SAOI.js} +5 -552
  25. package/dist/chunk-GVZ2SAOI.js.map +1 -0
  26. package/dist/{chunk-QU5O4CKY.js → chunk-H6IOPA3J.js} +5 -4
  27. package/dist/chunk-H6IOPA3J.js.map +1 -0
  28. package/dist/{chunk-7FR6KOW2.js → chunk-HRLF5NYO.js} +1237 -6583
  29. package/dist/chunk-HRLF5NYO.js.map +1 -0
  30. package/dist/chunk-K27LYMSF.js +435 -0
  31. package/dist/chunk-K27LYMSF.js.map +1 -0
  32. package/dist/chunk-MFAU3AWK.js +817 -0
  33. package/dist/chunk-MFAU3AWK.js.map +1 -0
  34. package/dist/chunk-OHWX6BEY.js +436 -0
  35. package/dist/chunk-OHWX6BEY.js.map +1 -0
  36. package/dist/chunk-PATS4DIF.js +484 -0
  37. package/dist/chunk-PATS4DIF.js.map +1 -0
  38. package/dist/chunk-WDQFNHYO.js +41 -0
  39. package/dist/chunk-WDQFNHYO.js.map +1 -0
  40. package/dist/chunk-WPWNYBNG.js +68 -0
  41. package/dist/chunk-WPWNYBNG.js.map +1 -0
  42. package/dist/chunk-WXXYJZE6.js +99 -0
  43. package/dist/chunk-WXXYJZE6.js.map +1 -0
  44. package/dist/host.js +4 -2
  45. package/dist/host.js.map +1 -1
  46. package/dist/index.js +26 -6
  47. package/dist/index.js.map +1 -1
  48. package/dist/render.js +11 -3
  49. package/dist/render.js.map +1 -1
  50. package/dist/tanstack.js +10 -2
  51. package/dist/tanstack.js.map +1 -1
  52. package/package.json +5 -5
  53. package/dist/chunk-7FR6KOW2.js.map +0 -1
  54. package/dist/chunk-7N5HKQBM.js.map +0 -1
  55. package/dist/chunk-QU5O4CKY.js.map +0 -1
@@ -0,0 +1,68 @@
1
+ import { useSessionStore } from './chunk-PATS4DIF.js';
2
+ import { useState, useCallback, useEffect } from 'react';
3
+ import { getShopProvider } from '@fayz-ai/shop/runtime';
4
+
5
+ // src/data/local-orders.ts
6
+ var KEY = "fayz.storefront.orders.v1";
7
+ var MAX = 25;
8
+ function read() {
9
+ try {
10
+ const raw = globalThis.localStorage?.getItem(KEY);
11
+ const parsed = raw ? JSON.parse(raw) : [];
12
+ return Array.isArray(parsed) ? parsed.filter((id) => typeof id === "string") : [];
13
+ } catch {
14
+ return [];
15
+ }
16
+ }
17
+ function rememberLocalOrder(orderId) {
18
+ if (!orderId) return;
19
+ try {
20
+ const next = [orderId, ...read().filter((id) => id !== orderId)].slice(0, MAX);
21
+ globalThis.localStorage?.setItem(KEY, JSON.stringify(next));
22
+ } catch {
23
+ }
24
+ }
25
+ function listLocalOrderIds() {
26
+ return read();
27
+ }
28
+
29
+ // src/hooks/use-my-orders.ts
30
+ function useMyOrders() {
31
+ const customerId = useSessionStore((s) => s.customerId);
32
+ const email = useSessionStore((s) => s.email);
33
+ const [orders, setOrders] = useState([]);
34
+ const [loading, setLoading] = useState(true);
35
+ const [tick, setTick] = useState(0);
36
+ const refresh = useCallback(() => setTick((t) => t + 1), []);
37
+ useEffect(() => {
38
+ let cancelled = false;
39
+ const local = listLocalOrderIds();
40
+ if (!customerId && !email) {
41
+ if (local.length === 0) {
42
+ setOrders([]);
43
+ setLoading(false);
44
+ return;
45
+ }
46
+ }
47
+ setLoading(true);
48
+ const query = customerId ? { customerId, limit: 50 } : { customerEmail: email, limit: 50 };
49
+ getShopProvider().listOrders(query).then((data) => {
50
+ if (!cancelled) setOrders(data);
51
+ }).catch(async () => {
52
+ const mine = await Promise.all(
53
+ listLocalOrderIds().map((id) => getShopProvider().getOrder(id).catch(() => null))
54
+ );
55
+ if (!cancelled) setOrders(mine.filter((o) => !!o));
56
+ }).finally(() => {
57
+ if (!cancelled) setLoading(false);
58
+ });
59
+ return () => {
60
+ cancelled = true;
61
+ };
62
+ }, [customerId, email, tick]);
63
+ return { orders, loading, refresh };
64
+ }
65
+
66
+ export { rememberLocalOrder, useMyOrders };
67
+ //# sourceMappingURL=chunk-WPWNYBNG.js.map
68
+ //# sourceMappingURL=chunk-WPWNYBNG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/data/local-orders.ts","../src/hooks/use-my-orders.ts"],"names":[],"mappings":"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","file":"chunk-WPWNYBNG.js","sourcesContent":["// ---------------------------------------------------------------------------\n// Os pedidos que ESTE navegador fez.\n//\n// Existe por causa de 0055. \"Minhas compras\" listava pedidos chamando\n// `shop_list_orders(customer_id)` — e essa função deixava um chamador anônimo\n// ler o histórico de qualquer cliente, com endereço de entrega, a partir de um\n// id que saía de sondar um e-mail. A trava foi corrigida, e com ela caiu a\n// única forma que um convidado tinha de rever a própria compra.\n//\n// A reposição não reabre o buraco: em vez de PERGUNTAR ao servidor \"o que é\n// deste cliente?\", o navegador lembra os ids que ele mesmo criou e busca um a\n// um por `shop_get_order(uuid)`. O uuid v4 é a credencial — o mesmo desenho que\n// já sustenta a página de confirmação — e ninguém consegue listar o que não\n// fez.\n//\n// Some quando o comprador limpa o navegador, e isso é honesto: quem quer\n// histórico que atravessa aparelhos precisa de conta de verdade.\n// ---------------------------------------------------------------------------\n\nconst KEY = 'fayz.storefront.orders.v1'\n/** O bastante para a página de conta; mais que isso é caso de criar conta. */\nconst MAX = 25\n\nfunction read(): string[] {\n try {\n const raw = globalThis.localStorage?.getItem(KEY)\n const parsed = raw ? JSON.parse(raw) : []\n return Array.isArray(parsed) ? parsed.filter((id) => typeof id === 'string') : []\n } catch {\n // Modo privado, storage cheio, JSON corrompido — nenhum deles é motivo para\n // derrubar a loja.\n return []\n }\n}\n\nexport function rememberLocalOrder(orderId: string): void {\n if (!orderId) return\n try {\n const next = [orderId, ...read().filter((id) => id !== orderId)].slice(0, MAX)\n globalThis.localStorage?.setItem(KEY, JSON.stringify(next))\n } catch {\n /* ver acima */\n }\n}\n\n/** Mais recente primeiro, que é a ordem em que foram lembrados. */\nexport function listLocalOrderIds(): string[] {\n return read()\n}\n\nexport function forgetLocalOrders(): void {\n try {\n globalThis.localStorage?.removeItem(KEY)\n } catch {\n /* ver acima */\n }\n}\n","import { useCallback, useEffect, useState } from 'react'\nimport { getShopProvider } from '@fayz-ai/shop/runtime'\nimport type { Order } from '@fayz-ai/shop/types'\nimport { useSessionStore } from '../stores/session.store'\nimport { listLocalOrderIds } from '../data/local-orders'\n\nexport function useMyOrders(): { orders: Order[]; loading: boolean; refresh: () => void } {\n const customerId = useSessionStore((s) => s.customerId)\n const email = useSessionStore((s) => s.email)\n const [orders, setOrders] = useState<Order[]>([])\n const [loading, setLoading] = useState(true)\n const [tick, setTick] = useState(0)\n\n const refresh = useCallback(() => setTick((t) => t + 1), [])\n\n useEffect(() => {\n let cancelled = false\n // Um convidado não tem sessão de verdade, então o servidor recusa listar\n // por cliente (0055) — e recusa CERTO. O que ele consegue ver é o que este\n // navegador criou.\n const local = listLocalOrderIds()\n if (!customerId && !email) {\n if (local.length === 0) {\n setOrders([])\n setLoading(false)\n return\n }\n }\n setLoading(true)\n // Scope by customerId (RLS-enforced to the owner on Supabase) when available;\n // fall back to email only for legacy sessions without a linked customer.\n const query = customerId ? { customerId, limit: 50 } : { customerEmail: email!, limit: 50 }\n getShopProvider()\n .listOrders(query)\n .then((data) => {\n if (!cancelled) setOrders(data)\n })\n .catch(async () => {\n // 42501 aqui é a trava de 0055 fazendo o trabalho dela: quem não entrou\n // não lista o histórico de ninguém. Cai para os pedidos deste\n // navegador, buscados um a um pelo uuid — que é a credencial.\n const mine = await Promise.all(\n listLocalOrderIds().map((id) => getShopProvider().getOrder(id).catch(() => null)),\n )\n if (!cancelled) setOrders(mine.filter((o): o is Order => !!o))\n })\n .finally(() => {\n if (!cancelled) setLoading(false)\n })\n return () => {\n cancelled = true\n }\n }, [customerId, email, tick])\n\n return { orders, loading, refresh }\n}\n"]}
@@ -0,0 +1,99 @@
1
+ import { useRef, useState, useEffect } from 'react';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
4
+ // src/lib/motion.tsx
5
+ function prefersReducedMotion() {
6
+ if (typeof window === "undefined" || !window.matchMedia) return false;
7
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
8
+ }
9
+ function useInView(margin = "0px 0px -8% 0px") {
10
+ const ref = useRef(null);
11
+ const [inView, setInView] = useState(() => prefersReducedMotion());
12
+ useEffect(() => {
13
+ if (inView) return;
14
+ const el = ref.current;
15
+ if (!el || typeof IntersectionObserver === "undefined") {
16
+ setInView(true);
17
+ return;
18
+ }
19
+ const observer = new IntersectionObserver(
20
+ (entries) => {
21
+ if (entries.some((e) => e.isIntersecting)) {
22
+ setInView(true);
23
+ observer.disconnect();
24
+ }
25
+ },
26
+ { rootMargin: margin }
27
+ );
28
+ observer.observe(el);
29
+ return () => observer.disconnect();
30
+ }, [inView, margin]);
31
+ return [ref, inView];
32
+ }
33
+ function Reveal({ delay = 0, children, className, style, ...rest }) {
34
+ const ref = useRef(null);
35
+ const [phase, setPhase] = useState("seen");
36
+ useEffect(() => {
37
+ const el = ref.current;
38
+ if (!el || prefersReducedMotion() || typeof IntersectionObserver === "undefined") return;
39
+ if (el.getBoundingClientRect().top < window.innerHeight) return;
40
+ setPhase("waiting");
41
+ const observer = new IntersectionObserver(
42
+ (entries) => {
43
+ if (entries.some((e) => e.isIntersecting)) {
44
+ setPhase("playing");
45
+ observer.disconnect();
46
+ }
47
+ },
48
+ { rootMargin: "0px 0px -8% 0px" }
49
+ );
50
+ observer.observe(el);
51
+ return () => observer.disconnect();
52
+ }, []);
53
+ return /* @__PURE__ */ jsx(
54
+ "div",
55
+ {
56
+ ref,
57
+ className: `${phase === "playing" ? "animate-fade-up" : ""} ${className ?? ""}`,
58
+ style: {
59
+ ...style,
60
+ // Off screen only. From there the class carries the fade.
61
+ ...phase === "waiting" ? { opacity: 0 } : null,
62
+ animationDelay: delay ? `${delay}ms` : void 0
63
+ },
64
+ ...rest,
65
+ children
66
+ }
67
+ );
68
+ }
69
+ function useScrolled(threshold = 8) {
70
+ const [scrolled, setScrolled] = useState(false);
71
+ useEffect(() => {
72
+ const onScroll = () => setScrolled(window.scrollY > threshold);
73
+ onScroll();
74
+ window.addEventListener("scroll", onScroll, { passive: true });
75
+ return () => window.removeEventListener("scroll", onScroll);
76
+ }, [threshold]);
77
+ return scrolled;
78
+ }
79
+ function usePopOnChange(value) {
80
+ const [pop, setPop] = useState(false);
81
+ const prev = useRef(value);
82
+ useEffect(() => {
83
+ if (prev.current !== value && value > 0) {
84
+ setPop(true);
85
+ const t = setTimeout(() => setPop(false), 400);
86
+ return () => clearTimeout(t);
87
+ }
88
+ prev.current = value;
89
+ return void 0;
90
+ }, [value]);
91
+ useEffect(() => {
92
+ prev.current = value;
93
+ });
94
+ return pop;
95
+ }
96
+
97
+ export { Reveal, prefersReducedMotion, useInView, usePopOnChange, useScrolled };
98
+ //# sourceMappingURL=chunk-WXXYJZE6.js.map
99
+ //# sourceMappingURL=chunk-WXXYJZE6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/motion.tsx"],"names":[],"mappings":"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","file":"chunk-WXXYJZE6.js","sourcesContent":["import React, { useEffect, useRef, useState } from 'react'\n\n// ---------------------------------------------------------------------------\n// Core motion primitives — every storefront inherits these; stores never\n// implement animation ad hoc. All effects respect prefers-reduced-motion.\n// ---------------------------------------------------------------------------\n\nexport function prefersReducedMotion(): boolean {\n if (typeof window === 'undefined' || !window.matchMedia) return false\n return window.matchMedia('(prefers-reduced-motion: reduce)').matches\n}\n\n/** True once the element has entered the viewport (one-shot). */\nexport function useInView<T extends HTMLElement>(margin = '0px 0px -8% 0px'): [React.RefObject<T>, boolean] {\n // `null!` keeps this compiling under BOTH React typings: 18 rejects passing a\n // RefObject<T | null> as a JSX ref, 19 rejects the reverse assignment.\n const ref = useRef<T>(null!)\n const [inView, setInView] = useState(() => prefersReducedMotion())\n\n useEffect(() => {\n if (inView) return\n const el = ref.current\n if (!el || typeof IntersectionObserver === 'undefined') {\n setInView(true)\n return\n }\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries.some((e) => e.isIntersecting)) {\n setInView(true)\n observer.disconnect()\n }\n },\n { rootMargin: margin },\n )\n observer.observe(el)\n return () => observer.disconnect()\n }, [inView, margin])\n\n return [ref, inView]\n}\n\nexport interface RevealProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Stagger delay in ms */\n delay?: number\n children: React.ReactNode\n}\n\n/**\n * A fade-up entrance for content the shopper has NOT seen yet.\n *\n * Content is visible by default — never pre-hidden in the markup — so\n * crawlers, screenshots and a browser without IntersectionObserver always see\n * it. The catch was that the animation's own from-state is `opacity: 0`: a\n * block the server had painted, that the shopper was already reading, blinked\n * out and faded back in the moment it crossed the observer's line. Measured on\n * the Artorius home page — six sections, one per scroll step.\n *\n * So the decision is made once, at mount, against the viewport as it is then:\n *\n * - already on screen (or scrolled past) → nothing. It has been seen, and an\n * entrance now would be a re-entrance.\n * - below the fold → held at `opacity: 0` while it is still off screen, where\n * holding it costs the shopper nothing, and played when it arrives.\n *\n * `prefersReducedMotion` skips the whole thing, as does a browser with no\n * observer: both leave the server's markup exactly as it came.\n */\nexport function Reveal({ delay = 0, children, className, style, ...rest }: RevealProps) {\n const ref = useRef<HTMLDivElement>(null!)\n const [phase, setPhase] = useState<'seen' | 'waiting' | 'playing'>('seen')\n\n useEffect(() => {\n const el = ref.current\n if (!el || prefersReducedMotion() || typeof IntersectionObserver === 'undefined') return\n // Strictly below the fold, not merely \"not intersecting\": a block with its\n // top edge peeking in has been seen too.\n if (el.getBoundingClientRect().top < window.innerHeight) return\n setPhase('waiting')\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries.some((e) => e.isIntersecting)) {\n setPhase('playing')\n observer.disconnect()\n }\n },\n { rootMargin: '0px 0px -8% 0px' },\n )\n observer.observe(el)\n return () => observer.disconnect()\n }, [])\n\n return (\n <div\n ref={ref}\n className={`${phase === 'playing' ? 'animate-fade-up' : ''} ${className ?? ''}`}\n style={{\n ...style,\n // Off screen only. From there the class carries the fade.\n ...(phase === 'waiting' ? { opacity: 0 } : null),\n animationDelay: delay ? `${delay}ms` : undefined,\n }}\n {...rest}\n >\n {children}\n </div>\n )\n}\n\n/** True after the window has scrolled past the threshold (header treatments). */\nexport function useScrolled(threshold = 8): boolean {\n const [scrolled, setScrolled] = useState(false)\n useEffect(() => {\n const onScroll = () => setScrolled(window.scrollY > threshold)\n onScroll()\n window.addEventListener('scroll', onScroll, { passive: true })\n return () => window.removeEventListener('scroll', onScroll)\n }, [threshold])\n return scrolled\n}\n\n/** Re-triggers a CSS animation class whenever `value` changes (cart badge pop). */\nexport function usePopOnChange(value: number): boolean {\n const [pop, setPop] = useState(false)\n const prev = useRef(value)\n useEffect(() => {\n if (prev.current !== value && value > 0) {\n setPop(true)\n const t = setTimeout(() => setPop(false), 400)\n return () => clearTimeout(t)\n }\n prev.current = value\n return undefined\n }, [value])\n useEffect(() => {\n prev.current = value\n })\n return pop\n}\n"]}
package/dist/host.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { buildRobotsTxt, buildSitemapXml } from './chunk-WCAQRATH.js';
2
2
  export { buildRobotsTxt, buildSitemapXml } from './chunk-WCAQRATH.js';
3
- import { resolveConfig, storefrontPaths, isPublicPath, matchPath, collectDataNeeds, menuDataNeeds, resolvePageDataNeed, isKnownPath, productSeo, menuSeo, pageSeo, toBlockNodes, defaultPageBlocks } from './chunk-7N5HKQBM.js';
4
- export { menuDataNeeds } from './chunk-7N5HKQBM.js';
3
+ import { storefrontPaths, isPublicPath, menuDataNeeds, isKnownPath, productSeo, menuSeo, pageSeo } from './chunk-45R7CIEA.js';
4
+ export { menuDataNeeds } from './chunk-45R7CIEA.js';
5
+ import { collectDataNeeds, resolvePageDataNeed, toBlockNodes, defaultPageBlocks } from './chunk-BKO2AQ3Q.js';
6
+ import { resolveConfig, matchPath } from './chunk-GVZ2SAOI.js';
5
7
  import { validateExperiments } from './chunk-4REZOGGW.js';
6
8
  export { EXPERIMENT_HEADER, decodeExperimentHeader } from './chunk-4REZOGGW.js';
7
9
  import { buildSeoTags } from '@fayz-ai/core';
package/dist/host.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/host/store-host.ts","../src/host/image-origin.ts"],"names":["getShopProvider"],"mappings":"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","file":"host.js","sourcesContent":["import { buildSeoTags, type BlockNode, type SeoConfig, type SeoLinkTag } from '@fayz-ai/core'\nimport { getShopProvider } from '@fayz-ai/shop/runtime'\nimport type { Category, Product, ServiceHours } from '@fayz-ai/shop/types'\nimport { resolveConfig, type ResolvedStorefrontConfig, type StorefrontConfig } from '../document/config'\nimport { isKnownPath, isPublicPath, storefrontPaths } from '../app/serving'\nimport { matchPath } from '../app/router'\nimport { collectDataNeeds, resolvePageDataNeed, type PageDataEntry } from '../blocks/page-data'\nimport { defaultPageBlocks, toBlockNodes } from '../blocks/page-blocks'\nimport { menuDataNeeds } from '../data/menu-needs'\nimport { menuSeo, pageSeo, productSeo, type MenuBusiness } from '../lib/seo'\nimport { buildRobotsTxt, buildSitemapXml } from '../app/sitemap'\nimport { validateExperiments, type ExperimentAssignments } from '../experiments/assign'\n\n// ---------------------------------------------------------------------------\n// A store served from a server, described once.\n//\n// Everything a server-rendered store has to decide per request that is NOT a\n// framework concern: which page a path is, what it loads before render, how it\n// is titled and described to a crawler, whether a shared cache may hold it,\n// where an old URL now lives, and what goes in sitemap.xml and robots.txt.\n//\n// It was written first as one store's host file (artorious-shop), and every\n// new store was going to copy it. Here it takes the store's config and returns\n// those answers; a framework adapter (`@fayz-ai/storefront/tanstack`) and the\n// store's route files call them. Framework-free: no React, nothing that only\n// runs in a browser or only on a server.\n// ---------------------------------------------------------------------------\n\n/** One read the page declares, keyed as `useLoader` asks for it. */\nexport interface HostDataNeed {\n key: string\n loader: string\n params: Record<string, unknown>\n}\n\n/** What the store's own routes add to a page, beyond the SDK's. */\nexport interface StoreHostExtension<Extra = unknown> {\n /** Reads an app-owned page makes (recipes, landing pages), seeded before render. */\n needs?(path: string): HostDataNeed[]\n /** The record an app-owned page is about, for its head and its 404. `null` = none. */\n load?(path: string): Promise<Extra | null>\n /** True when an app-owned URL has nothing behind it (answers 404). */\n missing?(path: string, extra: Extra | null): boolean\n /** The head for an app-owned page. Returning null leaves the SDK's. */\n head?(data: StoreRouteData<Extra>, ctx: StoreHeadContext): SeoConfig | null\n /** More JSON-LD for any page — added after the SDK's. */\n jsonLd?(data: StoreRouteData<Extra>, ctx: StoreHeadContext): Array<Record<string, unknown>>\n /** Parameterised paths the SDK cannot enumerate (a blog's posts). */\n sitemapPaths?(): Promise<string[]>\n}\n\nexport interface StoreHostOptions<Extra = unknown> {\n config: StorefrontConfig | ResolvedStorefrontConfig\n /**\n * The one address the store is indexed under ('https://loja.com.br'). A\n * request to any other public host — the workers.dev deployment URL, a\n * preview — is told not to be indexed, so one store does not compete with\n * itself under two hostnames. Unset: every host counts as canonical.\n */\n canonicalOrigin?: string\n /**\n * Title and description for the routes the SDK has no copy for, by path.\n * Write each for the query it should answer, not for the brand: a home page,\n * the delivery menu and three legal pages sharing one <title> is a\n * duplicate-title report and a coin flip over which one is shown.\n */\n pages?: Record<string, { title: string; description?: string }>\n /** The store's one-liner: the home page's description and the fallback. */\n description?: string\n /**\n * The house as the menu's Restaurant entity states it. Merged OVER what the\n * store's own data says (contact, social links, delivery locality, opening\n * hours read live from the back office) — set only what that data lacks.\n */\n business?: Partial<MenuBusiness>\n /**\n * Paths that answer 301 elsewhere. In delivery mode `/catalog` and `/menu`\n * redirect to the menu by default, so old links and results land where\n * people order; pass `{}` to turn that off.\n */\n redirects?: Record<string, string>\n /** The store's own routes. */\n extend?: StoreHostExtension<Extra>\n}\n\n/** What a route loader returns, serialised to the browser with the page. */\nexport interface StoreRouteData<Extra = unknown> {\n path: string\n /** Seeded page data, keyed by `dataNeedKey`. */\n entries: Record<string, PageDataEntry>\n /** The product a product URL is about, for its head and its 404. */\n product: Product | null\n /** The app-owned record (see `StoreHostExtension.load`). */\n extra: Extra | null\n /** Opening hours read for the Restaurant entity; empty when the store declares none. */\n hours: ServiceHours[]\n /** Answer 404. */\n missing: boolean\n /** Public origin of the request, for canonical and OpenGraph URLs. */\n origin: string\n /**\n * The variants the edge assigned this request (`createStorefrontEdge`'s\n * `experiments`), read from the `x-fayz-experiments` header by the route.\n * Variants only — never the visitor id: this is serialised into HTML the\n * edge caches per variant bucket. Absent: every experiment is control.\n */\n experiments?: ExperimentAssignments\n}\n\nexport interface StoreHeadContext {\n config: ResolvedStorefrontConfig\n path: string\n origin: string\n}\n\n/** The head as a framework wants it: meta, links, JSON-LD scripts. */\nexport interface StoreHead {\n /** `title` and `charSet` entries alongside name/property tags, as TanStack's head API takes them. */\n meta: Array<{ name?: string; property?: string; content?: string; title?: string; charSet?: 'utf-8' }>\n links: SeoLinkTag[]\n scripts: Array<{ type: string; children: string }>\n}\n\nexport interface StoreHost<Extra = unknown> {\n config: ResolvedStorefrontConfig\n /** Where a path now lives, or null. */\n redirectFor(path: string): string | null\n loadRoute(path: string, origin: string, request?: { experiments?: ExperimentAssignments | null }): Promise<StoreRouteData<Extra>>\n headFor(data: StoreRouteData<Extra> | undefined): StoreHead\n cacheControlFor(path: string): string\n /** Public pages render on the server; per-shopper ones only in the browser. */\n renderedOnServer(path: string): boolean\n isCanonicalHost(url: URL): boolean\n sitemapXml(origin: string): Promise<string>\n robotsTxt(url: URL, origin: string): string\n}\n\n// s-maxage is the edge TTL. Five minutes on purpose: an edge cache entry cannot\n// be purged globally from a workers.dev URL, so this bounds how stale a price\n// can be until the store's own zone and a purge hook exist.\nexport const PUBLIC_CACHE = 'public, max-age=0, s-maxage=300, stale-while-revalidate=3600'\nexport const PRIVATE_CACHE = 'private, no-store, must-revalidate'\n\nconst DAY = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']\n\n/** Back-office opening hours as schema.org `openingHours` ('Mo 11:00-22:00'). */\nexport function openingHoursFrom(hours: readonly ServiceHours[]): string[] {\n return hours\n .filter((h) => h.weekday >= 0 && h.weekday <= 6)\n .map((h) => `${DAY[h.weekday]} ${h.opensAt.slice(0, 5)}-${h.closesAt.slice(0, 5)}`)\n}\n\n/** A site-relative path made absolute; absolute URLs pass. */\nfunction absoluteUrl(url: string | undefined, origin: string): string | undefined {\n if (!url) return undefined\n if (/^https?:\\/\\//.test(url)) return url\n if (!origin) return undefined\n return `${origin}${url.startsWith('/') ? url : `/${url}`}`\n}\n\n/**\n * The business facts the store's own data already holds, with the explicit\n * ones on top. Nothing is guessed: a fact neither source has stays out of the\n * JSON-LD.\n */\nexport function businessFrom(\n config: ResolvedStorefrontConfig,\n input: { hours: readonly ServiceHours[]; origin: string; description?: string; override?: Partial<MenuBusiness> },\n): MenuBusiness {\n const contact = config.footer?.contact\n const social = (config.footer?.social ?? []).map((s) => s.href).filter((href) => /^https?:\\/\\//.test(href))\n const locality = config.delivery?.locality\n const opening = openingHoursFrom(input.hours)\n const base: MenuBusiness = {\n ...(input.description ? { description: input.description } : {}),\n ...(contact?.phone ? { telephone: contact.phone } : {}),\n ...(contact?.email ? { email: contact.email } : {}),\n ...(contact?.address ? { address: { streetAddress: contact.address } } : {}),\n ...(locality ? { areaServed: [locality] } : {}),\n ...(opening.length ? { openingHours: opening } : {}),\n ...(social.length ? { sameAs: social } : {}),\n }\n const cover = absoluteUrl(config.delivery?.coverImage, input.origin)\n if (cover) base.image = cover\n const merged = { ...base, ...input.override }\n if (merged.image) merged.image = absoluteUrl(merged.image, input.origin)\n if (merged.logo) merged.logo = absoluteUrl(merged.logo, input.origin)\n return merged\n}\n\n/** The house on the home page: Organization (knowledge panel) + WebSite. */\nexport function homeJsonLd(\n config: ResolvedStorefrontConfig,\n origin: string,\n business: MenuBusiness,\n): Array<Record<string, unknown>> {\n if (!origin) return []\n return [\n {\n '@context': 'https://schema.org',\n '@type': 'Organization',\n name: config.name,\n url: origin,\n ...(business.logo ? { logo: business.logo } : {}),\n ...(business.description ? { description: business.description } : {}),\n ...(business.email ? { email: business.email } : {}),\n ...(business.telephone ? { telephone: business.telephone } : {}),\n ...(business.sameAs?.length ? { sameAs: business.sameAs } : {}),\n },\n {\n '@context': 'https://schema.org',\n '@type': 'WebSite',\n name: config.name,\n url: origin,\n inLanguage: config.locale ?? 'pt-BR',\n },\n ]\n}\n\nexport function createStoreHost<Extra = unknown>(options: StoreHostOptions<Extra>): StoreHost<Extra> {\n const config = 'features' in options.config && 'pages' in options.config\n ? (options.config as ResolvedStorefrontConfig)\n : resolveConfig(options.config as StorefrontConfig)\n const menuPath = config.delivery?.menuPath\n const redirects: Record<string, string> =\n options.redirects ??\n (menuPath\n ? Object.fromEntries(['/catalog', '/menu'].filter((p) => p !== menuPath).map((p) => [p, menuPath]))\n : {})\n const extend = options.extend ?? {}\n // A typo in an experiment fails the build, not the experiment: a page that\n // silently renders no variant would run for weeks measuring nothing.\n const experimentIssues = validateExperiments(config.experiments)\n if (experimentIssues.length) throw new Error(`Invalid storefront experiments:\\n- ${experimentIssues.join('\\n- ')}`)\n const canonicalHost = options.canonicalOrigin ? new URL(options.canonicalOrigin).hostname : null\n\n const isMenu = (path: string) => Boolean(menuPath && matchPath(menuPath, path))\n\n /** The block tree the SDK renders for `path` — the source of its data needs. */\n function treeFor(path: string): BlockNode[] {\n const pages = config.pages\n if (matchPath('/product/:slug', path)) return toBlockNodes(pages.product?.blocks ?? defaultPageBlocks('product')) as BlockNode[]\n if (matchPath('/catalog', path)) return toBlockNodes(pages.catalog?.blocks ?? defaultPageBlocks('catalog')) as BlockNode[]\n if (path === '/' || path === '') return toBlockNodes(pages.home?.blocks ?? defaultPageBlocks('home')) as BlockNode[]\n return []\n }\n\n async function productFor(path: string): Promise<Product | null> {\n const slug = matchPath('/product/:slug', path)?.slug\n if (!slug) return null\n const found = await getShopProvider().listProducts({ slug, status: 'active', limit: 1 })\n return found[0] ?? null\n }\n\n /** Only where the hours are said: the menu and the home page. */\n async function hoursFor(path: string): Promise<ServiceHours[]> {\n if (!config.delivery || !(isMenu(path) || path === '/')) return []\n try {\n return (await getShopProvider().listServiceHours?.('delivery')) ?? []\n } catch {\n return []\n }\n }\n\n async function loadRoute(\n path: string,\n origin: string,\n request?: { experiments?: ExperimentAssignments | null },\n ): Promise<StoreRouteData<Extra>> {\n const params = matchPath('/product/:slug', path) ?? {}\n const needs: HostDataNeed[] = [\n ...collectDataNeeds(treeFor(path), params),\n ...(isMenu(path) ? menuDataNeeds() : []),\n ...(extend.needs?.(path) ?? []),\n ]\n const entries: Record<string, PageDataEntry> = {}\n const [, product, extra, hours] = await Promise.all([\n // Failures are isolated per need: an unseeded need falls back to fetching\n // in the browser, which is the pre-SSR behaviour, not a new failure mode.\n Promise.all(needs.map(async (need) => { entries[need.key] = await resolvePageDataNeed(need) })),\n productFor(path),\n extend.load ? extend.load(path) : Promise.resolve(null),\n hoursFor(path),\n ])\n // Two ways a URL has nothing behind it: the route table does not know it,\n // or it is a well-formed product URL whose product is gone. Both answer 404\n // — a soft 404 gets indexed as real content.\n const missing =\n !isKnownPath(config, path) ||\n (Boolean(matchPath('/product/:slug', path)) && product === null) ||\n Boolean(extend.missing?.(path, extra))\n // Variants only (see StoreRouteData.experiments).\n const flags = request?.experiments?.flags\n return { path, entries, product, extra, hours, missing, origin, ...(flags && Object.keys(flags).length ? { experiments: { flags } } : {}) }\n }\n\n function menuFrom(data: StoreRouteData<Extra>): { products: Product[]; categories: Category[] } {\n const read = <T,>(loader: string): T[] => {\n const need = menuDataNeeds().find((n) => n.loader === loader)\n const entry = need ? data.entries[need.key] : undefined\n return entry?.status === 'ready' && Array.isArray(entry.data) ? (entry.data as T[]) : []\n }\n return { products: read<Product>('products.list'), categories: read<Category>('categories.list') }\n }\n\n function headFor(data: StoreRouteData<Extra> | undefined): StoreHead {\n const path = data?.path ?? '/'\n const origin = (data?.origin ?? '').replace(/\\/$/, '')\n const ctx = { config, path, origin }\n const page = options.pages?.[path.split('?')[0] || '/']\n const business = businessFrom(config, {\n hours: data?.hours ?? [],\n origin,\n description: options.description,\n override: options.business,\n })\n\n const own = data ? extend.head?.(data, ctx) : null\n const base: SeoConfig =\n own ??\n (data?.product\n ? (productSeo(data.product, ctx) as SeoConfig)\n : data && isMenu(path)\n ? (menuSeo({ ...menuFrom(data), ...page, business }, ctx) as SeoConfig)\n : (pageSeo(ctx, page ?? { title: config.name, description: options.description }) as SeoConfig))\n\n const jsonLd = [\n ...(base.jsonLd ?? []),\n ...(path === '/' ? homeJsonLd(config, origin, business) : []),\n ...(data ? (extend.jsonLd?.(data, ctx) ?? []) : []),\n ]\n // A relative og:image renders as a broken share card; the store's cover is\n // the fallback for a page that has no photo of its own.\n const image = absoluteUrl(base.image ?? business.image, origin)\n const tags = buildSeoTags({ ...base, jsonLd, ...(image ? { image } : {}) })\n return {\n meta: [\n { charSet: 'utf-8' as const },\n { name: 'viewport', content: 'width=device-width, initial-scale=1' },\n { title: tags.title },\n ...tags.meta,\n // Checkout, account and order pages are one shopper's state at a URL —\n // never indexed. Same line the cache header is drawn on.\n ...(isPublicPath(config, path) ? [] : [{ name: 'robots', content: 'noindex,nofollow' }]),\n ],\n links: tags.links,\n scripts: tags.jsonLd.map((obj) => ({\n type: 'application/ld+json',\n children: JSON.stringify(obj).replace(/</g, '\\\\u003c'),\n })),\n }\n }\n\n return {\n config,\n redirectFor: (path) => redirects[path.split('?')[0] || '/'] ?? null,\n loadRoute,\n headFor,\n cacheControlFor: (path) => (isPublicPath(config, path) ? PUBLIC_CACHE : PRIVATE_CACHE),\n renderedOnServer: (path) => isPublicPath(config, path),\n isCanonicalHost(url) {\n if (!canonicalHost) return true\n const host = url.hostname\n // Local and preview hosts are never reachable by a crawler, and treating\n // them as foreign would put noindex in front of every e2e run.\n return host === canonicalHost || host === 'localhost' || host === '127.0.0.1' || host.endsWith('.local')\n },\n async sitemapXml(origin) {\n const [paths, extra] = await Promise.all([\n storefrontPaths({ config, provider: getShopProvider() }),\n extend.sitemapPaths?.() ?? Promise.resolve([]),\n ])\n // A URL that answers 301 does not belong in a sitemap: Search Console\n // reports it as an error, and the page it points to is listed already.\n const all = [...paths, ...extra].filter((p, i, list) => !redirects[p] && list.indexOf(p) === i)\n return buildSitemapXml(origin, all)\n },\n robotsTxt(url, origin) {\n if (this.isCanonicalHost(url)) return buildRobotsTxt(origin)\n return `# Not the store's address. The store is at ${options.canonicalOrigin}.\\nUser-agent: *\\nDisallow: /\\n`\n },\n }\n}\n","import { getShopProvider, setShopProvider } from '@fayz-ai/shop/runtime'\n\n// ---------------------------------------------------------------------------\n// Photos addressed at the store, not at the storage project.\n//\n// Catalogue rows hold absolute Supabase URLs, and the SDK builds every image\n// variant from whatever URL it is given — its matcher reads the PATH, not the\n// host. So serving photos from the store's own origin (see ./edge-handler) is\n// a matter of swapping the host on the way out of the provider.\n//\n// The origin is read per call, never captured: on the server it is the request\n// being answered, in the browser the page's own origin, and those two agree —\n// which keeps hydration quiet. With no origin, the row's URL is left alone and\n// photos come from Supabase: degraded, never broken.\n// ---------------------------------------------------------------------------\n\nconst PUBLIC_OBJECT = /^https?:\\/\\/[^/]+(\\/storage\\/v1\\/(?:object|render\\/image)\\/public\\/shop-images\\/[^\"'\\s]*)$/\n\n/** Swap the host of a storage URL for `origin`; anything else passes through. */\nexport function onOrigin(value: string, origin: string): string {\n const match = PUBLIC_OBJECT.exec(value)\n return match ? `${origin}${match[1]}` : value\n}\n\nfunction rewrite<T>(value: T, origin: string, depth = 0): T {\n if (depth > 6) return value\n if (typeof value === 'string') return onOrigin(value, origin) as T\n if (Array.isArray(value)) return value.map((v) => rewrite(v, origin, depth + 1)) as T\n if (value && typeof value === 'object' && (value as object).constructor === Object) {\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) out[k] = rewrite(v, origin, depth + 1)\n return out as T\n }\n return value\n}\n\n/**\n * Wrap any data provider so every storage URL it returns addresses `originOf()`.\n * A proxy rather than a list of methods: providers grow, and a photo can come\n * back from a product, a cart line, an order or a blog post.\n */\nexport function withImagesOnOrigin<T extends object>(provider: T, originOf: () => string): T {\n return new Proxy(provider, {\n get(target, prop) {\n // `target` as the receiver: a provider is a class instance, and a getter\n // handed the proxy as `this` throws on its private fields.\n const value = Reflect.get(target, prop, target)\n if (typeof value !== 'function') return value\n return (...args: unknown[]) => {\n const out = (value as (...a: unknown[]) => unknown).apply(target, args)\n const origin = originOf()\n if (!origin) return out\n return out instanceof Promise ? out.then((v) => rewrite(v, origin)) : rewrite(out, origin)\n }\n },\n }) as T\n}\n\n/**\n * Point every catalogue photo at the store's own origin. Call once, after\n * `initStorefrontRuntime` — it is the provider that call installs that gets\n * wrapped. Other providers serving the same bucket (a blog's) wrap themselves\n * with `withImagesOnOrigin`.\n */\nexport function serveShopImagesFromOrigin(originOf: () => string): void {\n setShopProvider(withImagesOnOrigin(getShopProvider(), originOf))\n}\n"]}
1
+ {"version":3,"sources":["../src/host/store-host.ts","../src/host/image-origin.ts"],"names":["getShopProvider"],"mappings":"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","file":"host.js","sourcesContent":["import { buildSeoTags, type BlockNode, type SeoConfig, type SeoLinkTag } from '@fayz-ai/core'\nimport { getShopProvider } from '@fayz-ai/shop/runtime'\nimport type { Category, Product, ServiceHours } from '@fayz-ai/shop/types'\nimport { resolveConfig, type ResolvedStorefrontConfig, type StorefrontConfig } from '../document/config'\nimport { isKnownPath, isPublicPath, storefrontPaths } from '../app/serving'\nimport { matchPath } from '../app/router'\nimport { collectDataNeeds, resolvePageDataNeed, type PageDataEntry } from '../blocks/page-data'\nimport { defaultPageBlocks, toBlockNodes } from '../blocks/page-blocks'\nimport { menuDataNeeds } from '../data/menu-needs'\nimport { menuSeo, pageSeo, productSeo, type MenuBusiness } from '../lib/seo'\nimport { buildRobotsTxt, buildSitemapXml } from '../app/sitemap'\nimport { validateExperiments, type ExperimentAssignments } from '../experiments/assign'\n\n// ---------------------------------------------------------------------------\n// A store served from a server, described once.\n//\n// Everything a server-rendered store has to decide per request that is NOT a\n// framework concern: which page a path is, what it loads before render, how it\n// is titled and described to a crawler, whether a shared cache may hold it,\n// where an old URL now lives, and what goes in sitemap.xml and robots.txt.\n//\n// It was written first as one store's host file (artorious-shop), and every\n// new store was going to copy it. Here it takes the store's config and returns\n// those answers; a framework adapter (`@fayz-ai/storefront/tanstack`) and the\n// store's route files call them. Framework-free: no React, nothing that only\n// runs in a browser or only on a server.\n// ---------------------------------------------------------------------------\n\n/** One read the page declares, keyed as `useLoader` asks for it. */\nexport interface HostDataNeed {\n key: string\n loader: string\n params: Record<string, unknown>\n}\n\n/** What the store's own routes add to a page, beyond the SDK's. */\nexport interface StoreHostExtension<Extra = unknown> {\n /** Reads an app-owned page makes (recipes, landing pages), seeded before render. */\n needs?(path: string): HostDataNeed[]\n /** The record an app-owned page is about, for its head and its 404. `null` = none. */\n load?(path: string): Promise<Extra | null>\n /** True when an app-owned URL has nothing behind it (answers 404). */\n missing?(path: string, extra: Extra | null): boolean\n /** The head for an app-owned page. Returning null leaves the SDK's. */\n head?(data: StoreRouteData<Extra>, ctx: StoreHeadContext): SeoConfig | null\n /** More JSON-LD for any page — added after the SDK's. */\n jsonLd?(data: StoreRouteData<Extra>, ctx: StoreHeadContext): Array<Record<string, unknown>>\n /** Parameterised paths the SDK cannot enumerate (a blog's posts). */\n sitemapPaths?(): Promise<string[]>\n}\n\nexport interface StoreHostOptions<Extra = unknown> {\n config: StorefrontConfig | ResolvedStorefrontConfig\n /**\n * The one address the store is indexed under ('https://loja.com.br'). A\n * request to any other public host — the workers.dev deployment URL, a\n * preview — is told not to be indexed, so one store does not compete with\n * itself under two hostnames. Unset: every host counts as canonical.\n */\n canonicalOrigin?: string\n /**\n * Title and description for the routes the SDK has no copy for, by path.\n * Write each for the query it should answer, not for the brand: a home page,\n * the delivery menu and three legal pages sharing one <title> is a\n * duplicate-title report and a coin flip over which one is shown.\n */\n pages?: Record<string, { title: string; description?: string }>\n /** The store's one-liner: the home page's description and the fallback. */\n description?: string\n /**\n * The house as the menu's Restaurant entity states it. Merged OVER what the\n * store's own data says (contact, social links, delivery locality, opening\n * hours read live from the back office) — set only what that data lacks.\n */\n business?: Partial<MenuBusiness>\n /**\n * Paths that answer 301 elsewhere. In delivery mode `/catalog` and `/menu`\n * redirect to the menu by default, so old links and results land where\n * people order; pass `{}` to turn that off.\n */\n redirects?: Record<string, string>\n /** The store's own routes. */\n extend?: StoreHostExtension<Extra>\n}\n\n/** What a route loader returns, serialised to the browser with the page. */\nexport interface StoreRouteData<Extra = unknown> {\n path: string\n /** Seeded page data, keyed by `dataNeedKey`. */\n entries: Record<string, PageDataEntry>\n /** The product a product URL is about, for its head and its 404. */\n product: Product | null\n /** The app-owned record (see `StoreHostExtension.load`). */\n extra: Extra | null\n /** Opening hours read for the Restaurant entity; empty when the store declares none. */\n hours: ServiceHours[]\n /** Answer 404. */\n missing: boolean\n /** Public origin of the request, for canonical and OpenGraph URLs. */\n origin: string\n /**\n * The variants the edge assigned this request (`createStorefrontEdge`'s\n * `experiments`), read from the `x-fayz-experiments` header by the route.\n * Variants only — never the visitor id: this is serialised into HTML the\n * edge caches per variant bucket. Absent: every experiment is control.\n */\n experiments?: ExperimentAssignments\n}\n\nexport interface StoreHeadContext {\n config: ResolvedStorefrontConfig\n path: string\n origin: string\n}\n\n/** The head as a framework wants it: meta, links, JSON-LD scripts. */\nexport interface StoreHead {\n /** `title` and `charSet` entries alongside name/property tags, as TanStack's head API takes them. */\n meta: Array<{ name?: string; property?: string; content?: string; title?: string; charSet?: 'utf-8' }>\n links: SeoLinkTag[]\n scripts: Array<{ type: string; children: string }>\n}\n\nexport interface StoreHost<Extra = unknown> {\n config: ResolvedStorefrontConfig\n /** Where a path now lives, or null. */\n redirectFor(path: string): string | null\n loadRoute(path: string, origin: string, request?: { experiments?: ExperimentAssignments | null }): Promise<StoreRouteData<Extra>>\n headFor(data: StoreRouteData<Extra> | undefined): StoreHead\n cacheControlFor(path: string): string\n /** Public pages render on the server; per-shopper ones only in the browser. */\n renderedOnServer(path: string): boolean\n isCanonicalHost(url: URL): boolean\n sitemapXml(origin: string): Promise<string>\n robotsTxt(url: URL, origin: string): string\n}\n\n// s-maxage is the edge TTL. Five minutes on purpose: an edge cache entry cannot\n// be purged globally from a workers.dev URL, so this bounds how stale a price\n// can be until the store's own zone and a purge hook exist.\nexport const PUBLIC_CACHE = 'public, max-age=0, s-maxage=300, stale-while-revalidate=3600'\nexport const PRIVATE_CACHE = 'private, no-store, must-revalidate'\n\nconst DAY = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']\n\n/** Back-office opening hours as schema.org `openingHours` ('Mo 11:00-22:00'). */\nexport function openingHoursFrom(hours: readonly ServiceHours[]): string[] {\n return hours\n .filter((h) => h.weekday >= 0 && h.weekday <= 6)\n .map((h) => `${DAY[h.weekday]} ${h.opensAt.slice(0, 5)}-${h.closesAt.slice(0, 5)}`)\n}\n\n/** A site-relative path made absolute; absolute URLs pass. */\nfunction absoluteUrl(url: string | undefined, origin: string): string | undefined {\n if (!url) return undefined\n if (/^https?:\\/\\//.test(url)) return url\n if (!origin) return undefined\n return `${origin}${url.startsWith('/') ? url : `/${url}`}`\n}\n\n/**\n * The business facts the store's own data already holds, with the explicit\n * ones on top. Nothing is guessed: a fact neither source has stays out of the\n * JSON-LD.\n */\nexport function businessFrom(\n config: ResolvedStorefrontConfig,\n input: { hours: readonly ServiceHours[]; origin: string; description?: string; override?: Partial<MenuBusiness> },\n): MenuBusiness {\n const contact = config.footer?.contact\n const social = (config.footer?.social ?? []).map((s) => s.href).filter((href) => /^https?:\\/\\//.test(href))\n const locality = config.delivery?.locality\n const opening = openingHoursFrom(input.hours)\n const base: MenuBusiness = {\n ...(input.description ? { description: input.description } : {}),\n ...(contact?.phone ? { telephone: contact.phone } : {}),\n ...(contact?.email ? { email: contact.email } : {}),\n ...(contact?.address ? { address: { streetAddress: contact.address } } : {}),\n ...(locality ? { areaServed: [locality] } : {}),\n ...(opening.length ? { openingHours: opening } : {}),\n ...(social.length ? { sameAs: social } : {}),\n }\n const cover = absoluteUrl(config.delivery?.coverImage, input.origin)\n if (cover) base.image = cover\n const merged = { ...base, ...input.override }\n if (merged.image) merged.image = absoluteUrl(merged.image, input.origin)\n if (merged.logo) merged.logo = absoluteUrl(merged.logo, input.origin)\n return merged\n}\n\n/** The house on the home page: Organization (knowledge panel) + WebSite. */\nexport function homeJsonLd(\n config: ResolvedStorefrontConfig,\n origin: string,\n business: MenuBusiness,\n): Array<Record<string, unknown>> {\n if (!origin) return []\n return [\n {\n '@context': 'https://schema.org',\n '@type': 'Organization',\n name: config.name,\n url: origin,\n ...(business.logo ? { logo: business.logo } : {}),\n ...(business.description ? { description: business.description } : {}),\n ...(business.email ? { email: business.email } : {}),\n ...(business.telephone ? { telephone: business.telephone } : {}),\n ...(business.sameAs?.length ? { sameAs: business.sameAs } : {}),\n },\n {\n '@context': 'https://schema.org',\n '@type': 'WebSite',\n name: config.name,\n url: origin,\n inLanguage: config.locale ?? 'pt-BR',\n },\n ]\n}\n\nexport function createStoreHost<Extra = unknown>(options: StoreHostOptions<Extra>): StoreHost<Extra> {\n const config = 'features' in options.config && 'pages' in options.config\n ? (options.config as ResolvedStorefrontConfig)\n : resolveConfig(options.config as StorefrontConfig)\n const menuPath = config.delivery?.menuPath\n const redirects: Record<string, string> =\n options.redirects ??\n (menuPath\n ? Object.fromEntries(['/catalog', '/menu'].filter((p) => p !== menuPath).map((p) => [p, menuPath]))\n : {})\n const extend = options.extend ?? {}\n // A typo in an experiment fails the build, not the experiment: a page that\n // silently renders no variant would run for weeks measuring nothing.\n const experimentIssues = validateExperiments(config.experiments)\n if (experimentIssues.length) throw new Error(`Invalid storefront experiments:\\n- ${experimentIssues.join('\\n- ')}`)\n const canonicalHost = options.canonicalOrigin ? new URL(options.canonicalOrigin).hostname : null\n\n const isMenu = (path: string) => Boolean(menuPath && matchPath(menuPath, path))\n\n /** The block tree the SDK renders for `path` — the source of its data needs. */\n function treeFor(path: string): BlockNode[] {\n const pages = config.pages\n if (matchPath('/product/:slug', path)) return toBlockNodes(pages.product?.blocks ?? defaultPageBlocks('product')) as BlockNode[]\n if (matchPath('/catalog', path)) return toBlockNodes(pages.catalog?.blocks ?? defaultPageBlocks('catalog')) as BlockNode[]\n if (path === '/' || path === '') return toBlockNodes(pages.home?.blocks ?? defaultPageBlocks('home')) as BlockNode[]\n return []\n }\n\n async function productFor(path: string): Promise<Product | null> {\n const slug = matchPath('/product/:slug', path)?.slug\n if (!slug) return null\n const found = await getShopProvider().listProducts({ slug, status: 'active', limit: 1 })\n return found[0] ?? null\n }\n\n /** Only where the hours are said: the menu and the home page. */\n async function hoursFor(path: string): Promise<ServiceHours[]> {\n if (!config.delivery || !(isMenu(path) || path === '/')) return []\n try {\n return (await getShopProvider().listServiceHours?.('delivery')) ?? []\n } catch {\n return []\n }\n }\n\n async function loadRoute(\n path: string,\n origin: string,\n request?: { experiments?: ExperimentAssignments | null },\n ): Promise<StoreRouteData<Extra>> {\n const params = matchPath('/product/:slug', path) ?? {}\n const needs: HostDataNeed[] = [\n ...collectDataNeeds(treeFor(path), params),\n ...(isMenu(path) ? menuDataNeeds() : []),\n ...(extend.needs?.(path) ?? []),\n ]\n const entries: Record<string, PageDataEntry> = {}\n const [, product, extra, hours] = await Promise.all([\n // Failures are isolated per need: an unseeded need falls back to fetching\n // in the browser, which is the pre-SSR behaviour, not a new failure mode.\n Promise.all(needs.map(async (need) => { entries[need.key] = await resolvePageDataNeed(need) })),\n productFor(path),\n extend.load ? extend.load(path) : Promise.resolve(null),\n hoursFor(path),\n ])\n // Two ways a URL has nothing behind it: the route table does not know it,\n // or it is a well-formed product URL whose product is gone. Both answer 404\n // — a soft 404 gets indexed as real content.\n const missing =\n !isKnownPath(config, path) ||\n (Boolean(matchPath('/product/:slug', path)) && product === null) ||\n Boolean(extend.missing?.(path, extra))\n // Variants only (see StoreRouteData.experiments).\n const flags = request?.experiments?.flags\n return { path, entries, product, extra, hours, missing, origin, ...(flags && Object.keys(flags).length ? { experiments: { flags } } : {}) }\n }\n\n function menuFrom(data: StoreRouteData<Extra>): { products: Product[]; categories: Category[] } {\n const read = <T,>(loader: string): T[] => {\n const need = menuDataNeeds().find((n) => n.loader === loader)\n const entry = need ? data.entries[need.key] : undefined\n return entry?.status === 'ready' && Array.isArray(entry.data) ? (entry.data as T[]) : []\n }\n return { products: read<Product>('products.list'), categories: read<Category>('categories.list') }\n }\n\n function headFor(data: StoreRouteData<Extra> | undefined): StoreHead {\n const path = data?.path ?? '/'\n const origin = (data?.origin ?? '').replace(/\\/$/, '')\n const ctx = { config, path, origin }\n const page = options.pages?.[path.split('?')[0] || '/']\n const business = businessFrom(config, {\n hours: data?.hours ?? [],\n origin,\n description: options.description,\n override: options.business,\n })\n\n const own = data ? extend.head?.(data, ctx) : null\n const base: SeoConfig =\n own ??\n (data?.product\n ? (productSeo(data.product, ctx) as SeoConfig)\n : data && isMenu(path)\n ? (menuSeo({ ...menuFrom(data), ...page, business }, ctx) as SeoConfig)\n : (pageSeo(ctx, page ?? { title: config.name, description: options.description }) as SeoConfig))\n\n const jsonLd = [\n ...(base.jsonLd ?? []),\n ...(path === '/' ? homeJsonLd(config, origin, business) : []),\n ...(data ? (extend.jsonLd?.(data, ctx) ?? []) : []),\n ]\n // A relative og:image renders as a broken share card; the store's cover is\n // the fallback for a page that has no photo of its own.\n const image = absoluteUrl(base.image ?? business.image, origin)\n const tags = buildSeoTags({ ...base, jsonLd, ...(image ? { image } : {}) })\n return {\n meta: [\n { charSet: 'utf-8' as const },\n { name: 'viewport', content: 'width=device-width, initial-scale=1' },\n { title: tags.title },\n ...tags.meta,\n // Checkout, account and order pages are one shopper's state at a URL —\n // never indexed. Same line the cache header is drawn on.\n ...(isPublicPath(config, path) ? [] : [{ name: 'robots', content: 'noindex,nofollow' }]),\n ],\n links: tags.links,\n scripts: tags.jsonLd.map((obj) => ({\n type: 'application/ld+json',\n children: JSON.stringify(obj).replace(/</g, '\\\\u003c'),\n })),\n }\n }\n\n return {\n config,\n redirectFor: (path) => redirects[path.split('?')[0] || '/'] ?? null,\n loadRoute,\n headFor,\n cacheControlFor: (path) => (isPublicPath(config, path) ? PUBLIC_CACHE : PRIVATE_CACHE),\n renderedOnServer: (path) => isPublicPath(config, path),\n isCanonicalHost(url) {\n if (!canonicalHost) return true\n const host = url.hostname\n // Local and preview hosts are never reachable by a crawler, and treating\n // them as foreign would put noindex in front of every e2e run.\n return host === canonicalHost || host === 'localhost' || host === '127.0.0.1' || host.endsWith('.local')\n },\n async sitemapXml(origin) {\n const [paths, extra] = await Promise.all([\n storefrontPaths({ config, provider: getShopProvider() }),\n extend.sitemapPaths?.() ?? Promise.resolve([]),\n ])\n // A URL that answers 301 does not belong in a sitemap: Search Console\n // reports it as an error, and the page it points to is listed already.\n const all = [...paths, ...extra].filter((p, i, list) => !redirects[p] && list.indexOf(p) === i)\n return buildSitemapXml(origin, all)\n },\n robotsTxt(url, origin) {\n if (this.isCanonicalHost(url)) return buildRobotsTxt(origin)\n return `# Not the store's address. The store is at ${options.canonicalOrigin}.\\nUser-agent: *\\nDisallow: /\\n`\n },\n }\n}\n","import { getShopProvider, setShopProvider } from '@fayz-ai/shop/runtime'\n\n// ---------------------------------------------------------------------------\n// Photos addressed at the store, not at the storage project.\n//\n// Catalogue rows hold absolute Supabase URLs, and the SDK builds every image\n// variant from whatever URL it is given — its matcher reads the PATH, not the\n// host. So serving photos from the store's own origin (see ./edge-handler) is\n// a matter of swapping the host on the way out of the provider.\n//\n// The origin is read per call, never captured: on the server it is the request\n// being answered, in the browser the page's own origin, and those two agree —\n// which keeps hydration quiet. With no origin, the row's URL is left alone and\n// photos come from Supabase: degraded, never broken.\n// ---------------------------------------------------------------------------\n\nconst PUBLIC_OBJECT = /^https?:\\/\\/[^/]+(\\/storage\\/v1\\/(?:object|render\\/image)\\/public\\/shop-images\\/[^\"'\\s]*)$/\n\n/** Swap the host of a storage URL for `origin`; anything else passes through. */\nexport function onOrigin(value: string, origin: string): string {\n const match = PUBLIC_OBJECT.exec(value)\n return match ? `${origin}${match[1]}` : value\n}\n\nfunction rewrite<T>(value: T, origin: string, depth = 0): T {\n if (depth > 6) return value\n if (typeof value === 'string') return onOrigin(value, origin) as T\n if (Array.isArray(value)) return value.map((v) => rewrite(v, origin, depth + 1)) as T\n if (value && typeof value === 'object' && (value as object).constructor === Object) {\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) out[k] = rewrite(v, origin, depth + 1)\n return out as T\n }\n return value\n}\n\n/**\n * Wrap any data provider so every storage URL it returns addresses `originOf()`.\n * A proxy rather than a list of methods: providers grow, and a photo can come\n * back from a product, a cart line, an order or a blog post.\n */\nexport function withImagesOnOrigin<T extends object>(provider: T, originOf: () => string): T {\n return new Proxy(provider, {\n get(target, prop) {\n // `target` as the receiver: a provider is a class instance, and a getter\n // handed the proxy as `this` throws on its private fields.\n const value = Reflect.get(target, prop, target)\n if (typeof value !== 'function') return value\n return (...args: unknown[]) => {\n const out = (value as (...a: unknown[]) => unknown).apply(target, args)\n const origin = originOf()\n if (!origin) return out\n return out instanceof Promise ? out.then((v) => rewrite(v, origin)) : rewrite(out, origin)\n }\n },\n }) as T\n}\n\n/**\n * Point every catalogue photo at the store's own origin. Call once, after\n * `initStorefrontRuntime` — it is the provider that call installs that gets\n * wrapped. Other providers serving the same bucket (a blog's) wrap themselves\n * with `withImagesOnOrigin`.\n */\nexport function serveShopImagesFromOrigin(originOf: () => string): void {\n setShopProvider(withImagesOnOrigin(getShopProvider(), originOf))\n}\n"]}
package/dist/index.js CHANGED
@@ -1,10 +1,30 @@
1
1
  export { auditStorefrontSeo, collectSeoAuditInput } from './chunk-6IXTENLB.js';
2
- import { STORE_SECTION_NAMES, isRecord, STORE_SECTIONS, slugifyStoreName, exportStore, validateStore, isJsonValue, sectionToJsonSchema, STOREFRONT_PAGE_KINDS, STOREFRONT_COMPONENT_KEYS, hydrateStoreDocument } from './chunk-QU5O4CKY.js';
3
- export { STOREFRONT_COMPONENT_KEYS, STOREFRONT_PAGE_KINDS, STORE_BACKEND_PROVIDERS, STORE_COMMERCE_MODES, STORE_DOCUMENT_KIND, STORE_PAYMENT_METHOD_KINDS, STORE_PAYMENT_MODES, STORE_ROUTE_CHROMES, STORE_ROUTE_KINDS, STORE_SECTIONS, STORE_SECTION_NAMES, exportStore, hydrateStoreDocument, sectionToJsonSchema, slugifyStoreName, validateStore } from './chunk-QU5O4CKY.js';
4
- import { initStorefrontRuntime, StorefrontConfigHost, StorefrontShell, registerStorefrontBlocks, registerStorefrontExtensionPoints, STOREFRONT_SLOTS, useCatalogStore, formatMoney, useCartStore, useSessionStore, selectTotal, selectShipping, selectDiscountTotal, selectSubtotal, selectCount, placeStorefrontOrder, useDeliveryStore, useBuyerCountry, selectRequiresShipping, StorefrontPage } from './chunk-7FR6KOW2.js';
5
- export { AddressForm, AddressSheet, AddressStrip, BEFORE_PLACE_ORDER_VALVE, BUYER_COUNTRY_COOKIE, BenefitsRow, CART_LINE_ANNOTATION_POINT, CHECKOUT_FIELD_POINT, CartDrawer, CatalogPage, CategoryShowcase, CheckoutPage, ClosedStoreDialog, CollectionCta, CollectionHero, ContentPage, CountdownBand, CountrySelect, DeliverToPicker, EmailConfirmationRequiredError, EntityListBlock, ExperimentBlock, ExperimentProvider, ExperimentVariantBlock, FaqSection, FiltersPanel, FormBlock, HeroSection, ImageTiles, ItemSheet, ManifestoBlock, MediaCarousel, MenuItemRow, MenuPage, MenuToolbar, MotifStrip, MyPurchasesPage, NewsletterBand, ORDER_METADATA_POINT, OrderConfirmationPage, OrderTrackingTimeline, PAGE_DATA_SCRIPT_ID, PLACEHOLDER_ASPECTS, PRODUCT_CARD_BADGE_POINT, PaymentTerms, Price, ProductCard, ProductDetailPage, ProductEnquiryForm, ProductGallery, ProductGrid, ProductOptionSelector, ProductRail, ProductReviews, ProductSlider, ProductSpecs, ProductSpotlight, PromoBanner, QuantityInput, Reveal, STOREFRONT_SLOTS, ScheduleSheet, SealsBand, SearchOverlay, ServiceHoursModal, Slot, SmoothImage, StepsSection, StoreCover, StorefrontAnalytics, StorefrontConfigHost, StorefrontFooter, StorefrontHeader, StorefrontPage, StorefrontShell, StorefrontThemeStyle, StoryQuote, THEME_SCALE_FIELDS, THEME_SCALE_VAR_PREFIX, TID, Testimonials, TierCards, applyDeliveryPolicy, applyStoreSettings, bannerPlaceholder, buildEnquiryFromForm, collectOrderMetadata, createStorefront, createStorefrontApp, entityListBlockMeta, establishCustomerSession, formatMoney, formatProductOptionSelection, getCartLineAnnotations, getCheckoutFieldContributions, getCustomerAuthAdapter, getProductCardBadges, getProductOptionGroups, hydratedEntriesFor, identifyStorefrontBuyer, initStorefrontRuntime, isExperimentBinding, listSlots, marketForCountry, marketUnitPrice, matchesFacets, mergeCollectionFacet, nextMilestone, normalizeProductOptionSelection, onStorefrontEvent, orderGalleryImages, overrideComponent, overrideProps, pickProductCardImage, placeStorefrontOrder, prefersReducedMotion, productCardComponentContract, productOptionSelectionKey, productPlaceholder, readHydratedPageData, registerStorefrontBlocks, registerStorefrontEntities, registerStorefrontExtensionPoints, registerStorefrontLoaders, resetPageDiagnostics, resetProductOptionsDeprecationWarnings, resetStorefrontExtensionPoints, resolveAuthAdapter, resolveExperimental, resolveOverride, resolvePaymentTerms, resolveStorefrontProvider, resolveVariantForSelection, roundCents, runBeforePlaceOrderValve, sectionsToBlocks, selectCount, selectDiscountTotal, selectIsOpen, selectShipping, selectShowsHours, selectSubtotal, selectTotal, selectionPrice, serializePageData, signInByEmail, signOutCustomer, signUpCustomer, startStorefrontPostHog, storeBuyerId, storefrontComponentContracts, themeToCss, toFacetMap, trackStorefront, useBuyerCountry, useBuyerStore, useCartStore, useCatalogStore, useCategories, useDiscountValidator, useEnquiry, useExperiment, useExperimentValue, useInView, useMarketProduct, useMarketStore, useMyOrders, usePopOnChange, useProduct, useProducts, useScrolled, useServiceStore, useSessionStore, useSlotContext, useSlotFilled, useStorefrontActions, useStorefrontHead, useStorefrontPage, useStorefrontPageOptional, useT, usedSlots, withFacet, withMarketPrice } from './chunk-7FR6KOW2.js';
6
- import { resolveConfig, useStorefrontConfig, isNestedBlockRef, BLOCK_STRUCTURAL_KEYS, blockInstanceId } from './chunk-7N5HKQBM.js';
7
- export { BlockDataError, CATALOG_BODY_BLOCK, Link, MESSAGES, PRODUCT_BODY_BLOCK, PageDataScope, StorefrontConfigProvider, StorefrontRouterProvider, checkPageBlocks, collectDataNeeds, contentPagePath, currentStaticRenderPass, defaultPageBlocks, isKnownPath, isPublicPath, matchContentPage, matchPath, menuDataNeeds, menuJsonLd, menuSeo, mergePageTheme, navigateTo, normalizeLanguage, pageSeo, pageThemeSelector, primaryImageUrl, productJsonLd, productSeo, resolveConfig, resolveDataParams, resolvePageData, resolvePageDataNeed, resolveStorefrontRoute, routePathParams, shellRouterAdapter, storefrontPaths, toBlockNodes, translate, useBlockData, useHashPath, useLoader, useNavigate, useRoutePath, useRouterAdapter, useScrollToTopOnNavigate, useStorefrontConfig, useStorefrontConfigOptional, withStaticRenderPass } from './chunk-7N5HKQBM.js';
2
+ import { placeStorefrontOrder } from './chunk-DRMDBXMT.js';
3
+ export { AddressForm, CheckoutPage, CountrySelect, DeliverToPicker, placeStorefrontOrder } from './chunk-DRMDBXMT.js';
4
+ export { OrderConfirmationPage } from './chunk-GQ3TCJVX.js';
5
+ import './chunk-WDQFNHYO.js';
6
+ export { MyPurchasesPage } from './chunk-CV6RYJIC.js';
7
+ export { useMyOrders } from './chunk-WPWNYBNG.js';
8
+ export { OrderTrackingTimeline } from './chunk-D5C5U2SG.js';
9
+ import './chunk-FCPOPWG3.js';
10
+ import { STORE_SECTION_NAMES, isRecord, STORE_SECTIONS, slugifyStoreName, exportStore, validateStore, isJsonValue, sectionToJsonSchema, STOREFRONT_PAGE_KINDS, STOREFRONT_COMPONENT_KEYS, hydrateStoreDocument } from './chunk-H6IOPA3J.js';
11
+ export { STOREFRONT_COMPONENT_KEYS, STOREFRONT_PAGE_KINDS, STORE_BACKEND_PROVIDERS, STORE_COMMERCE_MODES, STORE_DOCUMENT_KIND, STORE_PAYMENT_METHOD_KINDS, STORE_PAYMENT_MODES, STORE_ROUTE_CHROMES, STORE_ROUTE_KINDS, STORE_SECTIONS, STORE_SECTION_NAMES, exportStore, hydrateStoreDocument, sectionToJsonSchema, slugifyStoreName, validateStore } from './chunk-H6IOPA3J.js';
12
+ import { initStorefrontRuntime, StorefrontConfigHost, StorefrontShell, registerStorefrontBlocks, useCatalogStore } from './chunk-HRLF5NYO.js';
13
+ export { AddressStrip, BenefitsRow, CartDrawer, CatalogPage, CategoryShowcase, ClosedStoreDialog, CollectionCta, CollectionHero, ContentPage, CountdownBand, EntityListBlock, ExperimentBlock, ExperimentProvider, ExperimentVariantBlock, FaqSection, FiltersPanel, FormBlock, HeroSection, ImageTiles, ItemSheet, ManifestoBlock, MediaCarousel, MenuItemRow, MenuPage, MenuToolbar, MotifStrip, NewsletterBand, PLACEHOLDER_ASPECTS, PaymentTerms, Price, ProductCard, ProductDetailPage, ProductEnquiryForm, ProductGallery, ProductGrid, ProductOptionSelector, ProductRail, ProductReviews, ProductSlider, ProductSpecs, ProductSpotlight, PromoBanner, QuantityInput, ScheduleSheet, SealsBand, SearchOverlay, ServiceHoursModal, StepsSection, StoreCover, StorefrontAnalytics, StorefrontConfigHost, StorefrontFooter, StorefrontHeader, StorefrontShell, StorefrontThemeStyle, StoryQuote, THEME_SCALE_FIELDS, THEME_SCALE_VAR_PREFIX, Testimonials, TierCards, applyDeliveryPolicy, applyStoreSettings, bannerPlaceholder, buildEnquiryFromForm, createStorefront, createStorefrontApp, entityListBlockMeta, initStorefrontRuntime, isExperimentBinding, matchesFacets, mergeCollectionFacet, nextMilestone, orderGalleryImages, overrideComponent, overrideProps, pickProductCardImage, productCardComponentContract, productPlaceholder, registerStorefrontBlocks, registerStorefrontEntities, registerStorefrontLoaders, resolveExperimental, resolveOverride, resolvePaymentTerms, resolveStorefrontProvider, sectionsToBlocks, selectIsOpen, selectShowsHours, startStorefrontPostHog, storeBuyerId, storefrontComponentContracts, themeToCss, toFacetMap, useCatalogStore, useCategories, useEnquiry, useExperiment, useExperimentValue, useMarketProduct, useProduct, useProducts, useServiceStore, useStorefrontActions, withFacet } from './chunk-HRLF5NYO.js';
14
+ import { registerStorefrontExtensionPoints, STOREFRONT_SLOTS, useBuyerCountry, StorefrontPage } from './chunk-MFAU3AWK.js';
15
+ export { BEFORE_PLACE_ORDER_VALVE, BUYER_COUNTRY_COOKIE, CART_LINE_ANNOTATION_POINT, CHECKOUT_FIELD_POINT, ORDER_METADATA_POINT, PAGE_DATA_SCRIPT_ID, PRODUCT_CARD_BADGE_POINT, STOREFRONT_SLOTS, Slot, StorefrontPage, collectOrderMetadata, getCartLineAnnotations, getCheckoutFieldContributions, getProductCardBadges, hydratedEntriesFor, identifyStorefrontBuyer, listSlots, onStorefrontEvent, readHydratedPageData, registerStorefrontExtensionPoints, resetPageDiagnostics, resetStorefrontExtensionPoints, runBeforePlaceOrderValve, serializePageData, trackStorefront, useBuyerCountry, useBuyerStore, useDiscountValidator, useSlotContext, useSlotFilled, useStorefrontPage, useStorefrontPageOptional, useT, usedSlots } from './chunk-MFAU3AWK.js';
16
+ export { Reveal, prefersReducedMotion, useInView, usePopOnChange, useScrolled } from './chunk-WXXYJZE6.js';
17
+ export { AddressSheet } from './chunk-OHWX6BEY.js';
18
+ import { useCartStore, selectTotal, selectShipping, selectDiscountTotal, selectSubtotal, selectCount, useDeliveryStore, selectRequiresShipping } from './chunk-K27LYMSF.js';
19
+ export { formatProductOptionSelection, getProductOptionGroups, marketForCountry, marketUnitPrice, normalizeProductOptionSelection, productOptionSelectionKey, resetProductOptionsDeprecationWarnings, resolveVariantForSelection, selectCount, selectDiscountTotal, selectShipping, selectSubtotal, selectTotal, selectionPrice, useCartStore, useMarketStore, withMarketPrice } from './chunk-K27LYMSF.js';
20
+ import './chunk-DVQHKNNX.js';
21
+ import { formatMoney, useSessionStore } from './chunk-PATS4DIF.js';
22
+ export { EmailConfirmationRequiredError, SmoothImage, TID, establishCustomerSession, formatMoney, getCustomerAuthAdapter, resolveAuthAdapter, roundCents, signInByEmail, signOutCustomer, signUpCustomer, useSessionStore, useStorefrontHead } from './chunk-PATS4DIF.js';
23
+ export { isKnownPath, isPublicPath, menuDataNeeds, menuJsonLd, menuSeo, pageSeo, primaryImageUrl, productJsonLd, productSeo, resolveStorefrontRoute, storefrontPaths } from './chunk-45R7CIEA.js';
24
+ import { isNestedBlockRef, BLOCK_STRUCTURAL_KEYS, blockInstanceId } from './chunk-BKO2AQ3Q.js';
25
+ export { BlockDataError, CATALOG_BODY_BLOCK, PRODUCT_BODY_BLOCK, PageDataScope, checkPageBlocks, collectDataNeeds, contentPagePath, defaultPageBlocks, matchContentPage, mergePageTheme, pageThemeSelector, resolveDataParams, resolvePageData, resolvePageDataNeed, routePathParams, toBlockNodes, useBlockData, useLoader } from './chunk-BKO2AQ3Q.js';
26
+ import { resolveConfig, useStorefrontConfig } from './chunk-GVZ2SAOI.js';
27
+ export { Link, MESSAGES, StorefrontConfigProvider, StorefrontRouterProvider, currentStaticRenderPass, matchPath, navigateTo, normalizeLanguage, resolveConfig, shellRouterAdapter, translate, useHashPath, useNavigate, useRoutePath, useRouterAdapter, useScrollToTopOnNavigate, useStorefrontConfig, useStorefrontConfigOptional, withStaticRenderPass } from './chunk-GVZ2SAOI.js';
8
28
  export { CONTROL as EXPERIMENT_CONTROL, validateExperiments } from './chunk-4REZOGGW.js';
9
29
  import React3 from 'react';
10
30
  import { defineApp, registerScaffold, describeExtensionPoints, handleRegistry, describeEntities, describeLoaders, describeBlocks, ENTITY_FIELD_TYPES, BLOCK_SETTING_TYPES, errorMessage, normalizePostalCode, lookupPostalCode, formatPostalCode, BLOCK_PATH_ATTRIBUTE, BLOCK_TYPE_ATTRIBUTE, setBlockEditorMode, renderApp as renderApp$1 } from '@fayz-ai/core';