@proxima-io/storefront-core 0.3.0 → 0.8.2

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 (178) hide show
  1. package/README.md +68 -17
  2. package/dist/addresses/address-book.d.ts +36 -0
  3. package/dist/addresses/address-book.d.ts.map +1 -0
  4. package/dist/addresses/address-book.js +62 -0
  5. package/dist/addresses/address-book.js.map +1 -0
  6. package/dist/analytics/analytics.d.ts +28 -0
  7. package/dist/analytics/analytics.d.ts.map +1 -0
  8. package/dist/analytics/analytics.js +124 -0
  9. package/dist/analytics/analytics.js.map +1 -0
  10. package/dist/analytics/attribution.d.ts +28 -0
  11. package/dist/analytics/attribution.d.ts.map +1 -0
  12. package/dist/analytics/attribution.js +116 -0
  13. package/dist/analytics/attribution.js.map +1 -0
  14. package/dist/analytics/session.d.ts +12 -0
  15. package/dist/analytics/session.d.ts.map +1 -0
  16. package/dist/analytics/session.js +62 -0
  17. package/dist/analytics/session.js.map +1 -0
  18. package/dist/analytics/trackers.d.ts +29 -0
  19. package/dist/analytics/trackers.d.ts.map +1 -0
  20. package/dist/analytics/trackers.js +30 -0
  21. package/dist/analytics/trackers.js.map +1 -0
  22. package/dist/api/endpoints.d.ts +70 -0
  23. package/dist/api/endpoints.d.ts.map +1 -0
  24. package/dist/api/endpoints.js +70 -0
  25. package/dist/api/endpoints.js.map +1 -0
  26. package/dist/api/index.d.ts +3 -0
  27. package/dist/api/index.d.ts.map +1 -0
  28. package/dist/api/index.js +3 -0
  29. package/dist/api/index.js.map +1 -0
  30. package/dist/api/storefront-client.d.ts +50 -0
  31. package/dist/api/storefront-client.d.ts.map +1 -0
  32. package/dist/api/storefront-client.js +123 -0
  33. package/dist/api/storefront-client.js.map +1 -0
  34. package/dist/buyer/auth.d.ts +105 -0
  35. package/dist/buyer/auth.d.ts.map +1 -0
  36. package/dist/buyer/auth.js +215 -0
  37. package/dist/buyer/auth.js.map +1 -0
  38. package/dist/cache/cache.d.ts +31 -0
  39. package/dist/cache/cache.d.ts.map +1 -0
  40. package/dist/cache/cache.js +71 -0
  41. package/dist/cache/cache.js.map +1 -0
  42. package/dist/campaign/countdown.d.ts +40 -0
  43. package/dist/campaign/countdown.d.ts.map +1 -0
  44. package/dist/campaign/countdown.js +71 -0
  45. package/dist/campaign/countdown.js.map +1 -0
  46. package/dist/cart/cart.d.ts +57 -0
  47. package/dist/cart/cart.d.ts.map +1 -0
  48. package/dist/cart/cart.js +64 -0
  49. package/dist/cart/cart.js.map +1 -0
  50. package/dist/catalog/listings.d.ts +87 -0
  51. package/dist/catalog/listings.d.ts.map +1 -0
  52. package/dist/catalog/listings.js +140 -0
  53. package/dist/catalog/listings.js.map +1 -0
  54. package/dist/cms/payment-methods.d.ts +13 -0
  55. package/dist/cms/payment-methods.d.ts.map +1 -0
  56. package/dist/cms/payment-methods.js +41 -0
  57. package/dist/cms/payment-methods.js.map +1 -0
  58. package/dist/cms/website.d.ts +76 -0
  59. package/dist/cms/website.d.ts.map +1 -0
  60. package/dist/cms/website.js +146 -0
  61. package/dist/cms/website.js.map +1 -0
  62. package/dist/cookie-consent/consent.d.ts +22 -0
  63. package/dist/cookie-consent/consent.d.ts.map +1 -0
  64. package/dist/cookie-consent/consent.js +93 -0
  65. package/dist/cookie-consent/consent.js.map +1 -0
  66. package/dist/index.d.ts +45 -1310
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +42 -1576
  69. package/dist/index.js.map +1 -1
  70. package/dist/internal/http.d.ts +5 -0
  71. package/dist/internal/http.d.ts.map +1 -0
  72. package/dist/internal/http.js +27 -0
  73. package/dist/internal/http.js.map +1 -0
  74. package/dist/orders/guest.d.ts +9 -0
  75. package/dist/orders/guest.d.ts.map +1 -0
  76. package/dist/orders/guest.js +30 -0
  77. package/dist/orders/guest.js.map +1 -0
  78. package/dist/orders/orders.d.ts +23 -0
  79. package/dist/orders/orders.d.ts.map +1 -0
  80. package/dist/orders/orders.js +33 -0
  81. package/dist/orders/orders.js.map +1 -0
  82. package/dist/seo/engine-url.d.ts +26 -0
  83. package/dist/seo/engine-url.d.ts.map +1 -0
  84. package/dist/seo/engine-url.js +111 -0
  85. package/dist/seo/engine-url.js.map +1 -0
  86. package/dist/seo/hreflang.d.ts +19 -0
  87. package/dist/seo/hreflang.d.ts.map +1 -0
  88. package/dist/seo/hreflang.js +52 -0
  89. package/dist/seo/hreflang.js.map +1 -0
  90. package/dist/seo/indexnow.d.ts +24 -0
  91. package/dist/seo/indexnow.d.ts.map +1 -0
  92. package/dist/seo/indexnow.js +50 -0
  93. package/dist/seo/indexnow.js.map +1 -0
  94. package/dist/seo/json-ld.d.ts +57 -0
  95. package/dist/seo/json-ld.d.ts.map +1 -0
  96. package/dist/seo/json-ld.js +180 -0
  97. package/dist/seo/json-ld.js.map +1 -0
  98. package/dist/seo/page-seo.d.ts +21 -0
  99. package/dist/seo/page-seo.d.ts.map +1 -0
  100. package/dist/seo/page-seo.js +68 -0
  101. package/dist/seo/page-seo.js.map +1 -0
  102. package/dist/seo/robots.d.ts +23 -0
  103. package/dist/seo/robots.d.ts.map +1 -0
  104. package/dist/seo/robots.js +35 -0
  105. package/dist/seo/robots.js.map +1 -0
  106. package/dist/seo/sitemap.d.ts +35 -0
  107. package/dist/seo/sitemap.d.ts.map +1 -0
  108. package/dist/seo/sitemap.js +186 -0
  109. package/dist/seo/sitemap.js.map +1 -0
  110. package/dist/server/process.d.ts +136 -0
  111. package/dist/server/process.d.ts.map +1 -0
  112. package/dist/server/process.js +143 -0
  113. package/dist/server/process.js.map +1 -0
  114. package/dist/types/address.d.ts +40 -0
  115. package/dist/types/address.d.ts.map +1 -0
  116. package/dist/types/address.js +2 -0
  117. package/dist/types/address.js.map +1 -0
  118. package/dist/types/analytics.d.ts +79 -0
  119. package/dist/types/analytics.d.ts.map +1 -0
  120. package/dist/types/analytics.js +2 -0
  121. package/dist/types/analytics.js.map +1 -0
  122. package/dist/types/business.d.ts +95 -0
  123. package/dist/types/business.d.ts.map +1 -0
  124. package/dist/types/business.js +2 -0
  125. package/dist/types/business.js.map +1 -0
  126. package/dist/types/buyer.d.ts +144 -0
  127. package/dist/types/buyer.d.ts.map +1 -0
  128. package/dist/types/buyer.js +45 -0
  129. package/dist/types/buyer.js.map +1 -0
  130. package/dist/types/campaign.d.ts +51 -0
  131. package/dist/types/campaign.d.ts.map +1 -0
  132. package/dist/types/campaign.js +2 -0
  133. package/dist/types/campaign.js.map +1 -0
  134. package/dist/types/cart.d.ts +40 -0
  135. package/dist/types/cart.d.ts.map +1 -0
  136. package/dist/types/cart.js +2 -0
  137. package/dist/types/cart.js.map +1 -0
  138. package/dist/types/catalog.d.ts +164 -0
  139. package/dist/types/catalog.d.ts.map +1 -0
  140. package/dist/types/catalog.js +2 -0
  141. package/dist/types/catalog.js.map +1 -0
  142. package/dist/types/cms.d.ts +200 -0
  143. package/dist/types/cms.d.ts.map +1 -0
  144. package/dist/types/cms.js +2 -0
  145. package/dist/types/cms.js.map +1 -0
  146. package/dist/types/cookie-consent.d.ts +18 -0
  147. package/dist/types/cookie-consent.d.ts.map +1 -0
  148. package/dist/types/cookie-consent.js +7 -0
  149. package/dist/types/cookie-consent.js.map +1 -0
  150. package/dist/types/guest-order.d.ts +16 -0
  151. package/dist/types/guest-order.d.ts.map +1 -0
  152. package/dist/types/guest-order.js +9 -0
  153. package/dist/types/guest-order.js.map +1 -0
  154. package/dist/types/listing.d.ts +14 -0
  155. package/dist/types/listing.d.ts.map +1 -0
  156. package/dist/types/listing.js +2 -0
  157. package/dist/types/listing.js.map +1 -0
  158. package/dist/types/order.d.ts +40 -0
  159. package/dist/types/order.d.ts.map +1 -0
  160. package/dist/types/order.js +2 -0
  161. package/dist/types/order.js.map +1 -0
  162. package/dist/types/seo.d.ts +96 -0
  163. package/dist/types/seo.d.ts.map +1 -0
  164. package/dist/types/seo.js +2 -0
  165. package/dist/types/seo.js.map +1 -0
  166. package/dist/types/server-env.d.ts +19 -0
  167. package/dist/types/server-env.d.ts.map +1 -0
  168. package/dist/types/server-env.js +10 -0
  169. package/dist/types/server-env.js.map +1 -0
  170. package/dist/types/wishlist.d.ts +10 -0
  171. package/dist/types/wishlist.d.ts.map +1 -0
  172. package/dist/types/wishlist.js +2 -0
  173. package/dist/types/wishlist.js.map +1 -0
  174. package/dist/wishlist/wishlist.d.ts +28 -0
  175. package/dist/wishlist/wishlist.d.ts.map +1 -0
  176. package/dist/wishlist/wishlist.js +42 -0
  177. package/dist/wishlist/wishlist.js.map +1 -0
  178. package/package.json +1 -1
@@ -0,0 +1,57 @@
1
+ import type { BreadcrumbItem, JsonLdLocalBusinessMeta, JsonLdProductMeta, JsonLdWebsiteMeta } from '../types/seo.js';
2
+ /**
3
+ * Build a `WebSite` JSON-LD object.
4
+ * Enables Google's Search Action box in SERPs.
5
+ *
6
+ * @example
7
+ * <script type="application/ld+json" set:html={JSON.stringify(buildWebSiteJsonLd(website))} />
8
+ */
9
+ export declare function buildWebSiteJsonLd(website: JsonLdWebsiteMeta): Record<string, any>;
10
+ /**
11
+ * Build an `Organization` JSON-LD object.
12
+ * Returns `null` when `website.logo_url` is absent (Google ignores logo-less org markup).
13
+ *
14
+ * @example
15
+ * {orgJsonLd && <script type="application/ld+json" set:html={JSON.stringify(orgJsonLd)} />}
16
+ */
17
+ export declare function buildOrganizationJsonLd(website: JsonLdWebsiteMeta): Record<string, any> | null;
18
+ /**
19
+ * Build a `Product` JSON-LD object for a product detail page.
20
+ * Includes `Offer` with pricing, currency, and availability.
21
+ *
22
+ * @example
23
+ * <script type="application/ld+json" set:html={JSON.stringify(buildProductJsonLd(product, website))} />
24
+ */
25
+ export declare function buildProductJsonLd(product: JsonLdProductMeta, website: {
26
+ domain: string;
27
+ currency: string;
28
+ }, options?: {
29
+ productUrl?: string;
30
+ locale?: string;
31
+ localizedPaths?: Record<string, string>;
32
+ defaultLocale?: string;
33
+ }): Record<string, any>;
34
+ /**
35
+ * Build a `BreadcrumbList` JSON-LD object.
36
+ *
37
+ * @param items Array of breadcrumb steps. The last item typically has no `href`.
38
+ * @param siteUrl Absolute site URL, e.g. `https://example.com`
39
+ *
40
+ * @example
41
+ * const crumbs = buildBreadcrumbJsonLd(
42
+ * [{ label: "Inicio", href: "/" }, { label: "Zapatos", href: "/categoria/zapatos" }, { label: "Nike Air Max" }],
43
+ * `https://${website.domain}`
44
+ * );
45
+ * <script type="application/ld+json" set:html={JSON.stringify(crumbs)} />
46
+ */
47
+ export declare function buildBreadcrumbJsonLd(items: BreadcrumbItem[], siteUrl: string): Record<string, any>;
48
+ /**
49
+ * Build a `LocalBusiness` JSON-LD object for brick-and-mortar stores.
50
+ * Returns `null` when `seo` is falsy so callers can gate rendering easily.
51
+ *
52
+ * @example
53
+ * const schema = buildLocalBusinessJsonLd(seo);
54
+ * {schema && <script type="application/ld+json" set:html={JSON.stringify(schema)} />}
55
+ */
56
+ export declare function buildLocalBusinessJsonLd(seo: JsonLdLocalBusinessMeta | null | undefined): Record<string, unknown> | null;
57
+ //# sourceMappingURL=json-ld.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-ld.d.ts","sourceRoot":"","sources":["../../src/seo/json-ld.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAGrH;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAgBlF;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAU5B;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAC7C,OAAO,CAAC,EAAE;IACR,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GACA,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAqDrB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,cAAc,EAAE,EACvB,OAAO,EAAE,MAAM,GACd,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAarB;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,uBAAuB,GAAG,IAAI,GAAG,SAAS,GAC9C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CA6ChC"}
@@ -0,0 +1,180 @@
1
+ import { buildEnginePageUrl } from './engine-url.js';
2
+ /**
3
+ * Build a `WebSite` JSON-LD object.
4
+ * Enables Google's Search Action box in SERPs.
5
+ *
6
+ * @example
7
+ * <script type="application/ld+json" set:html={JSON.stringify(buildWebSiteJsonLd(website))} />
8
+ */
9
+ export function buildWebSiteJsonLd(website) {
10
+ const siteUrl = `https://${website.domain}`;
11
+ return {
12
+ "@context": "https://schema.org",
13
+ "@type": "WebSite",
14
+ "name": website.name,
15
+ "url": `${siteUrl}/`,
16
+ "potentialAction": {
17
+ "@type": "SearchAction",
18
+ "target": {
19
+ "@type": "EntryPoint",
20
+ "urlTemplate": `${siteUrl}/buscar?q={search_term_string}`,
21
+ },
22
+ "query-input": "required name=search_term_string",
23
+ },
24
+ };
25
+ }
26
+ /**
27
+ * Build an `Organization` JSON-LD object.
28
+ * Returns `null` when `website.logo_url` is absent (Google ignores logo-less org markup).
29
+ *
30
+ * @example
31
+ * {orgJsonLd && <script type="application/ld+json" set:html={JSON.stringify(orgJsonLd)} />}
32
+ */
33
+ export function buildOrganizationJsonLd(website) {
34
+ if (!website.logo_url)
35
+ return null;
36
+ const siteUrl = `https://${website.domain}`;
37
+ return {
38
+ "@context": "https://schema.org",
39
+ "@type": "Organization",
40
+ "name": website.name,
41
+ "url": `${siteUrl}/`,
42
+ "logo": { "@type": "ImageObject", "url": website.logo_url },
43
+ };
44
+ }
45
+ /**
46
+ * Build a `Product` JSON-LD object for a product detail page.
47
+ * Includes `Offer` with pricing, currency, and availability.
48
+ *
49
+ * @example
50
+ * <script type="application/ld+json" set:html={JSON.stringify(buildProductJsonLd(product, website))} />
51
+ */
52
+ export function buildProductJsonLd(product, website, options) {
53
+ const siteUrl = `https://${website.domain}`;
54
+ const defaultLocale = options?.defaultLocale ?? 'es';
55
+ const productUrl = options?.productUrl ??
56
+ (options?.localizedPaths && options.locale
57
+ ? buildEnginePageUrl(website.domain, options.locale, options.localizedPaths, { slug: product.slug }, defaultLocale)
58
+ : `${siteUrl}/producto/${product.slug}`);
59
+ // Deduplicated image list: primary first, then extras
60
+ const images = [
61
+ product.image,
62
+ ...(product.images?.filter((img) => img && img !== product.image) ?? []),
63
+ ].filter(Boolean);
64
+ const result = {
65
+ "@context": "https://schema.org",
66
+ "@type": "Product",
67
+ "name": product.name,
68
+ "image": images.length === 1 ? images[0] : images,
69
+ "offers": {
70
+ "@type": "Offer",
71
+ "url": productUrl,
72
+ "price": product.priceRaw,
73
+ "priceCurrency": website.currency,
74
+ "availability": product.inStock === false
75
+ ? "https://schema.org/OutOfStock"
76
+ : "https://schema.org/InStock",
77
+ },
78
+ };
79
+ if (product.description)
80
+ result["description"] = product.description;
81
+ if (product.sku)
82
+ result["sku"] = product.sku;
83
+ if (product.productId != null)
84
+ result["identifier"] = String(product.productId);
85
+ if (product.brand)
86
+ result["brand"] = { "@type": "Brand", "name": product.brand };
87
+ // Strikethrough price — only when compare-at is higher than current
88
+ if (product.compareAtPrice && product.compareAtPrice > product.priceRaw) {
89
+ result["offers"]["priceSpecification"] = {
90
+ "@type": "PriceSpecification",
91
+ "price": product.compareAtPrice,
92
+ "priceCurrency": website.currency,
93
+ };
94
+ }
95
+ return result;
96
+ }
97
+ /**
98
+ * Build a `BreadcrumbList` JSON-LD object.
99
+ *
100
+ * @param items Array of breadcrumb steps. The last item typically has no `href`.
101
+ * @param siteUrl Absolute site URL, e.g. `https://example.com`
102
+ *
103
+ * @example
104
+ * const crumbs = buildBreadcrumbJsonLd(
105
+ * [{ label: "Inicio", href: "/" }, { label: "Zapatos", href: "/categoria/zapatos" }, { label: "Nike Air Max" }],
106
+ * `https://${website.domain}`
107
+ * );
108
+ * <script type="application/ld+json" set:html={JSON.stringify(crumbs)} />
109
+ */
110
+ export function buildBreadcrumbJsonLd(items, siteUrl) {
111
+ return {
112
+ "@context": "https://schema.org",
113
+ "@type": "BreadcrumbList",
114
+ "itemListElement": items.map((item, i) => ({
115
+ "@type": "ListItem",
116
+ "position": i + 1,
117
+ "name": item.label,
118
+ ...(item.href
119
+ ? { "item": item.href.startsWith("http") ? item.href : `${siteUrl}${item.href}` }
120
+ : {}),
121
+ })),
122
+ };
123
+ }
124
+ /**
125
+ * Build a `LocalBusiness` JSON-LD object for brick-and-mortar stores.
126
+ * Returns `null` when `seo` is falsy so callers can gate rendering easily.
127
+ *
128
+ * @example
129
+ * const schema = buildLocalBusinessJsonLd(seo);
130
+ * {schema && <script type="application/ld+json" set:html={JSON.stringify(schema)} />}
131
+ */
132
+ export function buildLocalBusinessJsonLd(seo) {
133
+ if (!seo)
134
+ return null;
135
+ const result = {
136
+ "@context": "https://schema.org",
137
+ "@type": "LocalBusiness",
138
+ "name": seo.name,
139
+ "url": seo.url,
140
+ "@id": `${seo.url}#localbusiness`,
141
+ };
142
+ if (seo.image)
143
+ result["image"] = seo.image;
144
+ if (seo.telephone)
145
+ result["telephone"] = seo.telephone;
146
+ const hasAddress = seo.street_address || seo.address_locality || seo.address_country;
147
+ if (hasAddress) {
148
+ const address = { "@type": "PostalAddress" };
149
+ if (seo.street_address)
150
+ address["streetAddress"] = seo.street_address;
151
+ if (seo.address_locality)
152
+ address["addressLocality"] = seo.address_locality;
153
+ if (seo.address_region)
154
+ address["addressRegion"] = seo.address_region;
155
+ if (seo.postal_code)
156
+ address["postalCode"] = seo.postal_code;
157
+ if (seo.address_country)
158
+ address["addressCountry"] = seo.address_country;
159
+ result["address"] = address;
160
+ }
161
+ if (seo.latitude != null && seo.longitude != null) {
162
+ result["geo"] = {
163
+ "@type": "GeoCoordinates",
164
+ "latitude": seo.latitude,
165
+ "longitude": seo.longitude,
166
+ };
167
+ }
168
+ if (seo.opening_hours?.length || seo.opens || seo.closes) {
169
+ result["openingHoursSpecification"] = [{
170
+ "@type": "OpeningHoursSpecification",
171
+ ...(seo.opening_hours?.length ? { "dayOfWeek": seo.opening_hours } : {}),
172
+ ...(seo.opens ? { "opens": seo.opens } : {}),
173
+ ...(seo.closes ? { "closes": seo.closes } : {}),
174
+ }];
175
+ }
176
+ if (seo.social_links?.length)
177
+ result["sameAs"] = seo.social_links;
178
+ return result;
179
+ }
180
+ //# sourceMappingURL=json-ld.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-ld.js","sourceRoot":"","sources":["../../src/seo/json-ld.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { PageSeoMeta, PageSeoWebsiteMeta } from '../types/seo.js';
2
+ /**
3
+ * Build fully resolved SEO metadata for a page.
4
+ *
5
+ * Data priority:
6
+ * 1. Admin-set `PageSEO` fields in `composition.seo` (explicit overrides)
7
+ * 2. Entity-derived data in `composition.seo.entity_name` / `entity_image` (auto-populated by API)
8
+ * 3. Website-level defaults (`website.og_image_url`, etc.)
9
+ * 4. Hard fallbacks (empty strings)
10
+ *
11
+ * @param seoData The `seo` object from `ProximaCompositionResponse` (may be null)
12
+ * @param website Website-level SEO fields
13
+ * @param locale Locale code for resolving localized strings (e.g. "es")
14
+ * @param currentUrl Absolute URL of the current page — used as canonical fallback
15
+ *
16
+ * @example
17
+ * const seo = buildPageSeo(composition.seo, website, website.locale, canonicalUrl);
18
+ * // → pass to <SiteLayout seo={seo} />
19
+ */
20
+ export declare function buildPageSeo(seoData: Record<string, any> | null | undefined, website: PageSeoWebsiteMeta, locale: string, currentUrl: string): PageSeoMeta;
21
+ //# sourceMappingURL=page-seo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-seo.d.ts","sourceRoot":"","sources":["../../src/seo/page-seo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEvE;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,GAAG,SAAS,EAC/C,OAAO,EAAE,kBAAkB,EAC3B,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GACjB,WAAW,CAwDb"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Build fully resolved SEO metadata for a page.
3
+ *
4
+ * Data priority:
5
+ * 1. Admin-set `PageSEO` fields in `composition.seo` (explicit overrides)
6
+ * 2. Entity-derived data in `composition.seo.entity_name` / `entity_image` (auto-populated by API)
7
+ * 3. Website-level defaults (`website.og_image_url`, etc.)
8
+ * 4. Hard fallbacks (empty strings)
9
+ *
10
+ * @param seoData The `seo` object from `ProximaCompositionResponse` (may be null)
11
+ * @param website Website-level SEO fields
12
+ * @param locale Locale code for resolving localized strings (e.g. "es")
13
+ * @param currentUrl Absolute URL of the current page — used as canonical fallback
14
+ *
15
+ * @example
16
+ * const seo = buildPageSeo(composition.seo, website, website.locale, canonicalUrl);
17
+ * // → pass to <SiteLayout seo={seo} />
18
+ */
19
+ export function buildPageSeo(seoData, website, locale, currentUrl) {
20
+ /** Resolve a value that may be a localized dict `{ es: "...", en: "..." }` or a plain string */
21
+ function resolveLocalized(value) {
22
+ if (!value)
23
+ return null;
24
+ if (typeof value === "string")
25
+ return value || null;
26
+ if (typeof value === "object") {
27
+ const map = value;
28
+ return map[locale] ?? map["es"] ?? Object.values(map).find(Boolean) ?? null;
29
+ }
30
+ return null;
31
+ }
32
+ const entityName = seoData?.entity_name ?? null;
33
+ const entityImage = seoData?.entity_image ?? null;
34
+ // Title: admin-set > entity name + site name > site name
35
+ const adminTitle = resolveLocalized(seoData?.meta_title);
36
+ const title = adminTitle ?? (entityName ? `${entityName} | ${website.name}` : website.name);
37
+ // Description: admin-set > entity-based fallback > site name
38
+ const adminDescription = resolveLocalized(seoData?.meta_description);
39
+ const description = adminDescription ??
40
+ (entityName ? `${entityName} en ${website.name}` : website.name);
41
+ // OG image: admin-set > entity image > website og_image_url
42
+ const ogImage = seoData?.og_image ??
43
+ entityImage ??
44
+ (website.og_image_url ?? null);
45
+ const ogType = seoData?.og_type ?? "website";
46
+ const canonicalUrl = seoData?.canonical_url ?? currentUrl;
47
+ const robots = seoData?.robots === "noindex"
48
+ ? "noindex, nofollow"
49
+ : "index, follow";
50
+ const rawHandle = website.twitter_handle ?? null;
51
+ const twitterSite = rawHandle ? `@${rawHandle.replace(/^@/, "")}` : null;
52
+ return {
53
+ title,
54
+ description,
55
+ ogTitle: title,
56
+ ogDescription: description,
57
+ ogImage,
58
+ ogType,
59
+ ogSiteName: website.name,
60
+ canonicalUrl,
61
+ robots,
62
+ twitterCard: "summary_large_image",
63
+ twitterSite,
64
+ twitterImage: ogImage,
65
+ faviconUrl: website.favicon_url ?? null,
66
+ };
67
+ }
68
+ //# sourceMappingURL=page-seo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-seo.js","sourceRoot":"","sources":["../../src/seo/page-seo.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,YAAY,CAC1B,OAA+C,EAC/C,OAA2B,EAC3B,MAAc,EACd,UAAkB;IAElB,gGAAgG;IAChG,SAAS,gBAAgB,CAAC,KAAc;QACtC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,KAAK,IAAI,IAAI,CAAC;QACpD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,MAAM,GAAG,GAAG,KAA+B,CAAC;YAC5C,OAAO,GAAG,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;QAC9E,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,UAAU,GAAI,OAAO,EAAE,WAAyC,IAAI,IAAI,CAAC;IAC/E,MAAM,WAAW,GAAI,OAAO,EAAE,YAA0C,IAAI,IAAI,CAAC;IAEjF,yDAAyD;IACzD,MAAM,UAAU,GAAG,gBAAgB,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IACzD,MAAM,KAAK,GAAG,UAAU,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5F,6DAA6D;IAC7D,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;IACrE,MAAM,WAAW,GACf,gBAAgB;QAChB,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnE,4DAA4D;IAC5D,MAAM,OAAO,GACV,OAAO,EAAE,QAAsC;QAChD,WAAW;QACX,CAAC,OAAO,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC;IAEjC,MAAM,MAAM,GAAI,OAAO,EAAE,OAAqC,IAAI,SAAS,CAAC;IAC5E,MAAM,YAAY,GAAI,OAAO,EAAE,aAA2C,IAAI,UAAU,CAAC;IACzF,MAAM,MAAM,GACT,OAAO,EAAE,MAAoC,KAAK,SAAS;QAC1D,CAAC,CAAC,mBAAmB;QACrB,CAAC,CAAC,eAAe,CAAC;IAEtB,MAAM,SAAS,GAAG,OAAO,CAAC,cAAc,IAAI,IAAI,CAAC;IACjD,MAAM,WAAW,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAEzE,OAAO;QACL,KAAK;QACL,WAAW;QACX,OAAO,EAAE,KAAK;QACd,aAAa,EAAE,WAAW;QAC1B,OAAO;QACP,MAAM;QACN,UAAU,EAAE,OAAO,CAAC,IAAI;QACxB,YAAY;QACZ,MAAM;QACN,WAAW,EAAE,qBAAqB;QAClC,WAAW;QACX,YAAY,EAAE,OAAO;QACrB,UAAU,EAAE,OAAO,CAAC,WAAW,IAAI,IAAI;KACxC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Generate a `robots.txt` for a storefront.
3
+ *
4
+ * Blocks all private buyer routes and API paths.
5
+ * Adds a `Sitemap:` directive pointing to `{siteUrl}/sitemap.xml`.
6
+ *
7
+ * @example
8
+ * // apps/{slug}/src/pages/robots.txt.ts
9
+ * import type { APIRoute } from "astro";
10
+ * import { resolveWebsiteOnly } from "@/lib/resolver";
11
+ * import { generateRobotsTxt } from "@proxima-io/storefront-core";
12
+ *
13
+ * export const GET: APIRoute = async () => {
14
+ * const website = await resolveWebsiteOnly();
15
+ * return new Response(generateRobotsTxt(website), {
16
+ * headers: { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "public, max-age=86400" },
17
+ * });
18
+ * };
19
+ */
20
+ export declare function generateRobotsTxt(website: {
21
+ domain: string;
22
+ }): string;
23
+ //# sourceMappingURL=robots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"robots.d.ts","sourceRoot":"","sources":["../../src/seo/robots.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAcrE"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Generate a `robots.txt` for a storefront.
3
+ *
4
+ * Blocks all private buyer routes and API paths.
5
+ * Adds a `Sitemap:` directive pointing to `{siteUrl}/sitemap.xml`.
6
+ *
7
+ * @example
8
+ * // apps/{slug}/src/pages/robots.txt.ts
9
+ * import type { APIRoute } from "astro";
10
+ * import { resolveWebsiteOnly } from "@/lib/resolver";
11
+ * import { generateRobotsTxt } from "@proxima-io/storefront-core";
12
+ *
13
+ * export const GET: APIRoute = async () => {
14
+ * const website = await resolveWebsiteOnly();
15
+ * return new Response(generateRobotsTxt(website), {
16
+ * headers: { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "public, max-age=86400" },
17
+ * });
18
+ * };
19
+ */
20
+ export function generateRobotsTxt(website) {
21
+ const siteUrl = `https://${website.domain}`;
22
+ return [
23
+ "User-agent: *",
24
+ "Allow: /",
25
+ "",
26
+ "Disallow: /cuenta",
27
+ "Disallow: /carrito",
28
+ "Disallow: /checkout",
29
+ "Disallow: /api/",
30
+ "Disallow: /dev/",
31
+ "",
32
+ `Sitemap: ${siteUrl}/sitemap.xml`,
33
+ ].join("\n");
34
+ }
35
+ //# sourceMappingURL=robots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"robots.js","sourceRoot":"","sources":["../../src/seo/robots.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAA2B;IAC3D,MAAM,OAAO,GAAG,WAAW,OAAO,CAAC,MAAM,EAAE,CAAC;IAC5C,OAAO;QACL,eAAe;QACf,UAAU;QACV,EAAE;QACF,mBAAmB;QACnB,oBAAoB;QACpB,qBAAqB;QACrB,iBAAiB;QACjB,iBAAiB;QACjB,EAAE;QACF,YAAY,OAAO,cAAc;KAClC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
@@ -0,0 +1,35 @@
1
+ import type { SitemapWebsiteMeta } from '../types/seo.js';
2
+ /**
3
+ * Generate a complete `sitemap.xml` for a storefront.
4
+ *
5
+ * Includes:
6
+ * 1. Content pages from the website manifest (priority 1.0 for home, 0.8 for others)
7
+ * 2. Category pages from the recursive nav tree (priority 0.8)
8
+ * 3. Brand pages from the brands directory (priority 0.7)
9
+ * 4. Product pages — paginated up to `maxProducts` (priority 0.9)
10
+ *
11
+ * All entries use today's date as `lastmod`.
12
+ *
13
+ * @param website Resolved website object (domain + pages array)
14
+ * @param apiUrl Base URL of the Proxima API (e.g. `http://localhost:8000`)
15
+ * @param options Optional overrides: pageSize (default 60), maxProducts (default 3000)
16
+ *
17
+ * @example
18
+ * // apps/{slug}/src/pages/sitemap.xml.ts
19
+ * import type { APIRoute } from "astro";
20
+ * import { resolveWebsiteOnly } from "@/lib/resolver";
21
+ * import { generateSitemapXml } from "@proxima-io/storefront-core";
22
+ *
23
+ * export const GET: APIRoute = async () => {
24
+ * const website = await resolveWebsiteOnly();
25
+ * const xml = await generateSitemapXml(website, import.meta.env.PROXIMA_API_URL ?? "http://localhost:8000");
26
+ * return new Response(xml, {
27
+ * headers: { "Content-Type": "application/xml; charset=utf-8", "Cache-Control": "public, s-maxage=3600, stale-while-revalidate=86400" },
28
+ * });
29
+ * };
30
+ */
31
+ export declare function generateSitemapXml(website: SitemapWebsiteMeta, apiUrl: string, options?: {
32
+ pageSize?: number;
33
+ maxProducts?: number;
34
+ }): Promise<string>;
35
+ //# sourceMappingURL=sitemap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sitemap.d.ts","sourceRoot":"","sources":["../../src/seo/sitemap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAoD1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,kBAAkB,EAC3B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GACxD,OAAO,CAAC,MAAM,CAAC,CAqIjB"}
@@ -0,0 +1,186 @@
1
+ import { buildCanonicalUrl } from './hreflang.js';
2
+ import { fillPathTemplate, findWebsitePageByResolver, slugFromCatalogHref, } from './engine-url.js';
3
+ import { fetchBrandsDirectory, fetchCategoryNavTree, fetchStorefrontProducts, } from '../catalog/listings.js';
4
+ /** Private resolver kinds that must never appear in a sitemap */
5
+ const SITEMAP_PRIVATE_KINDS = new Set([
6
+ "cart",
7
+ "checkout",
8
+ "buyer_login",
9
+ "buyer_account",
10
+ "buyer_register",
11
+ "buyer_password_reset",
12
+ "order_list",
13
+ "order_detail",
14
+ "product_compare",
15
+ ]);
16
+ function _xmlEscape(str) {
17
+ return str
18
+ .replace(/&/g, "&amp;")
19
+ .replace(/</g, "&lt;")
20
+ .replace(/>/g, "&gt;")
21
+ .replace(/"/g, "&quot;")
22
+ .replace(/'/g, "&apos;");
23
+ }
24
+ function _urlEntry(loc, priority = "0.7", changefreq = "weekly", lastmod) {
25
+ const lines = [
26
+ ` <url>`,
27
+ ` <loc>${_xmlEscape(loc)}</loc>`,
28
+ ` <changefreq>${changefreq}</changefreq>`,
29
+ ` <priority>${priority}</priority>`,
30
+ ];
31
+ if (lastmod)
32
+ lines.push(` <lastmod>${lastmod}</lastmod>`);
33
+ lines.push(` </url>`);
34
+ return lines.join("\n");
35
+ }
36
+ /**
37
+ * Generate a complete `sitemap.xml` for a storefront.
38
+ *
39
+ * Includes:
40
+ * 1. Content pages from the website manifest (priority 1.0 for home, 0.8 for others)
41
+ * 2. Category pages from the recursive nav tree (priority 0.8)
42
+ * 3. Brand pages from the brands directory (priority 0.7)
43
+ * 4. Product pages — paginated up to `maxProducts` (priority 0.9)
44
+ *
45
+ * All entries use today's date as `lastmod`.
46
+ *
47
+ * @param website Resolved website object (domain + pages array)
48
+ * @param apiUrl Base URL of the Proxima API (e.g. `http://localhost:8000`)
49
+ * @param options Optional overrides: pageSize (default 60), maxProducts (default 3000)
50
+ *
51
+ * @example
52
+ * // apps/{slug}/src/pages/sitemap.xml.ts
53
+ * import type { APIRoute } from "astro";
54
+ * import { resolveWebsiteOnly } from "@/lib/resolver";
55
+ * import { generateSitemapXml } from "@proxima-io/storefront-core";
56
+ *
57
+ * export const GET: APIRoute = async () => {
58
+ * const website = await resolveWebsiteOnly();
59
+ * const xml = await generateSitemapXml(website, import.meta.env.PROXIMA_API_URL ?? "http://localhost:8000");
60
+ * return new Response(xml, {
61
+ * headers: { "Content-Type": "application/xml; charset=utf-8", "Cache-Control": "public, s-maxage=3600, stale-while-revalidate=86400" },
62
+ * });
63
+ * };
64
+ */
65
+ export async function generateSitemapXml(website, apiUrl, options = {}) {
66
+ const PAGE_SIZE = Math.min(60, options.pageSize ?? 60);
67
+ const MAX_PRODUCTS = options.maxProducts ?? 3000;
68
+ const MAX_PAGES = Math.ceil(MAX_PRODUCTS / PAGE_SIZE);
69
+ const TODAY = new Date().toISOString().split("T")[0];
70
+ const siteUrl = `https://${website.domain}`;
71
+ const defaultLocale = website.default_locale ?? "es";
72
+ const enabledLocales = website.enabled_locales?.length
73
+ ? website.enabled_locales
74
+ : [defaultLocale];
75
+ const entries = [];
76
+ function localizedTemplatesFor(resolverKind, fallbackTemplate) {
77
+ const page = findWebsitePageByResolver(website.pages, resolverKind);
78
+ const localizedPaths = page?.localized_paths ?? {};
79
+ const hasLocalized = Object.keys(localizedPaths).length > 0;
80
+ if (!hasLocalized) {
81
+ return [{ locale: defaultLocale, template: page?.path ?? fallbackTemplate }];
82
+ }
83
+ return enabledLocales
84
+ .map((locale) => ({
85
+ locale,
86
+ template: localizedPaths[locale] ?? localizedPaths[defaultLocale] ?? page?.path ?? fallbackTemplate,
87
+ }))
88
+ .filter((item) => Boolean(item.template));
89
+ }
90
+ function pushEngineUrls(resolverKind, fallbackTemplate, slug, priority, changefreq) {
91
+ for (const { locale, template } of localizedTemplatesFor(resolverKind, fallbackTemplate)) {
92
+ const logicalPath = template.includes('{')
93
+ ? fillPathTemplate(template, { slug })
94
+ : template;
95
+ const loc = buildCanonicalUrl(website.domain, locale, logicalPath, defaultLocale);
96
+ entries.push(_urlEntry(loc, priority, changefreq, TODAY));
97
+ }
98
+ }
99
+ // 1. Content pages from the website manifest
100
+ for (const page of website.pages ?? []) {
101
+ if (SITEMAP_PRIVATE_KINDS.has(page.resolver_kind))
102
+ continue;
103
+ if (page.resolver_kind !== "content_page")
104
+ continue;
105
+ const localizedPaths = page.localized_paths ?? {};
106
+ const localesToEmit = Object.keys(localizedPaths).length > 0
107
+ ? enabledLocales.filter((locale) => localizedPaths[locale] || page.path)
108
+ : [defaultLocale];
109
+ for (const locale of localesToEmit) {
110
+ const path = localizedPaths[locale] ?? page.path;
111
+ if (!path)
112
+ continue;
113
+ const priority = path === "/" ? "1.0" : "0.8";
114
+ const changefreq = path === "/" ? "daily" : "weekly";
115
+ const loc = buildCanonicalUrl(website.domain, locale, path, defaultLocale);
116
+ entries.push(_urlEntry(loc, priority, changefreq, TODAY));
117
+ }
118
+ }
119
+ // 2. Category pages (recursive nav tree)
120
+ try {
121
+ const tree = await fetchCategoryNavTree({ baseUrl: apiUrl }, website);
122
+ function collectHrefs(nodes) {
123
+ for (const node of nodes) {
124
+ const slug = slugFromCatalogHref(node.href, 'categoria') ??
125
+ slugFromCatalogHref(node.href, 'category') ??
126
+ node.href.split('/').filter(Boolean).pop();
127
+ if (slug) {
128
+ pushEngineUrls('category_detail', '/categoria/{slug}', slug, '0.8', 'daily');
129
+ }
130
+ else {
131
+ entries.push(_urlEntry(`${siteUrl}${node.href}`, '0.8', 'daily', TODAY));
132
+ }
133
+ if (node.children.length > 0)
134
+ collectHrefs(node.children);
135
+ }
136
+ }
137
+ collectHrefs(tree.nodes);
138
+ }
139
+ catch {
140
+ /* API offline — skip category URLs */
141
+ }
142
+ // 3. Brand pages
143
+ try {
144
+ const brandsResult = await fetchBrandsDirectory({ baseUrl: apiUrl }, website);
145
+ for (const brand of brandsResult.items) {
146
+ const slug = slugFromCatalogHref(brand.href, 'marca') ??
147
+ slugFromCatalogHref(brand.href, 'brand') ??
148
+ brand.slug;
149
+ if (slug) {
150
+ pushEngineUrls('brand_detail', '/marca/{slug}', slug, '0.7', 'weekly');
151
+ }
152
+ else {
153
+ entries.push(_urlEntry(`${siteUrl}${brand.href}`, '0.7', 'weekly', TODAY));
154
+ }
155
+ }
156
+ }
157
+ catch {
158
+ /* API offline — skip brand URLs */
159
+ }
160
+ // 4. Product pages (paginated)
161
+ try {
162
+ let currentPage = 1;
163
+ let totalPages = 1;
164
+ while (currentPage <= totalPages && currentPage <= MAX_PAGES) {
165
+ const result = await fetchStorefrontProducts({ baseUrl: apiUrl }, website, {
166
+ page: currentPage,
167
+ pageSize: PAGE_SIZE,
168
+ });
169
+ for (const product of result.items) {
170
+ pushEngineUrls('product_detail', '/producto/{slug}', product.slug, '0.9', 'weekly');
171
+ }
172
+ totalPages = result.pagination.total_pages;
173
+ currentPage++;
174
+ }
175
+ }
176
+ catch {
177
+ /* API offline — skip product URLs */
178
+ }
179
+ return [
180
+ '<?xml version="1.0" encoding="UTF-8"?>',
181
+ '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
182
+ ...entries,
183
+ "</urlset>",
184
+ ].join("\n");
185
+ }
186
+ //# sourceMappingURL=sitemap.js.map