@replohq/sdk 0.1.0

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 (280) hide show
  1. package/LICENSE +11 -0
  2. package/_vendor/replo-utils/lib/crypto.d.ts +30 -0
  3. package/_vendor/replo-utils/lib/errors.d.ts +83 -0
  4. package/_vendor/replo-utils/lib/json.d.ts +6 -0
  5. package/_vendor/replo-utils/lib/json.mjs +43 -0
  6. package/_vendor/replo-utils/lib/misc.d.ts +145 -0
  7. package/_vendor/replo-utils/lib/object.d.ts +31 -0
  8. package/_vendor/schemas/errors.d.ts +459 -0
  9. package/_vendor/schemas/generated/consent.d.ts +55 -0
  10. package/_vendor/schemas/generated/errors.d.ts +61 -0
  11. package/_vendor/schemas/loaderKeys.d.ts +1 -0
  12. package/_vendor/schemas/loaderKeys.mjs +27 -0
  13. package/_vendor/schemas/money.d.ts +60 -0
  14. package/_vendor/schemas/money.mjs +2051 -0
  15. package/analytics/analytics-provider.d.ts +16 -0
  16. package/analytics/analytics-provider.js +113 -0
  17. package/analytics/analytics-provider.js.map +7 -0
  18. package/analytics/analytics-sink.d.ts +26 -0
  19. package/analytics/analytics-sink.js +1 -0
  20. package/analytics/analytics-sink.js.map +7 -0
  21. package/analytics/get-analytics-sinks.d.ts +27 -0
  22. package/analytics/get-analytics-sinks.js +52 -0
  23. package/analytics/get-analytics-sinks.js.map +7 -0
  24. package/analytics/hooks/use-product-viewed-analytics.d.ts +23 -0
  25. package/analytics/hooks/use-product-viewed-analytics.js +34 -0
  26. package/analytics/hooks/use-product-viewed-analytics.js.map +7 -0
  27. package/analytics/replo-first-party-pixel.d.ts +12 -0
  28. package/analytics/replo-first-party-pixel.js +31 -0
  29. package/analytics/replo-first-party-pixel.js.map +7 -0
  30. package/analytics/replo-pixel-script.d.ts +13 -0
  31. package/analytics/replo-pixel-script.js +30 -0
  32. package/analytics/replo-pixel-script.js.map +7 -0
  33. package/analytics/sinks/ga4-sink.d.ts +15 -0
  34. package/analytics/sinks/ga4-sink.js +60 -0
  35. package/analytics/sinks/ga4-sink.js.map +7 -0
  36. package/analytics/sinks/meta-sink.d.ts +14 -0
  37. package/analytics/sinks/meta-sink.js +56 -0
  38. package/analytics/sinks/meta-sink.js.map +7 -0
  39. package/analytics/sinks/pinterest-sink.d.ts +14 -0
  40. package/analytics/sinks/pinterest-sink.js +76 -0
  41. package/analytics/sinks/pinterest-sink.js.map +7 -0
  42. package/analytics/sinks/reddit-sink.d.ts +14 -0
  43. package/analytics/sinks/reddit-sink.js +80 -0
  44. package/analytics/sinks/reddit-sink.js.map +7 -0
  45. package/analytics/sinks/replo-sink.d.ts +14 -0
  46. package/analytics/sinks/replo-sink.js +57 -0
  47. package/analytics/sinks/replo-sink.js.map +7 -0
  48. package/analytics/sinks/snapchat-sink.d.ts +14 -0
  49. package/analytics/sinks/snapchat-sink.js +67 -0
  50. package/analytics/sinks/snapchat-sink.js.map +7 -0
  51. package/analytics/sinks/tiktok-sink.d.ts +14 -0
  52. package/analytics/sinks/tiktok-sink.js +79 -0
  53. package/analytics/sinks/tiktok-sink.js.map +7 -0
  54. package/analytics/sinks/window-sink.d.ts +18 -0
  55. package/analytics/sinks/window-sink.js +46 -0
  56. package/analytics/sinks/window-sink.js.map +7 -0
  57. package/analytics/utils/analytics-utils.d.ts +7 -0
  58. package/analytics/utils/analytics-utils.js +130 -0
  59. package/analytics/utils/analytics-utils.js.map +7 -0
  60. package/analytics/utils/navigation-tracker.d.ts +16 -0
  61. package/analytics/utils/navigation-tracker.js +44 -0
  62. package/analytics/utils/navigation-tracker.js.map +7 -0
  63. package/analytics/utils/shopify-funnel-gate.d.ts +8 -0
  64. package/analytics/utils/shopify-funnel-gate.js +22 -0
  65. package/analytics/utils/shopify-funnel-gate.js.map +7 -0
  66. package/cart/buy-now-action.d.ts +21 -0
  67. package/cart/buy-now-action.js +104 -0
  68. package/cart/buy-now-action.js.map +7 -0
  69. package/cart/cart-actions.d.ts +63 -0
  70. package/cart/cart-actions.js +90 -0
  71. package/cart/cart-actions.js.map +7 -0
  72. package/cart/cart-loader.d.ts +9 -0
  73. package/cart/cart-loader.js +11 -0
  74. package/cart/cart-loader.js.map +7 -0
  75. package/cart/cart-provider.d.ts +85 -0
  76. package/cart/cart-provider.js +372 -0
  77. package/cart/cart-provider.js.map +7 -0
  78. package/cart/cart-schema.d.ts +74 -0
  79. package/cart/cart-schema.js +69 -0
  80. package/cart/cart-schema.js.map +7 -0
  81. package/cart/cart-types.d.ts +132 -0
  82. package/cart/cart-types.js +8 -0
  83. package/cart/cart-types.js.map +7 -0
  84. package/cart/gateways/cart-constants.d.ts +1 -0
  85. package/cart/gateways/cart-constants.js +5 -0
  86. package/cart/gateways/cart-constants.js.map +7 -0
  87. package/cart/gateways/cart-gateway.d.ts +10 -0
  88. package/cart/gateways/cart-gateway.js +19 -0
  89. package/cart/gateways/cart-gateway.js.map +7 -0
  90. package/cart/gateways/cookie-cart-gateway.d.ts +28 -0
  91. package/cart/gateways/cookie-cart-gateway.js +109 -0
  92. package/cart/gateways/cookie-cart-gateway.js.map +7 -0
  93. package/cart/gateways/default-cart-gateway.d.ts +19 -0
  94. package/cart/gateways/default-cart-gateway.js +30 -0
  95. package/cart/gateways/default-cart-gateway.js.map +7 -0
  96. package/cart/gateways/resolver-cart-gateway.d.ts +2 -0
  97. package/cart/gateways/resolver-cart-gateway.js +109 -0
  98. package/cart/gateways/resolver-cart-gateway.js.map +7 -0
  99. package/cart/gateways/stripe-cart-gateway.d.ts +19 -0
  100. package/cart/gateways/stripe-cart-gateway.js +28 -0
  101. package/cart/gateways/stripe-cart-gateway.js.map +7 -0
  102. package/cart/hooks/use-add-to-cart.d.ts +63 -0
  103. package/cart/hooks/use-add-to-cart.js +124 -0
  104. package/cart/hooks/use-add-to-cart.js.map +7 -0
  105. package/cart/hooks/use-buy-now.d.ts +49 -0
  106. package/cart/hooks/use-buy-now.js +62 -0
  107. package/cart/hooks/use-buy-now.js.map +7 -0
  108. package/cart/utils/cart-utils.d.ts +43 -0
  109. package/cart/utils/cart-utils.js +46 -0
  110. package/cart/utils/cart-utils.js.map +7 -0
  111. package/cart/utils/cookie-cart-persistence.d.ts +55 -0
  112. package/cart/utils/cookie-cart-persistence.js +166 -0
  113. package/cart/utils/cookie-cart-persistence.js.map +7 -0
  114. package/cart/utils/variant-to-cart-line.d.ts +74 -0
  115. package/cart/utils/variant-to-cart-line.js +162 -0
  116. package/cart/utils/variant-to-cart-line.js.map +7 -0
  117. package/chunk-UJCSKKID.mjs +27 -0
  118. package/consent/consent-action.d.ts +9 -0
  119. package/consent/consent-action.js +25 -0
  120. package/consent/consent-action.js.map +7 -0
  121. package/consent/consent-actions.d.ts +28 -0
  122. package/consent/consent-actions.js +36 -0
  123. package/consent/consent-actions.js.map +7 -0
  124. package/consent/consent-store.d.ts +69 -0
  125. package/consent/consent-store.js +194 -0
  126. package/consent/consent-store.js.map +7 -0
  127. package/consent/cookie.d.ts +36 -0
  128. package/consent/cookie.js +82 -0
  129. package/consent/cookie.js.map +7 -0
  130. package/consent/replo-scripts.d.ts +23 -0
  131. package/consent/replo-scripts.js +102 -0
  132. package/consent/replo-scripts.js.map +7 -0
  133. package/consent/script-registration-store.d.ts +25 -0
  134. package/consent/script-registration-store.js +61 -0
  135. package/consent/script-registration-store.js.map +7 -0
  136. package/consent/script-snippets.d.ts +24 -0
  137. package/consent/script-snippets.js +122 -0
  138. package/consent/script-snippets.js.map +7 -0
  139. package/consent/types.d.ts +34 -0
  140. package/consent/types.js +1 -0
  141. package/consent/types.js.map +7 -0
  142. package/consent/window-api.d.ts +41 -0
  143. package/consent/window-api.js +73 -0
  144. package/consent/window-api.js.map +7 -0
  145. package/env.d.ts +9 -0
  146. package/env.js +42 -0
  147. package/env.js.map +7 -0
  148. package/gateways/canopy-api.d.ts +83 -0
  149. package/gateways/canopy-api.js +197 -0
  150. package/gateways/canopy-api.js.map +7 -0
  151. package/hooks/use-client-value.d.ts +10 -0
  152. package/hooks/use-client-value.js +12 -0
  153. package/hooks/use-client-value.js.map +7 -0
  154. package/hooks/use-formatted-price.d.ts +15 -0
  155. package/hooks/use-formatted-price.js +20 -0
  156. package/hooks/use-formatted-price.js.map +7 -0
  157. package/lib/canopy-error.d.ts +25 -0
  158. package/lib/canopy-error.js +24 -0
  159. package/lib/canopy-error.js.map +7 -0
  160. package/lib/get-query-client.d.ts +2 -0
  161. package/lib/get-query-client.js +34 -0
  162. package/lib/get-query-client.js.map +7 -0
  163. package/lib/integration-utils.d.ts +2 -0
  164. package/lib/integration-utils.js +9 -0
  165. package/lib/integration-utils.js.map +7 -0
  166. package/lib/shopify-utils.d.ts +10 -0
  167. package/lib/shopify-utils.js +7 -0
  168. package/lib/shopify-utils.js.map +7 -0
  169. package/lib/url-utils.d.ts +4 -0
  170. package/lib/url-utils.js +8 -0
  171. package/lib/url-utils.js.map +7 -0
  172. package/loaders/collection-loader.d.ts +11 -0
  173. package/loaders/collection-loader.js +55 -0
  174. package/loaders/collection-loader.js.map +7 -0
  175. package/loaders/collection-products-loader.d.ts +12 -0
  176. package/loaders/collection-products-loader.js +58 -0
  177. package/loaders/collection-products-loader.js.map +7 -0
  178. package/loaders/contentful-entries-loader.d.ts +17 -0
  179. package/loaders/contentful-entries-loader.js +78 -0
  180. package/loaders/contentful-entries-loader.js.map +7 -0
  181. package/loaders/contentful-entry-loader.d.ts +14 -0
  182. package/loaders/contentful-entry-loader.js +60 -0
  183. package/loaders/contentful-entry-loader.js.map +7 -0
  184. package/loaders/invoke-loader-action.d.ts +11 -0
  185. package/loaders/invoke-loader-action.js +14 -0
  186. package/loaders/invoke-loader-action.js.map +7 -0
  187. package/loaders/invoke-loader-server.d.ts +18 -0
  188. package/loaders/invoke-loader-server.js +40 -0
  189. package/loaders/invoke-loader-server.js.map +7 -0
  190. package/loaders/invoke-loader.d.ts +16 -0
  191. package/loaders/invoke-loader.js +37 -0
  192. package/loaders/invoke-loader.js.map +7 -0
  193. package/loaders/levanta-product-loader.d.ts +11 -0
  194. package/loaders/levanta-product-loader.js +53 -0
  195. package/loaders/levanta-product-loader.js.map +7 -0
  196. package/loaders/loader-error-boundary.d.ts +25 -0
  197. package/loaders/loader-error-boundary.js +24 -0
  198. package/loaders/loader-error-boundary.js.map +7 -0
  199. package/loaders/loader-keys.d.ts +34 -0
  200. package/loaders/loader-keys.js +26 -0
  201. package/loaders/loader-keys.js.map +7 -0
  202. package/loaders/loader-schemas.d.ts +946 -0
  203. package/loaders/loader-schemas.js +353 -0
  204. package/loaders/loader-schemas.js.map +7 -0
  205. package/loaders/metaobject-loader.d.ts +12 -0
  206. package/loaders/metaobject-loader.js +54 -0
  207. package/loaders/metaobject-loader.js.map +7 -0
  208. package/loaders/metaobjects-loader.d.ts +14 -0
  209. package/loaders/metaobjects-loader.js +60 -0
  210. package/loaders/metaobjects-loader.js.map +7 -0
  211. package/loaders/okendo-review-aggregate-loader.d.ts +17 -0
  212. package/loaders/okendo-review-aggregate-loader.js +56 -0
  213. package/loaders/okendo-review-aggregate-loader.js.map +7 -0
  214. package/loaders/okendo-reviews-loader.d.ts +23 -0
  215. package/loaders/okendo-reviews-loader.js +54 -0
  216. package/loaders/okendo-reviews-loader.js.map +7 -0
  217. package/loaders/prefetch-loaders.d.ts +33 -0
  218. package/loaders/prefetch-loaders.js +46 -0
  219. package/loaders/prefetch-loaders.js.map +7 -0
  220. package/loaders/preview-placeholder.d.ts +25 -0
  221. package/loaders/preview-placeholder.js +222 -0
  222. package/loaders/preview-placeholder.js.map +7 -0
  223. package/loaders/product-loader-args.d.ts +21 -0
  224. package/loaders/product-loader-args.js +17 -0
  225. package/loaders/product-loader-args.js.map +7 -0
  226. package/loaders/product-loader.d.ts +10 -0
  227. package/loaders/product-loader.js +52 -0
  228. package/loaders/product-loader.js.map +7 -0
  229. package/loaders/rebuy-recommendations-loader.d.ts +165 -0
  230. package/loaders/rebuy-recommendations-loader.js +57 -0
  231. package/loaders/rebuy-recommendations-loader.js.map +7 -0
  232. package/loaders/reviews-io-review-aggregate-loader.d.ts +12 -0
  233. package/loaders/reviews-io-review-aggregate-loader.js +43 -0
  234. package/loaders/reviews-io-review-aggregate-loader.js.map +7 -0
  235. package/loaders/reviews-io-reviews-loader.d.ts +14 -0
  236. package/loaders/reviews-io-reviews-loader.js +65 -0
  237. package/loaders/reviews-io-reviews-loader.js.map +7 -0
  238. package/loaders/smile-earning-rules-loader.d.ts +19 -0
  239. package/loaders/smile-earning-rules-loader.js +44 -0
  240. package/loaders/smile-earning-rules-loader.js.map +7 -0
  241. package/loaders/smile-rewards-loader.d.ts +19 -0
  242. package/loaders/smile-rewards-loader.js +44 -0
  243. package/loaders/smile-rewards-loader.js.map +7 -0
  244. package/loaders/smile-vip-tiers-loader.d.ts +18 -0
  245. package/loaders/smile-vip-tiers-loader.js +43 -0
  246. package/loaders/smile-vip-tiers-loader.js.map +7 -0
  247. package/loaders/statsig-experiment-loader.d.ts +20 -0
  248. package/loaders/statsig-experiment-loader.js +81 -0
  249. package/loaders/statsig-experiment-loader.js.map +7 -0
  250. package/loaders/yotpo-review-aggregate-loader.d.ts +12 -0
  251. package/loaders/yotpo-review-aggregate-loader.js +50 -0
  252. package/loaders/yotpo-review-aggregate-loader.js.map +7 -0
  253. package/loaders/yotpo-reviews-loader.d.ts +14 -0
  254. package/loaders/yotpo-reviews-loader.js +65 -0
  255. package/loaders/yotpo-reviews-loader.js.map +7 -0
  256. package/money.d.ts +9 -0
  257. package/money.js +13 -0
  258. package/money.js.map +7 -0
  259. package/next/config.d.ts +4 -0
  260. package/next/config.js +91 -0
  261. package/next/config.js.map +7 -0
  262. package/next/image-loader.d.ts +2 -0
  263. package/next/image-loader.js +52 -0
  264. package/next/image-loader.js.map +7 -0
  265. package/next/open-next.d.ts +2 -0
  266. package/next/open-next.js +24 -0
  267. package/next/open-next.js.map +7 -0
  268. package/orders/order-status-schema.d.ts +161 -0
  269. package/orders/order-status-schema.js +83 -0
  270. package/orders/order-status-schema.js.map +7 -0
  271. package/orders/order-status.d.ts +11 -0
  272. package/orders/order-status.js +41 -0
  273. package/orders/order-status.js.map +7 -0
  274. package/package.json +216 -0
  275. package/providers/query-provider.d.ts +1 -0
  276. package/providers/query-provider.js +12 -0
  277. package/providers/query-provider.js.map +7 -0
  278. package/providers/replo-provider.d.ts +1 -0
  279. package/providers/replo-provider.js +22 -0
  280. package/providers/replo-provider.js.map +7 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../cart/utils/cookie-cart-persistence.ts"],
4
+ "sourcesContent": ["\"use server\";\n\nimport type {\n AddToCartPayload,\n Cart,\n CartLine,\n CartLinePayload,\n RemoveFromCartPayload,\n UpdateCartLineItemPayload,\n} from \"../cart-types\";\n\nimport { cookies } from \"next/headers\";\n\nimport { calculateLineItemsSubtotal } from \"./cart-utils\";\n\nconst CART_COOKIE_MAX_AGE = 60 * 60 * 24 * 30; // 30 days\n\n// NOTE (Jasmine, 2026-07-22): Cookie carts are their own source of truth (no\n// server roundtrip), so recompute cost from line items on every mutation.\n// Without this the persisted cost stays at its initial 0 and every consumer\n// that trusts it (cart total, checkout analytics) reports $0 for a priced cart.\n// Cookie carts apply no cart-level discounts, so subtotal equals total.\nfunction recalculateCartCost(cart: Cart): Cart {\n const subtotal = calculateLineItemsSubtotal(cart.lines);\n return {\n ...cart,\n cost: {\n ...cart.cost,\n subtotalAmount: subtotal,\n totalAmount: subtotal,\n },\n };\n}\n\n/**\n * Line merge function type - returns index of existing line to merge with, or -1 to add as new line\n */\nexport type LineMergeFn = (\n existingLines: CartLine[],\n newLine: CartLinePayload,\n) => number;\n\n/**\n * Options for creating a cart with persistence\n */\ninterface CreateCartOptions {\n cookieName: string;\n lines?: CartLinePayload[];\n lineIdGenerator: () => string;\n cartIdGenerator: () => string;\n}\n\n/**\n * Options for adding to cart with persistence\n */\ninterface AddToCartOptions {\n cookieName: string;\n mergeFn: LineMergeFn;\n lineIdGenerator: () => string;\n}\n\n/**\n * Save cart to cookie\n */\nexport async function saveCartToCookie(cart: Cart, cookieName: string) {\n const cookieStore = await cookies();\n cookieStore.set(cookieName, JSON.stringify(cart), {\n maxAge: CART_COOKIE_MAX_AGE,\n httpOnly: true,\n secure: process.env.NODE_ENV === \"production\",\n sameSite: \"lax\",\n path: \"/\",\n });\n}\n\n/**\n * Load cart from cookie\n */\nexport async function loadCartFromCookie(\n cookieName: string,\n): Promise<Cart | null> {\n const cookieStore = await cookies();\n const cartCookie = cookieStore.get(cookieName);\n\n if (!cartCookie?.value) {\n return null;\n }\n\n try {\n // Legacy cast - may or may not be necessary\n // eslint-disable-next-line replo/no-as-cast\n return JSON.parse(cartCookie.value) as Cart;\n } catch (error) {\n console.warn(\"Failed to parse cart cookie:\", error);\n await clearCartCookie(cookieName);\n return null;\n }\n}\n\n/**\n * Clear cart cookie\n */\nexport async function clearCartCookie(cookieName: string) {\n const cookieStore = await cookies();\n cookieStore.delete(cookieName);\n}\n\n/**\n * Update cart in cookie atomically (load-transform-save pattern)\n */\nexport async function updateCartInCookie(\n cartId: string,\n cookieName: string,\n updater: (cart: Cart) => Cart,\n): Promise<Cart | null> {\n const existingCart = await loadCartFromCookie(cookieName);\n\n if (!existingCart || existingCart.id !== cartId) {\n return null;\n }\n\n const updatedCart = recalculateCartCost(updater(existingCart));\n\n await saveCartToCookie(updatedCart, cookieName);\n\n return updatedCart;\n}\n\n/**\n * Create a new cart with persistence\n */\nexport async function createCartWithPersistence(\n options: CreateCartOptions,\n): Promise<Cart | null> {\n const cartId = options.cartIdGenerator();\n\n const cart: Cart = {\n id: cartId,\n lines: [],\n cost: {\n subtotalAmount: 0,\n totalAmount: 0,\n currencyCode: \"USD\",\n },\n checkoutUrl: \"/checkout\",\n };\n\n for (const line of options.lines ?? []) {\n cart.lines.push(\n convertCartLinePayloadToCartLine(line, options.lineIdGenerator),\n );\n }\n\n const cartWithCost = recalculateCartCost(cart);\n await saveCartToCookie(cartWithCost, options.cookieName);\n return cartWithCost;\n}\n\n/**\n * Add lines to cart with configurable line merging strategy\n */\nexport async function addToCartWithPersistence(\n payload: AddToCartPayload,\n options: AddToCartOptions,\n): Promise<Cart | null> {\n return await updateCartInCookie(\n payload.cartId,\n options.cookieName,\n (cart) => {\n for (const linePayload of payload.lines) {\n const existingLineIndex = options.mergeFn(cart.lines, linePayload);\n\n if (existingLineIndex >= 0) {\n cart.lines[existingLineIndex]!.quantity += linePayload.quantity;\n } else {\n const newLine: CartLine = convertCartLinePayloadToCartLine(\n linePayload,\n options.lineIdGenerator,\n );\n cart.lines.push(newLine);\n }\n }\n\n return cart;\n },\n );\n}\n\n/**\n * Update cart line item\n */\nexport async function updateCartLineItemWithPersistence(\n payload: UpdateCartLineItemPayload,\n cookieName: string,\n): Promise<Cart | null> {\n return await updateCartInCookie(payload.cartId, cookieName, (cart) => {\n const lineIndex = cart.lines.findIndex(\n (line) => line.id === payload.line.id,\n );\n\n if (lineIndex >= 0) {\n if (payload.line.quantity <= 0) {\n cart.lines.splice(lineIndex, 1);\n } else {\n cart.lines[lineIndex]!.quantity = payload.line.quantity;\n if (payload.line.properties) {\n cart.lines[lineIndex]!.attributes = payload.line.properties;\n }\n if (payload.line.merchandise) {\n cart.lines[lineIndex]!.merchandise = payload.line.merchandise;\n }\n }\n }\n\n return cart;\n });\n}\n\n/**\n * Remove cart line items\n */\nexport async function removeCartLineItemsWithPersistence(\n payload: RemoveFromCartPayload,\n cookieName: string,\n): Promise<Cart | null> {\n return await updateCartInCookie(payload.cartId, cookieName, (cart) => {\n const removeSet = new Set(payload.lineIds);\n cart.lines = cart.lines.filter((line) => !removeSet.has(line.id));\n return cart;\n });\n}\n\n/**\n * Convert CartLinePayload to CartLine with custom ID generator\n */\nfunction convertCartLinePayloadToCartLine(\n linePayload: CartLinePayload,\n lineIdGenerator: () => string,\n): CartLine {\n // Cookie-persisted carts (Stripe/default) are their own source of truth \u2014\n // there's no server roundtrip to backfill merchandise later. Fall back to a\n // placeholder when the caller didn't pass merchandise so the persisted line\n // is renderable; otherwise an unresolvable line would render as a skeleton\n // forever. Shopify carts hit a different code path and use the skeleton\n // treatment in cart-provider.tsx.\n const fallbackMerchandise = {\n id: linePayload.id,\n product: {\n id: linePayload.merchandise?.product.id ?? linePayload.id,\n title: linePayload.merchandise?.product.title ?? \"Product\",\n },\n title: linePayload.merchandise?.title ?? \"Product Variant\",\n price: linePayload.merchandise?.price ?? 0,\n compareAtPrice: linePayload.merchandise?.compareAtPrice ?? null,\n image: linePayload.merchandise?.image\n ? {\n url: linePayload.merchandise.image.url,\n altText: linePayload.merchandise.image.altText,\n }\n : null,\n selectedOptions: linePayload.merchandise?.selectedOptions ?? [],\n };\n\n return {\n id: lineIdGenerator(),\n quantity: linePayload.quantity,\n merchandise: linePayload.merchandise ?? fallbackMerchandise,\n attributes: linePayload.properties ?? [],\n sellingPlanAllocation: linePayload.sellingPlanId\n ? {\n sellingPlan: {\n id: linePayload.sellingPlanId,\n name: \"Subscription Plan\",\n description: null,\n },\n priceAdjustments: [],\n checkoutChargeAmount: { amount: 0, currencyCode: \"USD\" },\n remainingBalanceChargeAmount: { amount: 0, currencyCode: \"USD\" },\n }\n : null,\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,74 @@
1
+ import type { CartLine, CartLineMerchandise, SellingPlanAllocation } from "../cart-types";
2
+ /**
3
+ * Gets a specific attribute value from a cart line.
4
+ *
5
+ * @param cartLine - The cart line to get the attribute from
6
+ * @param key - The attribute key to retrieve
7
+ * @returns The attribute value, or undefined if not found
8
+ */
9
+ export declare function getCartLineAttribute(cartLine: CartLine, key: string): string | number | boolean | undefined;
10
+ /**
11
+ * Calculates pricing information for a cart line, including discounts and
12
+ * selling plan adjustments.
13
+ *
14
+ * @param cartLine - The cart line to calculate pricing for
15
+ * @returns Object containing compareAtPrice, discountPercentage, hasDiscount, sellingPlanAdjustedPrice, and finalPrice
16
+ */
17
+ export declare function getCartLinePricing(cartLine: CartLine): {
18
+ compareAtPrice: number;
19
+ discountPercentage: number;
20
+ hasDiscount: boolean;
21
+ sellingPlanAdjustedPrice: number;
22
+ finalPrice: number;
23
+ };
24
+ /**
25
+ * Extracts bundle information from cart line attributes.
26
+ *
27
+ * @param cartLine - The cart line to get bundle info from
28
+ * @returns Object containing bundle ID, discount, and whether it's a bundle item
29
+ */
30
+ export declare function getCartLineBundleInfo(cartLine: CartLine): {
31
+ bundleId: string;
32
+ bundleDiscount: number;
33
+ isBundleItem: boolean;
34
+ };
35
+ /**
36
+ * Extracts special item information from cart line attributes.
37
+ *
38
+ * @param cartLine - The cart line to get special info from
39
+ * @returns Object containing whether it's a special item and any special instructions
40
+ */
41
+ export declare function getCartLineSpecialInfo(cartLine: CartLine): {
42
+ isSpecialItem: boolean;
43
+ specialInstructions: string[];
44
+ };
45
+ /**
46
+ * Extracts subscription (selling plan) information from a cart line.
47
+ *
48
+ * @param cartLine - The cart line to get subscription info from
49
+ * @returns Object containing isSubscription, frequency, discount, and sellingPlan
50
+ */
51
+ export declare function getCartLineSubscriptionInfo(cartLine: CartLine): {
52
+ isSubscription: boolean;
53
+ frequency: string;
54
+ discount: number;
55
+ sellingPlan: {
56
+ id: string;
57
+ name: string;
58
+ description: string | null;
59
+ };
60
+ } | {
61
+ isSubscription: boolean;
62
+ frequency: null;
63
+ discount: number;
64
+ sellingPlan: null;
65
+ };
66
+ /**
67
+ * Creates an optimistic selling plan allocation using product data.
68
+ * This matches the exact structure that Shopify returns so the UI doesn't need conditional logic.
69
+ */
70
+ export declare function createOptimisticSellingPlanAllocation({ merchandise, sellingPlanId, currencyCode, }: {
71
+ merchandise: CartLineMerchandise;
72
+ sellingPlanId: string | null;
73
+ currencyCode?: string;
74
+ }): SellingPlanAllocation | null;
@@ -0,0 +1,162 @@
1
+ import { fromMajorUnitsToMinorUnits } from "../../_vendor/schemas/money.mjs";
2
+ function applyPriceAdjustment(variantPrice, adjustment, currencyCode) {
3
+ switch (adjustment.valueType) {
4
+ // NOTE (Max, 2026-08-03): variantPrice is integer minor units, but the
5
+ // loader emits money-typed adjustments as decimal major units (its
6
+ // storefront contract), so the price/fixedAmount arms convert here at the
7
+ // cart boundary. Percentage is unitless and needs no conversion.
8
+ case "price":
9
+ return fromMajorUnitsToMinorUnits({
10
+ amount: adjustment.value,
11
+ currencyCode
12
+ });
13
+ case "percentage":
14
+ return Math.max(
15
+ 0,
16
+ Math.round(variantPrice * (1 - adjustment.value / 100))
17
+ );
18
+ case "fixedAmount":
19
+ return Math.max(
20
+ 0,
21
+ variantPrice - fromMajorUnitsToMinorUnits({
22
+ amount: adjustment.value,
23
+ currencyCode
24
+ })
25
+ );
26
+ }
27
+ }
28
+ function getCartLineAttribute(cartLine, key) {
29
+ return cartLine.attributes.find((attr) => attr.key === key)?.value;
30
+ }
31
+ function getCartLinePricing(cartLine) {
32
+ if (!cartLine.merchandise) {
33
+ return {
34
+ compareAtPrice: 0,
35
+ discountPercentage: 0,
36
+ hasDiscount: false,
37
+ sellingPlanAdjustedPrice: 0,
38
+ finalPrice: 0
39
+ };
40
+ }
41
+ const compareAtPrice = cartLine.merchandise.compareAtPrice ?? 0;
42
+ const currentPrice = cartLine.merchandise.price;
43
+ const discountPercentage = compareAtPrice > currentPrice ? (compareAtPrice - currentPrice) / compareAtPrice * 100 : 0;
44
+ const hasDiscount = compareAtPrice > currentPrice;
45
+ let sellingPlanAdjustedPrice = cartLine.merchandise.price;
46
+ if (cartLine.sellingPlanAllocation && cartLine.sellingPlanAllocation.priceAdjustments.length > 0 && cartLine.sellingPlanAllocation.priceAdjustments[0]?.price) {
47
+ sellingPlanAdjustedPrice = cartLine.sellingPlanAllocation.priceAdjustments[0].price.amount;
48
+ }
49
+ return {
50
+ compareAtPrice,
51
+ discountPercentage,
52
+ hasDiscount,
53
+ sellingPlanAdjustedPrice,
54
+ finalPrice: sellingPlanAdjustedPrice
55
+ };
56
+ }
57
+ function getCartLineBundleInfo(cartLine) {
58
+ const bundleId = getCartLineAttribute(cartLine, "bundleId");
59
+ const bundleDiscount = Number(
60
+ getCartLineAttribute(cartLine, "bundleDiscount") ?? 0
61
+ );
62
+ const isBundleItem = getCartLineAttribute(
63
+ cartLine,
64
+ "isBundleItem"
65
+ );
66
+ return {
67
+ bundleId: bundleId ?? null,
68
+ bundleDiscount,
69
+ isBundleItem: isBundleItem ?? false
70
+ };
71
+ }
72
+ function getCartLineSpecialInfo(cartLine) {
73
+ const isSpecialItem = getCartLineAttribute(
74
+ cartLine,
75
+ "isSpecialItem"
76
+ );
77
+ const specialInstructions = getCartLineAttribute(
78
+ cartLine,
79
+ "specialInstructions"
80
+ );
81
+ return {
82
+ isSpecialItem: isSpecialItem ?? false,
83
+ specialInstructions: specialInstructions ? specialInstructions.split("|") : []
84
+ };
85
+ }
86
+ function getCartLineSubscriptionInfo(cartLine) {
87
+ if (cartLine.sellingPlanAllocation) {
88
+ return {
89
+ isSubscription: true,
90
+ frequency: cartLine.sellingPlanAllocation.sellingPlan.name,
91
+ discount: 0,
92
+ // Shopify calculates this from price adjustments when available
93
+ sellingPlan: cartLine.sellingPlanAllocation.sellingPlan
94
+ };
95
+ }
96
+ return {
97
+ isSubscription: false,
98
+ frequency: null,
99
+ discount: 0,
100
+ sellingPlan: null
101
+ };
102
+ }
103
+ function createOptimisticSellingPlanAllocation({
104
+ merchandise,
105
+ sellingPlanId,
106
+ currencyCode = "USD"
107
+ }) {
108
+ const { product, price: variantPrice } = merchandise;
109
+ if (!sellingPlanId || !product.sellingPlanGroups) {
110
+ return null;
111
+ }
112
+ for (const group of product.sellingPlanGroups) {
113
+ const sellingPlan = group.sellingPlans.find(
114
+ (sp) => sp.id === sellingPlanId
115
+ );
116
+ if (sellingPlan) {
117
+ return {
118
+ sellingPlan: {
119
+ id: sellingPlan.id,
120
+ name: sellingPlan.name,
121
+ description: sellingPlan.description
122
+ },
123
+ priceAdjustments: sellingPlan.priceAdjustments.map((adj) => {
124
+ const adjustedPrice = variantPrice !== void 0 ? applyPriceAdjustment(variantPrice, adj, currencyCode) : 0;
125
+ return {
126
+ compareAtPrice: {
127
+ amount: variantPrice ?? 0,
128
+ currencyCode
129
+ },
130
+ perDeliveryPrice: {
131
+ amount: adjustedPrice,
132
+ currencyCode
133
+ },
134
+ price: {
135
+ amount: adjustedPrice,
136
+ currencyCode
137
+ },
138
+ unitPrice: null
139
+ };
140
+ }),
141
+ checkoutChargeAmount: {
142
+ amount: 0,
143
+ currencyCode
144
+ },
145
+ remainingBalanceChargeAmount: {
146
+ amount: 0,
147
+ currencyCode
148
+ }
149
+ };
150
+ }
151
+ }
152
+ return null;
153
+ }
154
+ export {
155
+ createOptimisticSellingPlanAllocation,
156
+ getCartLineAttribute,
157
+ getCartLineBundleInfo,
158
+ getCartLinePricing,
159
+ getCartLineSpecialInfo,
160
+ getCartLineSubscriptionInfo
161
+ };
162
+ //# sourceMappingURL=variant-to-cart-line.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../cart/utils/variant-to-cart-line.ts"],
4
+ "sourcesContent": ["import type {\n CartLine,\n CartLineAttribute,\n CartLineMerchandise,\n SellingPlanAllocation,\n} from \"../cart-types\";\n\nimport { fromMajorUnitsToMinorUnits } from \"schemas/money\";\n\n// TODO: Temporary \u2014 inlined from removed features/product/utils/pricing-utils. Keep until cart uses canonical product/loader types.\nfunction applyPriceAdjustment(\n variantPrice: number,\n adjustment: {\n value: number;\n valueType: \"percentage\" | \"fixedAmount\" | \"price\";\n },\n currencyCode: string,\n): number {\n switch (adjustment.valueType) {\n // NOTE (Max, 2026-08-03): variantPrice is integer minor units, but the\n // loader emits money-typed adjustments as decimal major units (its\n // storefront contract), so the price/fixedAmount arms convert here at the\n // cart boundary. Percentage is unitless and needs no conversion.\n case \"price\":\n return fromMajorUnitsToMinorUnits({\n amount: adjustment.value,\n currencyCode,\n });\n case \"percentage\":\n return Math.max(\n 0,\n Math.round(variantPrice * (1 - adjustment.value / 100)),\n );\n case \"fixedAmount\":\n return Math.max(\n 0,\n variantPrice -\n fromMajorUnitsToMinorUnits({\n amount: adjustment.value,\n currencyCode,\n }),\n );\n }\n}\n\n// === Cart Line Utilities ===\n\n/**\n * Gets a specific attribute value from a cart line.\n *\n * @param cartLine - The cart line to get the attribute from\n * @param key - The attribute key to retrieve\n * @returns The attribute value, or undefined if not found\n */\nexport function getCartLineAttribute(\n cartLine: CartLine,\n key: string,\n): string | number | boolean | undefined {\n return cartLine.attributes.find((attr: CartLineAttribute) => attr.key === key)\n ?.value;\n}\n\n/**\n * Calculates pricing information for a cart line, including discounts and\n * selling plan adjustments.\n *\n * @param cartLine - The cart line to calculate pricing for\n * @returns Object containing compareAtPrice, discountPercentage, hasDiscount, sellingPlanAdjustedPrice, and finalPrice\n */\nexport function getCartLinePricing(cartLine: CartLine) {\n if (!cartLine.merchandise) {\n return {\n compareAtPrice: 0,\n discountPercentage: 0,\n hasDiscount: false,\n sellingPlanAdjustedPrice: 0,\n finalPrice: 0,\n };\n }\n\n const compareAtPrice = cartLine.merchandise.compareAtPrice ?? 0;\n const currentPrice = cartLine.merchandise.price;\n const discountPercentage =\n compareAtPrice > currentPrice\n ? ((compareAtPrice - currentPrice) / compareAtPrice) * 100\n : 0;\n const hasDiscount = compareAtPrice > currentPrice;\n\n let sellingPlanAdjustedPrice = cartLine.merchandise.price;\n if (\n cartLine.sellingPlanAllocation &&\n cartLine.sellingPlanAllocation.priceAdjustments.length > 0 &&\n cartLine.sellingPlanAllocation.priceAdjustments[0]?.price\n ) {\n // Use the price from the first price adjustment, which represents the selling plan price\n sellingPlanAdjustedPrice =\n cartLine.sellingPlanAllocation.priceAdjustments[0]!.price.amount;\n }\n\n return {\n compareAtPrice,\n discountPercentage,\n hasDiscount: hasDiscount,\n sellingPlanAdjustedPrice,\n finalPrice: sellingPlanAdjustedPrice,\n };\n}\n\n/**\n * Extracts bundle information from cart line attributes.\n *\n * @param cartLine - The cart line to get bundle info from\n * @returns Object containing bundle ID, discount, and whether it's a bundle item\n */\nexport function getCartLineBundleInfo(cartLine: CartLine) {\n // Legacy cast - may or may not be necessary\n // eslint-disable-next-line replo/no-as-cast\n const bundleId = getCartLineAttribute(cartLine, \"bundleId\") as string;\n const bundleDiscount = Number(\n getCartLineAttribute(cartLine, \"bundleDiscount\") ?? 0,\n );\n // Legacy cast - may or may not be necessary\n // eslint-disable-next-line replo/no-as-cast\n const isBundleItem = getCartLineAttribute(\n cartLine,\n \"isBundleItem\",\n ) as boolean;\n\n return {\n bundleId: bundleId ?? null,\n bundleDiscount,\n isBundleItem: isBundleItem ?? false,\n };\n}\n\n/**\n * Extracts special item information from cart line attributes.\n *\n * @param cartLine - The cart line to get special info from\n * @returns Object containing whether it's a special item and any special instructions\n */\nexport function getCartLineSpecialInfo(cartLine: CartLine) {\n // Legacy cast - may or may not be necessary\n // eslint-disable-next-line replo/no-as-cast\n const isSpecialItem = getCartLineAttribute(\n cartLine,\n \"isSpecialItem\",\n ) as boolean;\n // Legacy cast - may or may not be necessary\n // eslint-disable-next-line replo/no-as-cast\n const specialInstructions = getCartLineAttribute(\n cartLine,\n \"specialInstructions\",\n ) as string;\n\n return {\n isSpecialItem: isSpecialItem ?? false,\n specialInstructions: specialInstructions\n ? specialInstructions.split(\"|\")\n : [],\n };\n}\n\n/**\n * Extracts subscription (selling plan) information from a cart line.\n *\n * @param cartLine - The cart line to get subscription info from\n * @returns Object containing isSubscription, frequency, discount, and sellingPlan\n */\nexport function getCartLineSubscriptionInfo(cartLine: CartLine) {\n if (cartLine.sellingPlanAllocation) {\n return {\n isSubscription: true,\n frequency: cartLine.sellingPlanAllocation.sellingPlan.name,\n discount: 0, // Shopify calculates this from price adjustments when available\n sellingPlan: cartLine.sellingPlanAllocation.sellingPlan,\n };\n }\n\n // No subscription if no sellingPlanAllocation\n return {\n isSubscription: false,\n frequency: null,\n discount: 0,\n sellingPlan: null,\n };\n}\n\n/**\n * Creates an optimistic selling plan allocation using product data.\n * This matches the exact structure that Shopify returns so the UI doesn't need conditional logic.\n */\nexport function createOptimisticSellingPlanAllocation({\n merchandise,\n sellingPlanId,\n currencyCode = \"USD\",\n}: {\n merchandise: CartLineMerchandise;\n sellingPlanId: string | null;\n currencyCode?: string;\n}): SellingPlanAllocation | null {\n const { product, price: variantPrice } = merchandise;\n\n if (!sellingPlanId || !product.sellingPlanGroups) {\n return null;\n }\n\n // Find the selling plan in the product's selling plan groups\n for (const group of product.sellingPlanGroups) {\n const sellingPlan = group.sellingPlans.find(\n (sp) => sp.id === sellingPlanId,\n );\n if (sellingPlan) {\n return {\n sellingPlan: {\n id: sellingPlan.id,\n name: sellingPlan.name,\n description: sellingPlan.description,\n },\n priceAdjustments: sellingPlan.priceAdjustments.map((adj) => {\n const adjustedPrice =\n variantPrice !== undefined\n ? applyPriceAdjustment(variantPrice, adj, currencyCode)\n : 0;\n\n return {\n compareAtPrice: {\n amount: variantPrice ?? 0,\n currencyCode,\n },\n perDeliveryPrice: {\n amount: adjustedPrice,\n currencyCode,\n },\n price: {\n amount: adjustedPrice,\n currencyCode,\n },\n unitPrice: null,\n };\n }),\n checkoutChargeAmount: {\n amount: 0,\n currencyCode,\n },\n remainingBalanceChargeAmount: {\n amount: 0,\n currencyCode,\n },\n };\n }\n }\n\n return null;\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,27 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __commonJS = (cb, mod) => function __require() {
8
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
+ // If the importer is in node compatibility mode or this is not an ESM
20
+ // file that has been converted to a CommonJS file using a Babel-
21
+ // compatible transform (i.e. "__esModule" has not been set), then set
22
+ // "default" to the CommonJS "module.exports" for node compatibility.
23
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
+ mod
25
+ ));
26
+
27
+ export { __commonJS, __toESM };
@@ -0,0 +1,9 @@
1
+ import type { ConsentAction } from "../_vendor/schemas/generated/consent";
2
+ import type { ConsentCategories } from "./cookie";
3
+ export declare function deriveConsentAction({ previous, next, }: {
4
+ previous: {
5
+ decidedAt: number | null;
6
+ categories: ConsentCategories;
7
+ };
8
+ next: ConsentCategories;
9
+ }): ConsentAction;
@@ -0,0 +1,25 @@
1
+ const NON_NECESSARY_CATEGORIES = [
2
+ "analytics",
3
+ "marketing",
4
+ "preferences",
5
+ "sale_of_data"
6
+ ];
7
+ function deriveConsentAction({
8
+ previous,
9
+ next
10
+ }) {
11
+ const anyGranted = NON_NECESSARY_CATEGORIES.some(
12
+ (category) => next[category]
13
+ );
14
+ if (previous.decidedAt === null) {
15
+ return anyGranted ? "accept" : "reject";
16
+ }
17
+ if (!anyGranted) {
18
+ return "withdraw";
19
+ }
20
+ return "update";
21
+ }
22
+ export {
23
+ deriveConsentAction
24
+ };
25
+ //# sourceMappingURL=consent-action.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../consent/consent-action.ts"],
4
+ "sourcesContent": ["import type { ConsentAction, ConsentCategory } from \"schemas/generated/consent\";\nimport type { ConsentCategories } from \"./cookie\";\n\n/**\n * Pure mapping from a consent decision (previous state vs the resulting state)\n * to the legal-record `action` written to the proof-of-consent audit log.\n *\n * Only non-necessary categories matter (`necessary` is always granted). The\n * mapping is:\n * - first decision (`decidedAt === null`), some category granted -> \"accept\"\n * - first decision, nothing granted -> \"reject\"\n * - later decision that leaves nothing granted -> \"withdraw\"\n * - any other later change -> \"update\"\n *\n * `withdraw` is reserved for going from some consent to none (the GDPR\n * withdrawal right); a partial reduction that still leaves something granted is\n * an `update`.\n */\nconst NON_NECESSARY_CATEGORIES: Exclude<ConsentCategory, \"necessary\">[] = [\n \"analytics\",\n \"marketing\",\n \"preferences\",\n \"sale_of_data\",\n];\n\nexport function deriveConsentAction({\n previous,\n next,\n}: {\n previous: { decidedAt: number | null; categories: ConsentCategories };\n next: ConsentCategories;\n}): ConsentAction {\n const anyGranted = NON_NECESSARY_CATEGORIES.some(\n (category) => next[category],\n );\n\n if (previous.decidedAt === null) {\n return anyGranted ? \"accept\" : \"reject\";\n }\n if (!anyGranted) {\n return \"withdraw\";\n }\n return \"update\";\n}\n"],
5
+ "mappings": "AAkBA,MAAM,2BAAoE;AAAA,EACxE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AACF,GAGkB;AAChB,QAAM,aAAa,yBAAyB;AAAA,IAC1C,CAAC,aAAa,KAAK,QAAQ;AAAA,EAC7B;AAEA,MAAI,SAAS,cAAc,MAAM;AAC/B,WAAO,aAAa,WAAW;AAAA,EACjC;AACA,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AACA,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -0,0 +1,28 @@
1
+ import type { ConsentAction, ConsentMode } from "../_vendor/schemas/generated/consent";
2
+ import type { ConsentCategories } from "./cookie";
3
+ /**
4
+ * Records a proof-of-consent decision by forwarding it to analytics-fire.
5
+ *
6
+ * Runs server-side — exactly like the cart's actions — so the browser never needs
7
+ * the project id or the analytics-fire URL: both are read from the worker env
8
+ * here via `getEnv()`. The visitor's user-agent / referrer / coarse geo come from
9
+ * the incoming request headers and are forwarded so analytics-fire derives them
10
+ * the same way it does for the legacy pixel.
11
+ *
12
+ * Best-effort and self-disabling: no-ops when analytics-fire isn't configured
13
+ * (e.g. lean local dev) and never throws into the page.
14
+ */
15
+ export declare function recordConsentAction(decision: {
16
+ consentId: string;
17
+ action: ConsentAction;
18
+ /** Full resulting consent state (all categories), not a diff. */
19
+ categories: ConsentCategories;
20
+ consentMode: ConsentMode;
21
+ decidedAt: number;
22
+ /**
23
+ * Site-owned policy version declared by the consent caller (via
24
+ * `useConsent({ version })`); the site bumps it when the banner copy or
25
+ * category set changes.
26
+ */
27
+ policyVersion: number;
28
+ }): Promise<void>;
@@ -0,0 +1,36 @@
1
+ "use server";
2
+ import { headers } from "next/headers";
3
+ import { getEnv } from "../env";
4
+ async function recordConsentAction(decision) {
5
+ try {
6
+ const { PROJECT_ID, ANALYTICS_FIRE_API_URL } = await getEnv();
7
+ if (!ANALYTICS_FIRE_API_URL) {
8
+ return;
9
+ }
10
+ const incoming = await headers();
11
+ const body = {
12
+ consentId: decision.consentId,
13
+ projectId: PROJECT_ID,
14
+ decidedAt: decision.decidedAt,
15
+ action: decision.action,
16
+ categories: decision.categories,
17
+ consentMode: decision.consentMode,
18
+ policyVersion: decision.policyVersion
19
+ };
20
+ await fetch(`${ANALYTICS_FIRE_API_URL}/api/v1/consent`, {
21
+ method: "POST",
22
+ headers: {
23
+ "content-type": "application/json",
24
+ "user-agent": incoming.get("user-agent") ?? "",
25
+ referer: incoming.get("referer") ?? "",
26
+ "cf-ipcountry": incoming.get("cf-ipcountry") ?? ""
27
+ },
28
+ body: JSON.stringify(body)
29
+ });
30
+ } catch {
31
+ }
32
+ }
33
+ export {
34
+ recordConsentAction
35
+ };
36
+ //# sourceMappingURL=consent-actions.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../consent/consent-actions.ts"],
4
+ "sourcesContent": ["\"use server\";\n\nimport type {\n ConsentAction,\n ConsentMode,\n ConsentRecordRequest,\n} from \"schemas/generated/consent\";\nimport type { ConsentCategories } from \"./cookie\";\n\nimport { headers } from \"next/headers\";\n\nimport { getEnv } from \"../env\";\n\n/**\n * Records a proof-of-consent decision by forwarding it to analytics-fire.\n *\n * Runs server-side \u2014 exactly like the cart's actions \u2014 so the browser never needs\n * the project id or the analytics-fire URL: both are read from the worker env\n * here via `getEnv()`. The visitor's user-agent / referrer / coarse geo come from\n * the incoming request headers and are forwarded so analytics-fire derives them\n * the same way it does for the legacy pixel.\n *\n * Best-effort and self-disabling: no-ops when analytics-fire isn't configured\n * (e.g. lean local dev) and never throws into the page.\n */\nexport async function recordConsentAction(decision: {\n consentId: string;\n action: ConsentAction;\n /** Full resulting consent state (all categories), not a diff. */\n categories: ConsentCategories;\n consentMode: ConsentMode;\n decidedAt: number;\n /**\n * Site-owned policy version declared by the consent caller (via\n * `useConsent({ version })`); the site bumps it when the banner copy or\n * category set changes.\n */\n policyVersion: number;\n}) {\n // Best-effort and total: never reject (the caller fire-and-forgets it), even if\n // the Cloudflare env / request context is unavailable.\n try {\n const { PROJECT_ID, ANALYTICS_FIRE_API_URL } = await getEnv();\n if (!ANALYTICS_FIRE_API_URL) {\n return;\n }\n\n const incoming = await headers();\n const body: ConsentRecordRequest = {\n consentId: decision.consentId,\n projectId: PROJECT_ID,\n decidedAt: decision.decidedAt,\n action: decision.action,\n categories: decision.categories,\n consentMode: decision.consentMode,\n policyVersion: decision.policyVersion,\n };\n\n await fetch(`${ANALYTICS_FIRE_API_URL}/api/v1/consent`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n \"user-agent\": incoming.get(\"user-agent\") ?? \"\",\n referer: incoming.get(\"referer\") ?? \"\",\n \"cf-ipcountry\": incoming.get(\"cf-ipcountry\") ?? \"\",\n },\n body: JSON.stringify(body),\n });\n } catch {\n // Consent logging must never surface an error in the page.\n }\n}\n"],
5
+ "mappings": ";AASA,SAAS,eAAe;AAExB,SAAS,cAAc;AAcvB,eAAsB,oBAAoB,UAavC;AAGD,MAAI;AACF,UAAM,EAAE,YAAY,uBAAuB,IAAI,MAAM,OAAO;AAC5D,QAAI,CAAC,wBAAwB;AAC3B;AAAA,IACF;AAEA,UAAM,WAAW,MAAM,QAAQ;AAC/B,UAAM,OAA6B;AAAA,MACjC,WAAW,SAAS;AAAA,MACpB,WAAW;AAAA,MACX,WAAW,SAAS;AAAA,MACpB,QAAQ,SAAS;AAAA,MACjB,YAAY,SAAS;AAAA,MACrB,aAAa,SAAS;AAAA,MACtB,eAAe,SAAS;AAAA,IAC1B;AAEA,UAAM,MAAM,GAAG,sBAAsB,mBAAmB;AAAA,MACtD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,cAAc,SAAS,IAAI,YAAY,KAAK;AAAA,QAC5C,SAAS,SAAS,IAAI,SAAS,KAAK;AAAA,QACpC,gBAAgB,SAAS,IAAI,cAAc,KAAK;AAAA,MAClD;AAAA,MACA,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC;AAAA,EACH,QAAQ;AAAA,EAER;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,69 @@
1
+ import type { ConsentCategory, ConsentMode } from "../_vendor/schemas/generated/consent";
2
+ import type { ConsentCategories } from "./cookie";
3
+ /**
4
+ * Framework-agnostic, cookie-backed consent store. Lives as a single module
5
+ * singleton so `ReploScripts`, the site's consent banner, and `AnalyticsProvider`
6
+ * all share the same state regardless of where they sit in the React tree — no
7
+ * provider-ancestor relationship is required.
8
+ *
9
+ * Consent-dependent work (script injection, banner visibility) happens on the
10
+ * client (effects / post-mount), so there is no SSR/hydration mismatch. The
11
+ * client reads the (non-httpOnly) consent cookie directly, so no server seed is
12
+ * needed. The whole-site `mode` comes from the `data-replo-consent-mode`
13
+ * attribute on `<html>`.
14
+ */
15
+ export interface ConsentState {
16
+ mode: ConsentMode;
17
+ categories: ConsentCategories;
18
+ /** Epoch ms when the visitor last made a choice, or null if undecided. */
19
+ decidedAt: number | null;
20
+ /**
21
+ * Persistent pseudonymous per-browser id linking a visitor's consent history.
22
+ * Absent until the first `commit()` (and on legacy pre-id cookies, until the
23
+ * next interaction lazily backfills it).
24
+ */
25
+ consentId?: string;
26
+ }
27
+ export declare const consentStore: {
28
+ getSnapshot(): ConsentState;
29
+ getServerSnapshot(): ConsentState;
30
+ subscribe(listener: () => void): () => void;
31
+ /** Grant every consent category. */
32
+ accept(options?: {
33
+ policyVersion?: number;
34
+ }): void;
35
+ /** Deny every non-necessary category. */
36
+ reject(options?: {
37
+ policyVersion?: number;
38
+ }): void;
39
+ /** Set a subset of categories, leaving the rest at their current value. */
40
+ updateCategories({ next, policyVersion, }: {
41
+ next: Partial<ConsentCategories>;
42
+ policyVersion?: number;
43
+ }): void;
44
+ /** Test-only: reset cached state so the next read recomputes. */
45
+ reset(): void;
46
+ };
47
+ export interface UseConsentOptions {
48
+ /**
49
+ * Site-owned consent policy version, stamped onto each proof-of-consent
50
+ * record so a decision can be tied to the exact banner the visitor saw.
51
+ * Bump it whenever the banner copy or category set changes. Defaults to 1.
52
+ */
53
+ version?: number;
54
+ }
55
+ export interface UseConsentResult extends ConsentState {
56
+ accept(): void;
57
+ reject(): void;
58
+ updateCategories(next: Partial<ConsentCategories>): void;
59
+ }
60
+ export declare function useConsent(options?: UseConsentOptions): UseConsentResult;
61
+ /**
62
+ * Whether a script requiring `requiredConsent` is allowed to load right now.
63
+ * `mode === "off"` always allows; otherwise every required category must be
64
+ * granted. An empty requirement list is always allowed.
65
+ */
66
+ export declare function isConsentAllowed({ state, requiredConsent, }: {
67
+ state: ConsentState;
68
+ requiredConsent: ConsentCategory[];
69
+ }): boolean;