{"version":3,"file":"standard-storefront-actions-snippet.min.js","mappings":"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","sources":["webpack://lange/./lib/decorate-error.ts","webpack://lange/./lib/cart/error.ts","webpack://lange/../node_modules/@integrabeauty/assert/src/index.ts","webpack://lange/../node_modules/@integrabeauty/shopify-ajax-api/src/cart.ts","webpack://lange/../node_modules/uuid/dist/rng.js","webpack://lange/../node_modules/uuid/dist/stringify.js","webpack://lange/../node_modules/uuid/dist/v4.js","webpack://lange/./lib/object.ts","webpack://lange/./lib/cart/discount/discount.ts","webpack://lange/./lib/cart/state.ts","webpack://lange/./lib/perf.ts","webpack://lange/./lib/cart/transaction.ts","webpack://lange/./snippets/standard-storefront-actions/index.ts"],"sourcesContent":["/**\n * Copies a single piece of supplementary context onto an error before it is thrown or reported.\n *\n * Use this when decorating a value the calling code did not construct, such as a caught value or an\n * error handed in by another module. An error the caller just built with new is always writable, so\n * plain assignment is fine there. A value from elsewhere may be a foreign object this code does not\n * own. Such a value can carry a non-writable property, such as a DOMException whose code is a read-\n * only accessor, or be frozen outright, and assigning to it then throws. Attaching context must\n * never itself fail, so a rejected assignment is swallowed and the remaining context is still\n * attempted. Guarding each property in isolation means a single read-only slot, the common case,\n * costs only that one property rather than all of them.\n *\n * In practice the value that triggers this is almost always a DOMException that reached the caller\n * from a fetch the browser cut short, such as an abort when an in-flight request is interrupted.\n * The platform defines a DOMException's code, name, and message as read-only attributes, so\n * assigning a code onto one throws under the strict mode that all bundled module code runs in. It\n * surfaces from Safari far more than elsewhere for two reasons. The reported wording, attempted to\n * assign to readonly property, is JavaScriptCore's phrasing for this failure, so the same bug in\n * another engine is worded differently and groups as a separate report, which makes this one look\n * Safari specific even though the read-only rule is not. And iOS Safari abandons in-flight fetches\n * aggressively when a tab is backgrounded or a connection drops, which is when such a DOMException\n * tends to be produced and handed to code that assumed an ordinary writable error.\n *\n * A swallowed failure is logged at a level low enough that it is recorded as a breadcrumb for\n * monitoring rather than escalated into its own captured event. That keeps the lost context visible\n * alongside the eventual report without adding fresh noise of its own. The breadcrumb names the\n * property that could not be attached, the kind of value that rejected it, and why, which is enough\n * to recognize a read-only or frozen caught value after the fact.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Read-only\n * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMException\n * @see https://webidl.spec.whatwg.org/#dfn-read-only\n */\nexport function decorateError(error: any, key: string, value: unknown) {\n  try {\n    error[key] = value;\n  } catch (assignmentError) {\n    let reason: string;\n    if (assignmentError instanceof Error) {\n      reason = assignmentError.message;\n    } else {\n      reason = `${assignmentError}`;\n    }\n\n    console.log('Failed to assign error property', {\n      key,\n      name: error?.name,\n      tag: Object.prototype.toString.call(error),\n      reason\n    });\n  }\n}\n","import type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { decorateError } from '../decorate-error.js';\nimport type { Transaction } from './transaction.js';\n\ntype CartUpdateErredEvent = CustomEvent<{\n  /**\n   * Cart state.\n   *\n   * This may be undefined or null or invalid because almost nothing is guaranteed.\n   */\n  cart?: Cart;\n\n  /**\n   * An error code that describes the context of the error or the type of the error.\n   */\n  code: string;\n\n  /**\n   * The error that occurred. This will almost always be of type `Error`. However, this is set to\n   * the value of whatever error was caught. Values not of type `Error` can be thrown. We are not\n   * checking that the value is an instance of an `Error` first. It is not safe to assume this is of\n   * type `Error` particularly because of code minimization and third party modifications.\n   */\n  error: any;\n\n  /**\n   * A mix of misc properties that various cart transactions will include that help provide more\n   * information about the inputs to the code that led to the error.\n   */\n  inputs?: Record<string, any>;\n\n  /**\n   * If true, this means the event was emitted by a multi-fetch operation where some of the fetches\n   * succeeded. For example, consider the addItems call, which has to do two fetches in its virtual\n   * transaction. This will be true when the fetch to add the item succeeded but the fetch to get\n   * the new cart state after the add failed. This flag exists so that callers can choose to react\n   * to this situation differently, e.g. by retrying just the cart state fetch, or changing what\n   * error message is displayed (a failure to add versus a partially successful add).\n   */\n  partial?: boolean;\n\n  /**\n   * An optional programmatic code or human-readable description of the context in which some cart\n   * update was taken. This is primarily for analytics, but can also be used for ui logic.\n   *\n   * For example:\n   * * distinctly identify two different add to cart buttons\n   * * distinctly identify a particular ui element used to apply a discount\n   */\n  scenario?: string;\n\n  /**\n   * The transaction in which the error occurred.\n   */\n  transaction: Transaction;\n}>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'cart-update-erred': CartUpdateErredEvent;\n  }\n}\n\n/**\n * Broadcasts a cart failure and, when it is a genuine programmer error, surfaces it to the global\n * error handler so that monitoring records it.\n *\n * The cart helpers deliberately catch their own failures so that the code awaiting them is not\n * forced to handle a promise rejection. Every failure is broadcast as an event for the interface to\n * react to. That catch keeps the promise from rejecting, but for an unexpected failure it does not\n * mean the problem was actually dealt with. It is still a real error that would have reached the\n * top level had it never been caught. The escalation step re-throws those so that they do, leaving\n * error monitoring to record whatever uncaught errors arrive with no knowledge of the cart.\n */\nexport function report(dispatcher: EventTarget | null, detail: CartUpdateErredEvent['detail']) {\n  dispatchCartUpdateErred(dispatcher, detail);\n  escalateCartError(detail);\n}\n\n/**\n * Dispatches the failure as an event for the interface to react to.\n *\n * The dispatcher is the target the event is dispatched from. It is required but may be null, and it\n * may be any event target, not necessarily an element or window. The target is used as given, with\n * two exceptions that exist only to keep the event reaching window level listeners: a null target\n * dispatches from window, and so does a detached element.\n *\n * A detached element is swapped for window because of how bubbling works. We want to guarantee the\n * event bubbles up to window so that window level listeners are notified. A detached element only\n * bubbles up to its detached subtree root, which is not window, so dispatching from it directly\n * would not reach those listeners.\n */\nfunction dispatchCartUpdateErred(dispatcher: EventTarget | null,\n  detail: CartUpdateErredEvent['detail']) {\n  // Dispatch the failure from whatever target the caller designates; this forms no opinion about\n  // what kind of target it is. The only special handling exists to keep the event reaching the\n  // global listeners: a detached element has no live ancestor chain to bubble through, so it is\n  // swapped for the global scope, and a null target means the global scope outright. Any other\n  // target, including a connected element, is used as given. Callers should still prefer the\n  // element most directly responsible for the failure, so a connected element carries truthful\n  // provenance and bubbles, crossing shadow boundaries, up to the global listeners.\n\n  let target: EventTarget;\n  if (dispatcher instanceof Element) {\n    if (dispatcher.isConnected) {\n      target = dispatcher;\n    } else {\n      target = window;\n    }\n  } else if (dispatcher) {\n    target = dispatcher;\n  } else {\n    target = window;\n  }\n\n  const event = new CustomEvent<typeof detail>('cart-update-erred', {\n    detail,\n    bubbles: true,\n    composed: true\n  });\n\n  target.dispatchEvent(event);\n}\n\n/**\n * Surfaces a genuine programmer error to the global error handler so that monitoring records it.\n *\n * Some failures are expected outcomes with their own user facing messaging rather than programmer\n * errors: a shopper adding an out of stock item, or mistyping a discount code. Those are never\n * escalated to monitoring. Each such failure is identified by its code, the same classification the\n * cart helpers attach for the interface, so the list here must keep pace with the codes those\n * helpers produce.\n *\n * The throw is deferred to a later task so that it escapes the surrounding promise chain instead of\n * rejecting it, preserving the rejection-free contract of the cart helpers. The error keeps the\n * stack captured when it was first created, so the report still points at the origin of the failure\n * rather than at this deferred throw. However, beware that this does mean that the error event\n * properties will reflect this throw context, not the original error context, which means that the\n * instrumenting code must prioritize the original error properties over the error event properties.\n *\n * When available, the throw is routed through the reportError api, which surfaces the value to the\n * global error handler without the extra task hop. Older engines that lack it fall back to a\n * deferred throw, which reaches the same handler.\n *\n * The context describing the failure is attached to the error so that monitoring which serializes\n * the non-standard properties of an error can include it. A value that is not an error is wrapped\n * so that the context still has somewhere to live.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/reportError\n */\nfunction escalateCartError(detail: CartUpdateErredEvent['detail']) {\n  // These are expected outcomes with their own user facing messaging rather than programmer errors.\n  //\n  // This list of codes to ignore deliberately excludes invalid selling plan codes such as\n  // ADD_ITEMS_INVALID_SELLING_PLAN, ADD_ITEMS_INVALID_BACKORDER_SELLING_PLAN, and\n  // ADD_ITEMS_INVALID_TBYB_SELLING_PLAN. Unlike the ephemeral shopper conditions, a rejected\n  // selling plan is a plan we attached to the item, so it signals that something is misconfigured\n  // rather than a transient outcome. We want these to continue reaching error diagnostics so that\n  // misconfiguration is visible.\n\n  const ignoredCodes = [\n    'APPLY_DISCOUNTS',\n    'ADD_ITEMS_OOS',\n    'ADD_ITEMS_BUNDLE_ADDON_MIX',\n    'ADD_ITEMS_MAX_QUANTITY',\n    'CHANGE_CART_OOS',\n    'UPDATE_CART_OOS'\n  ];\n\n  if (detail && !ignoredCodes.includes(detail.code)) {\n    let error: any;\n\n    if (detail.error instanceof Error) {\n      error = detail.error;\n    } else {\n      error = new Error(`${detail.error}`);\n    }\n\n    decorateError(error, 'code', detail.code);\n    decorateError(error, 'inputs', detail.inputs);\n    decorateError(error, 'scenario', detail.scenario);\n    decorateError(error, 'cart', detail.cart);\n    decorateError(error, 'transaction_id', detail.transaction?.id);\n    decorateError(error, 'transaction_name', detail.transaction?.name);\n\n    if (typeof reportError === 'function') {\n      reportError(error);\n    } else {\n      setTimeout(raise, 0, error);\n    }\n  }\n}\n\nfunction raise(value: unknown) {\n  throw value;\n}\n","/**\n * Throws an assertion error if the given condition is falsy.\n *\n * @param expression any expression, although one that evaluates to true or false is preferred by\n * convention\n * @param message optional error text\n */\nexport function assert(expression: unknown, message?: string) {\n  if (!expression) {\n    throw new AssertionError(message);\n  }\n}\n\nexport class AssertionError extends Error {\n  constructor(message = 'Assertion error') {\n    super(message);\n    this.name = this.constructor.name;\n  }\n}\n","import type {} from '@integrabeauty/shopify-browser-types';\n\n/**\n * Represents the value of a cart attribute. Ordinarily values are strings. However, Shopify also\n * supports storing nested objects as values.\n */\nexport type AttributeValue = Record<string, string> | string;\n\n/**\n * The type of the value of `Cart['attributes']`\n */\nexport type Attributes = Record<string, AttributeValue>;\n\n/**\n * This is the stored cart, which is what a get cart answers with. A mutation answers with a\n * superset, so see the response body types alongside each mutation for the extra properties those\n * carry.\n *\n * @todo selling plan properties\n * @todo endpoint request body response body types\n *\n * @see https://shopify.dev/docs/api/ajax/reference/cart\n */\nexport interface Cart {\n  /**\n   * An object where the key is the attribute name and the value is the attribute value.\n   */\n  attributes: Attributes;\n\n  /**\n   * A discount application represents a type of discount.\n   *\n   * When a discount is applied to one or more line items, it is said to be _allocated_ to the line\n   * items. That is why line level discount allocation objects have a nested application object that\n   * will correlate with one of the applications in this array.\n   *\n   * When a discount is applied to the whole cart, it will only be present in this array because it\n   * is not allocated to particular lines.\n   */\n  cart_level_discount_applications: DiscountApplication[];\n\n  /**\n   * The ISO currency code for the cart.\n   *\n   * @example \"USD\"\n   */\n  currency: string;\n\n  /**\n   * Discount codes that have been applied to the cart.\n   *\n   * Not all codes are applicable. It is possible for a code to be applied but inapplicable.\n   *\n   * This does not provide information about the amount of each discount. Refer to the cart level\n   * discount applications or line level discount allocations for that information.\n   *\n   * This array will include shipping discounts. A shipping discount will be present in the array if\n   * applied but in the inapplicable state while in the cart. The discount will appear as applicable\n   * at checkout automatically.\n   */\n  discount_codes: DiscountCode[];\n\n  /**\n   * Total quantity of items in a cart\n   */\n  item_count: number;\n\n  /**\n   * The cart's line items.\n   *\n   * A cart will always have a defined items array, even if the cart is empty.\n   */\n  items: Item[];\n\n  items_subtotal_price: number;\n\n  /**\n   * An optional textual note that may be applied to the cart. If a cart becomes a checkout, and\n   * then the checkout becomes an order, the cart note will carry over to the order note.\n   *\n   * An order's note, and an order's note attributes, are two completely separate concepts.\n   */\n  note: string;\n\n  original_total_price: number;\n\n  /**\n   * This is true when at least one item in the cart requires shipping.\n   *\n   * This is false when no items in the cart require shipping.\n   *\n   * Shopify supports digital-only carts. These are carts that when purchased do not require the\n   * creation of a fulfillment. For example, a cart that consists only of a digital gift card.\n   */\n  requires_shipping: boolean;\n\n  /**\n   * This property is defined when there are cart mutations that involve the use of liquid-rendered\n   * sections.\n   *\n   * @see https://shopify.dev/docs/api/ajax/reference/cart#request-sections\n   */\n  sections: Record<string, string>;\n\n  /**\n   * This is the publicly visible cart id.\n   *\n   * If a checkout is created from this cart, the checkout's cart token will be equal to this value.\n   *\n   * Never write logic that parses this value. The format can change at any time, and has changed\n   * several times in the past.\n   *\n   * The value may contain url-like values, e.g. \"abc?key=123\"\n   */\n  token: string;\n\n  /**\n   * The total reduction in price in currency subunits (e.g. in cents for USD).\n   *\n   * @example 5010\n   */\n  total_discount: number;\n\n  /**\n   * Total price in currency subunits. For example, if the currency is USD, this is in cents.\n   *\n   * @example 1050\n   */\n  total_price: number;\n\n  /**\n   * The total weight of the cart (in grams?). This is a simple sum of the weight of the items of\n   * the cart (unit weight * quantity).\n   *\n   * @example 123.456\n   */\n  total_weight: number;\n}\n\n/**\n * Represents a discount that has been applied to an item.\n */\nexport interface Discount {\n  amount: number;\n  title: string;\n}\n\n/**\n * Represents a discount application that has been allocated to a line item.\n */\nexport interface DiscountAllocation {\n  /**\n   * The amount of the application that was allocated to a particular line item.\n   *\n   * When a discount application is allocated to several line items, this is a partial value of the\n   * full discount application amount that is allocated to this particular line. When the discount\n   * application is only allocated to a single line, this value will be equal to the total value of\n   * the discount application.\n   *\n   * For example, let's say we have cart with 2 line items and 10 dollar off discount application.\n   * The total value of the discount application is 10 dollars. Let's say the discount is allocated\n   * to both lines. The value of each allocation is 5 dollars. For each line this amount will\n   * contain the value 500. Within the nested application object, the application's total amount\n   * will be 1000. This is because each nested application object is a copy of the one application\n   * that happens to have multiple allocations (the discount is applied to multiple lines).\n   *\n   * The amount is in currency subunits. For example, for USD, this is the value of cents. For\n   * example, a discount of $2.50 is 250.\n   *\n   * @example 1050\n   */\n  amount: number;\n\n  /**\n   * The corresponding discount application that was allocated to a particular line item.\n   */\n  discount_application: DiscountApplication;\n}\n\n/**\n * A discount application represents a discount of some kind. There are automatically applied\n * discount applications, discount code applications, and script applied discount applications.\n */\nexport interface DiscountApplication {\n  allocation_method: string;\n  created_at: string;\n  description: string;\n  key: string;\n  target_selection: string;\n  target_type: string;\n  title: string;\n  total_allocated_amount: number;\n  type: string;\n  value: string;\n  value_type: string;\n}\n\n/**\n * The details of an image resource, such as the hero image for a product.\n */\nexport interface Image {\n  alt: string;\n  aspect_ratio: number;\n  height: number;\n  url: string;\n  width: number;\n}\n\n/**\n * Represents a line item in a cart.\n */\nexport interface Item {\n  /**\n   * The price of the line item after discounts\n   */\n  discounted_price: number;\n\n  /**\n   * Discount codes applied to the line item.\n   *\n   * @deprecated\n   */\n  discounts: Discount[];\n\n  /**\n   * The main image for the product\n   */\n  featured_image: Image;\n\n  /**\n   * The combined price, in the currency's subunit, of all of the items in the line item. This\n   * includes any line-level discounts.\n   *\n   * The value is equal to line_item.final_price multiplied by line_item.quantity. It's output in\n   * the customer's local (presentment) currency.\n   *\n   * For currencies without subunits, such as JPY and KRW, tenths and hundredths of a unit are\n   * appended. For example, 1000 Japanese yen is output as 100000.\n   */\n  final_line_price: number;\n\n  /**\n   * The price of the line item in the currency's subunit. This includes any line-level discounts.\n   *\n   * The value is output in the customer's local (presentment) currency.\n   *\n   * For currencies without subunits, such as JPY and KRW, tenths and hundredths of a unit are\n   * appended. For example, 1000 Japanese yen is output as 100000.\n   */\n  final_price: number;\n\n  /**\n   * Returns true if the product associated with the line item is a gift card. Returns false if not.\n   */\n  gift_card: boolean;\n\n  /**\n   * The weight of the line item in the store's default weight unit.\n   */\n  grams: number;\n\n  handle: string;\n\n  has_components: boolean;\n\n  /**\n   * The ID of the line item. The ID differs depending on the context. The following table outlines\n   * the possible contexts and their associated values:\n   *\n   * Context Value\n   *\n   * * cart.items The ID of the line item's variant. This ID isn't unique, and can be shared by\n   *   multiple items with the same variant.\n   * * checkout.line_items A temporary unique hash generated for the checkout.\n   * * order.line_items A unique integer ID.\n   */\n  id: number;\n\n  image: string;\n\n  /**\n   * Added in conjunction with parent_relationship to support determining whether a line item can be\n   * removed or have its quantity updated when the line item is a child of another line item, e.g.\n   * part of a bundle or value set or promotional gift.\n   */\n  instructions: {\n    can_remove: boolean;\n    can_update_quantity: boolean;\n  };\n\n  key: string;\n\n  /**\n   * The discount allocations that apply to the line item.\n   */\n  line_level_discount_allocations: DiscountAllocation[];\n\n  /**\n   * The total amount of any discounts applied to the line item in the currency's subunit.\n   *\n   * The value is output in the customer's local (presentment) currency.\n   *\n   * For currencies without subunits, such as JPY and KRW, tenths and hundredths of a unit are\n   * appended. For example, 1000 Japanese yen is output as 100000.\n   */\n  line_level_total_discount: number;\n\n  /**\n   * @deprecated https://shopify.dev/docs/api/liquid/objects/line_item\n   */\n  line_price: number;\n\n  /**\n   * The name and value pairs for each option of the variant associated with the line item.\n   *\n   * The array is never empty because variants with no options still have a default option. Because\n   * of this, you should use line_item.product.has_only_default_variant to check whether there's any\n   * information to output.\n   */\n  options_with_values: Option[];\n\n  /**\n   * The combined price of all of the items in a line item in the currency's subunit, before any\n   * discounts have been applied.\n   *\n   * The value is equal to line_item.original_price multiplied by line_item.quantity. It's output in\n   * the customer's local (presentment) currency.\n   *\n   * For currencies without subunits, such as JPY and KRW, tenths and hundredths of a unit are\n   * appended. For example, 1000 Japanese yen is output as 100000.\n   */\n  original_line_price: number;\n\n  /**\n   * The price of the line item in the currency's subunit, before discounts have been applied.\n   *\n   * The value is output in the customer's local (presentment) currency.\n   *\n   * For currencies without subunits, such as JPY and KRW, tenths and hundredths of a unit are\n   * appended. For example, 1000 Japanese yen is output as 100000.\n   */\n  original_price: number;\n\n  /**\n   * The parent relationship of the line item. This is only present if the line item is a child of\n   * another line item. For example, when the line item is part of a bundle or value set or\n   * promotional gift.\n   *\n   * The parent relationship must be established by adding the \"parent_id\" property to the add to\n   * bag request.\n   *\n   * @see https://shopify.dev/docs/apps/build/product-merchandising/nested-cart-lines/create-nested-cart-lines#cart-ajax-api\n   */\n  parent_relationship: {\n    parent_key: string;\n  };\n\n  presentment_price: number;\n\n  /**\n   * @deprecated https://shopify.dev/docs/api/liquid/objects/line_item\n   */\n  price: number;\n\n  product_description: string;\n\n  product_has_only_default_variant: boolean;\n\n  /**\n   * The ID of the line item's product.\n   */\n  product_id: number;\n\n  product_title: string;\n\n  product_type: string;\n\n  properties: Record<string, string>;\n  quantity: number;\n\n  quantity_rule: QuantityRule;\n\n  requires_shipping: boolean;\n\n  /**\n   * The selling plan allocation of the line item.\n   */\n  selling_plan_allocation: SellingPlanAllocation;\n\n  /**\n   * The SKU of the variant in the line item.\n   *\n   * Stock Keeping Unit.\n   */\n  sku: string;\n\n  successfully_fulfilled_quantity: number;\n\n  taxable: boolean;\n\n  /**\n   * The title of the line item. The title is a combination of line_item.product.title and\n   * line_item.variant.title, separated by a hyphen.\n   *\n   * In most contexts, the line item title appears in the customer's preferred language. However, in\n   * the context of an order, the line item title appears in the language that the customer checked\n   * out in. The title can receive an override value from the Cart Transform API. Overrides take\n   * precedence over translations.\n   *\n   * #### Line item title history\n   *\n   * When referencing line item, product, and variant titles in the context of an order, the\n   * following changes might result in a different output than you expect:\n   * * A product or variant being deleted\n   * * A product or variant title being edited\n   *\n   * When line_item.title is referenced for an order, the line item title at the time of the order\n   * is returned. However, when line_item.product.title and line_item.variant.title are referenced,\n   * the current value for each title is returned.\n   */\n  title: string;\n\n  /**\n   * @deprecated https://shopify.dev/docs/api/liquid/objects/line_item\n   */\n  total_discount: number;\n\n  unit_price_measurement: {\n    measured_type: string;\n    quantity_unit: string;\n    quantity_value: string;\n    reference_unit: string;\n    reference_value: number;\n  };\n\n  untranslated_product_title: string;\n\n  untranslated_variant_title: string;\n\n  /**\n   * The relative URL of the variant associated with the line item.\n   */\n  url: string;\n\n  /**\n   * The ID of the line item's variant.\n   */\n  variant_id: number;\n\n  variant_options: string[];\n\n  variant_title: string;\n\n  /**\n   * The vendor of the variant associated with the line item.\n   */\n  vendor: string;\n}\n\nexport interface SellingPlanAllocation {\n  compare_at_price: number;\n\n  per_delivery_price: number;\n\n  /**\n   * @example 900\n   */\n  price: number;\n\n  price_adjustments: SellingPlanAllocationPriceAdjustment[];\n  selling_plan: SellingPlan;\n}\n\nexport interface SellingPlanAllocationPriceAdjustment {\n  /**\n   * @example 1\n   */\n  position: number;\n\n  /**\n   * @example 900\n   */\n  price: number;\n}\n\nexport interface SellingPlan {\n  description: unknown;\n\n  fixed_selling_plan: boolean;\n\n  id: number;\n  name: string;\n\n  options: SellingPlanOption[];\n\n  price_adjustments: SellingPlanPriceAdjustment[];\n\n  recurring_deliveries: boolean;\n}\n\nexport interface SellingPlanOption {\n  name: string;\n  position: number;\n  value: string;\n}\n\nexport interface SellingPlanPriceAdjustment {\n  order_count: unknown;\n  position: number;\n  value: number;\n  value_type: string;\n}\n\nexport interface Option {\n  name: string;\n  value: string;\n}\n\nexport interface QuantityRule {\n  increment: number;\n  max: number;\n  min: number;\n}\n\nexport interface DiscountCode {\n  /**\n   * Whether the discount is currently applicable. It is possible to apply a discount code to a cart\n   * but have it not yet be applicable. When not applicable, there will not be any associated\n   * discount applications or allocations. The moment someone performs an action that makes the\n   * discount code applicable, Shopify will update the property to true.\n   */\n  applicable: boolean;\n\n  /**\n   * The discount code string.\n   *\n   * This is NOT guaranteed to be uppercase. Discount codes entered at checkout are captured in\n   * whatever case was used during input.\n   *\n   * @example \"MONEY1234\"\n   * @example \"Abc123def\"\n   */\n  code: string;\n}\n\n/**\n * Returns the request id header from the ajax cart api response. Capturing this value is important\n * when helping Shopify support investigate issues.\n *\n * This does not error. If there is a problem, this returns null or undefined.\n */\nfunction getRequestId(response: Response) {\n  try {\n    return response.headers.get('X-Request-Id');\n  } catch (error) {\n    // ignore\n  }\n}\n\n/**\n * Returns whether the given value is shaped like a fetch Response.\n *\n * The return type of fetch is a contract, not a verified fact. A third party that overrides the\n * global fetch can resolve it to null, undefined, or some unrelated value, in which case the very\n * next property access throws an opaque error with no indication that a bad override was the cause.\n * This checks structurally for the members this module reads rather than with an instanceof, so a\n * conforming fetch polyfill that returns its own Response class still passes while a missing or\n * malformed value is trapped at the point it is received.\n *\n * This type check does not technically prove that the value is a genuine Response object, only that\n * it has the expected shape. We are intentionally lying with types here, so that we can leverage\n * the type inference to support the subset of use cases this module actually relies on. More over,\n * this lying is ok, as the call to `response = await fetch` is also lying because it overly trusts\n * that fetch returns value of type Response. We are going to keep trying to \"make fetch happen\"\n * regardless.\n *\n * ## War Story\n *\n * As much as it seems like this is an insane and irrationally paranoid check, you would be\n * surprised. For example, in the wild, we observed a third party vendor that hooked global fetch,\n * had an error in their hook, and simultaneously, another vendor's third party polyfill that only\n * defined fetch without also defining Response, leading to even more obscure cases when older\n * browsers were involved. Then, to make matters worse, a fix was implemented leveraging the clean\n * global fetch using an iframe content window hack to work around the inability to cleanly capture\n * the native fetch before it was erroneously hooked, and this broke certain expressions that use\n * instanceof because instanceof has unintuitive behavior when checking instanceof of a value from a\n * different global context, e.g. from an iframe. A web of lies!\n */\nfunction isResponse(value: any): value is Response {\n  if (value === null || typeof value !== 'object') {\n    return false;\n  }\n\n  if (typeof value.ok !== 'boolean') {\n    return false;\n  }\n\n  if (typeof value.status !== 'number') {\n    return false;\n  }\n\n  if (typeof value.text !== 'function') {\n    return false;\n  }\n\n  if (typeof value.json !== 'function') {\n    return false;\n  }\n\n  return true;\n}\n\n/**\n * Attaches a single piece of context onto an error before it is thrown.\n *\n * The target may be a value caught upstream rather than one constructed here, and such a value can\n * carry a non-writable property, such as a DOMException whose code is a read-only accessor, or be\n * frozen outright, in which case assigning to it throws. Decorating an error must never itself\n * fail, so a rejected assignment is swallowed. This context is best effort metadata, so the\n * property is skipped when it cannot be set, leaving the error otherwise intact to be thrown and\n * handled by the caller.\n */\nfunction decorateError(error: any, key: string, value: unknown) {\n  try {\n    error[key] = value;\n  } catch (error) {\n    // The target rejects this property. Skip it rather than letting decoration fail.\n  }\n}\n\n/**\n * Returns an absolute url for the given path.\n *\n * It would be nice to just use a relative url in fetch. However, we see real world errors about\n * requests including credentials. Visitors might have buggy extensions inserting document.baseURI\n * or they are browsing the site in some weird way. To work around this, we build up a full url that\n * omits the credentials.\n *\n * Unfortunately, specifying the base url means we have to also solve the problem of determining it.\n * There is currently a desire to avoid location.origin due to browser support. we do not want to\n * touch the document base uri. Use host over hostname since that includes port.\n */\nfunction resolveUrl(path: string) {\n  return new URL(path, `${location.protocol}//${location.host}`);\n}\n\n/**\n * An item to add to the cart\n */\nexport interface AddItem {\n  /**\n   * Variant id\n   *\n   * Shopify says that it supports variant ids as numbers, but we have witnessed strange errors when\n   * using numbers so we impose an artificial limitation on the type here and require strings.\n   */\n  id: string;\n\n  /**\n   * The id of the line item to add the item as a child of. This is used for adding items as\n   * children of value sets and bundles. If not specified, the item is added as a top level line\n   * item.\n   *\n   * @see https://shopify.dev/docs/apps/build/product-merchandising/nested-cart-lines/create-nested-cart-lines#cart-ajax-api\n   */\n  parent_id?: number | string;\n\n  /**\n   * Line item properties to include.\n   *\n   * If the variant does not already exist in the cart, then the logic is straightforward. This will\n   * result in a new line item with these properties.\n   *\n   * If the variant exists in the cart, then the logic is convoluted. If the specified properties\n   * are identical to the existing properties, then the quantity of the existing line item\n   * increments. If the specified properties are different, then a new line item is created with\n   * these properties, resulting in the same variant being present in multiple line items.\n   */\n  properties?: Attributes;\n\n  /**\n   * The amount of the variant to add.\n   *\n   * @see https://shopify.dev/docs/api/ajax/reference/cart#post-locale-cart-add-js\n   */\n  quantity: number;\n\n  selling_plan?: number | string;\n}\n\n/**\n * The response body of a cart add.\n *\n * This one is not a cart at all, unlike the response body of a change or an update. It carries only\n * the items the request asked for, so there is no token, no totals, and no view of the rest of the\n * cart. See the notes on the add function for why the body is not worth reading.\n *\n * None of the functions here parse this body, because a caller that wants the resulting state\n * performs its own get cart afterward. So this type exists to describe what the endpoint answers\n * with rather than to annotate a return value.\n */\nexport interface CartAddResponseBody {\n  /**\n   * The line items the add produced, one per item in the request.\n   *\n   * These look like ordinary cart line items and are typed as such, though a few of the rarer line\n   * item properties have not been seen on one, so treat the shape as the usual line item rather\n   * than a guaranteed match.\n   */\n  items: Item[];\n}\n\n/**\n * Adds items to the cart. At least one item should be specified.\n *\n * This does not fetch the response body. The raw response body does not contain the full cart\n * state. The raw response body may not be the latest state of cart items. The raw response body may\n * contain zero-quantity items still present after prior removal.\n *\n * This validates the response status. If the status is not ok this throws an error.\n *\n * This specifies fetch mode no-store so that the call is not unintentionally cached.\n *\n * Adding an item the cart does not necessarily mean a new line item is created. For each item in\n * the input, Shopify makes a server side determination of whether to update the quantity of an\n * existing line item or add a new line item. The factors in that decision are related to how a line\n * item key is determined, which is poorly documented. Basically, if an item has some different\n * properties, e.g. line item properties, or pricing, then the item will always be added as a new\n * line item, even though the variant id is the same as an existing line item.\n *\n * The line item scripts run on every mutation. The scripts will remove any orphaned value set line\n * items implicitly. Keep this in mind when debugging.\n *\n * The content type header must be specified in the request or Shopify interprets the request as a\n * form submission, which we are not doing here, which leads to an error.\n *\n * Shopify's injected front end code (e.g. Trekkie, Monorail) triggers a console warning when it\n * detects that the number of items requested to be added is less than the number that was added.\n * This is not necessarily an error. It can indicate that the line item scripts removed one or more\n * line items during the add mutation.\n *\n * @see https://shopify.dev/docs/api/ajax/reference/cart#post-locale-cart-add-js\n * @see https://help.shopify.com/en/manual/checkout-settings/script-editor/shopify-scripts\n */\nexport async function cartAdd(items: AddItem[]) {\n  const url = resolveUrl(Shopify.routes.root + 'cart/add.js');\n\n  const init: RequestInit = {\n    body: JSON.stringify({ items }),\n    headers: {\n      'Content-Type': 'application/json'\n    },\n    cache: 'no-store',\n    method: 'POST'\n  };\n\n  let response;\n  try {\n    response = await fetch(url, init);\n  } catch (error) {\n    decorateError(error, 'items', items);\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!isResponse(response)) {\n    const error: any = new Error('fetch did not return a response adding to cart');\n    decorateError(error, 'items', items);\n    decorateError(error, 'response', response);\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!response.ok) {\n    const error: any = new Error(`Received status ${response.status} adding to cart`);\n    decorateError(error, 'items', items);\n    decorateError(error, 'request_id', getRequestId(response));\n    decorateError(error, 'response_status', response.status);\n\n    try {\n      const responseBody = await response.json();\n      decorateError(error, 'response_body', responseBody);\n    } catch (responseConsumeAndParseBodyError) {\n      // ignore\n    }\n\n    // For certain error responses, Shopify provides a clearer error message in the response body.\n    // On some 422 responses a more specific description is present alongside the generic message,\n    // so prefer the description, fall back to the generic message, and otherwise leave the default\n    // received-status message in place. For example, adding a bundle whose component is unavailable\n    // yields a body like the following, where description is meaningful and message is generic:\n    //\n    // { \"description\": \"The bundle product 'Product Title' cannot be added to the cart.\",\n    // \"message\": \"Cart Error\", \"status\": 422 }\n    //\n    // The status property of the response body mirrors the HTTP status code (empirical).\n\n    let message;\n    if (response.status === 422 && typeof error.response_body?.description === 'string') {\n      message = error.response_body.description;\n    } else if (typeof error.response_body?.message === 'string') {\n      message = error.response_body.message;\n    }\n\n    if (message) {\n      decorateError(error, 'message', message);\n    }\n\n    throw error;\n  }\n\n  return response;\n}\n\n/**\n * Either id or line must be specified.\n */\nexport interface CartChangeInput {\n  /**\n   * Can be either the variant id or the line item key.\n   */\n  id?: number | string;\n\n  /**\n   * A cart can have multiple line items that share the same variant_id. For example, when variants\n   * have different line item properties, or automatic discounts create variants at different\n   * prices. To account for this, you can use the line property when updating the cart.\n   *\n   * The value of line is the 1-based index position of the item in the cart.\n   */\n  line?: number;\n\n  /**\n   * The properties property sets the line item properties. Its value must be an object of key-value\n   * pairs.\n   *\n   * Whenever a POST request includes properties, it overwrites the entire properties object. Any\n   * key-value pairs that were already in the properties object are erased.\n   *\n   * It's not possible to set a line item's properties property to an empty object once a value is\n   * set. If you need to remove all line item properties, then you need to remove the entire line\n   * item.\n   *\n   * You can visually hide line item properties at checkout by creating private properties. This\n   * technique might be an alternative to removing a line item when managing properties.\n   */\n  properties?: Record<string, any>;\n\n  /**\n   * The value of the quantity property represents the new quantity you want for the line item. It\n   * must be an integer.\n   *\n   * You can remove a line item by setting the quantity to 0.\n   *\n   * It is not clear from the documentation, but based on the description of the selling plan\n   * property, it looks like quantity is actually optional and defaults to 1 when not specified.\n   */\n  quantity?: number;\n\n  /**\n   * The selling_plan property sets the line item selling plan. Its value must be one of the\n   * following:\n   *\n   * * The selling plan ID: To set a specific selling plan for the line item.\n   * * null or an empty string: To remove any selling plan from the line item.\n   *\n   * When specifying the selling_plan property, consider the following:\n   * * You can use only the line property for identifying the line item.\n   * * You should always specify the quantity property. If the quantity isn't specified, then it's\n   *   assumed to be 1.\n   */\n  selling_plan?: number;\n}\n\n/**\n * The response body of a cart change.\n *\n * The two extra arrays are not part of the stored cart. Neither one is present in a get cart\n * response, and a cart update answers with a different extra property instead, so these describe\n * what the change did rather than what the cart holds.\n *\n * None of the functions here parse this body, because a caller that wants the resulting state\n * performs its own get cart afterward. So this type exists to describe what the endpoint answers\n * with rather than to annotate a return value.\n */\nexport interface CartChangeResponseBody extends Cart {\n  /**\n   * The line items the change brought into the cart.\n   *\n   * Nothing has been observed to put an entry in here, including a change that raised a quantity,\n   * so whether an entry looks like a removed one is unconfirmed.\n   */\n  items_added: CartChangeItem[];\n\n  /**\n   * The line items the change took out of the cart.\n   *\n   * A removal driven from a cart drawer, which identifies the line by its position rather than by\n   * variant, does report the removed line here. A removal that identifies the line by variant id\n   * has come back empty, so what qualifies is not settled.\n   */\n  items_removed: CartChangeItem[];\n}\n\n/**\n * An entry in one of the two arrays a cart change response carries.\n *\n * This is undocumented and the shape here is what has been observed on a removal. It is close\n * enough to a line item to be mistaken for one and it is not one, so a few differences are worth\n * knowing. The id is not the variant id, the line key arrives under a different property name, and\n * the price is a decimal string rather than an integer in subunits. It also carries no line item\n * properties, no weight, no discount details, and no parent relationship.\n */\nexport interface CartChangeItem {\n  /**\n   * An opaque identifier for the entry, formatted as a uuid.\n   *\n   * This is not the variant id, unlike the id of a cart line item.\n   */\n  id: string;\n\n  image: string;\n\n  presentment_price: number;\n\n  /**\n   * The price as a decimal string, in the presentment currency.\n   *\n   * Note that this is not an integer in currency subunits, which is how a price is expressed\n   * everywhere else in the cart.\n   *\n   * @example \"289.00\"\n   */\n  price: string;\n\n  product_id: number;\n\n  /**\n   * The title of the product, which here includes the variant.\n   *\n   * Note that a cart line item excludes the variant from this property and only combines the two in\n   * its title.\n   *\n   * @example \"TurboStyle - Blush\"\n   */\n  product_title: string;\n\n  product_type: string;\n  quantity: number;\n  sku: string;\n  title: string;\n  untranslated_product_title: string;\n  untranslated_variant_title: string;\n  url: string;\n  variant_id: number;\n  variant_title: string;\n  vendor: string;\n\n  /**\n   * The line item key, which a cart line item exposes as its key instead.\n   *\n   * @example \"47267055599716:fa232aa1a0b5b250164c9843d7a2ee6c\"\n   */\n  view_key: string;\n}\n\n/**\n * Use the /{locale}/cart/change.js endpoint to change the quantity, properties, and selling_plan\n * properties of a cart line item. Only items already in your cart can be changed, and you can\n * change only one line item at a time.\n *\n * The POST data requires either an id or line property to identify the line item to be changed.\n *\n * The value of the id property can be one of the following:\n *\n * * The line item's variant_id\n * * The line item's key\n *\n * A cart can have multiple line items that share the same variant_id. For example, when variants\n * have different line item properties, or automatic discounts create variants at different prices.\n * Because of this, it's recommended to use the line item key to ensure that you're only changing\n * the intended line item.\n *\n * If the item that you're attempting to change isn't already in the cart, then\n * /{locale}/cart/change.js doesn't add it. Instead, it returns a 404 error.\n *\n * The documentation states that the response body is the JSON of the cart. This exposes the\n * response object but does not fetch the response body.\n *\n * ### Removing out of stock items\n * It is unclear but based on a conversation with Shopify support, prefer to use cart/change instead\n * of cart/update to avoid issues with removing an out of stock item from the cart. Using update\n * results in a 422 error but supposedly cart/change does not.\n */\nexport async function cartChange(input: CartChangeInput) {\n  const url = resolveUrl(Shopify.routes.root + 'cart/change.js');\n\n  const init: RequestInit = {\n    body: JSON.stringify(input),\n    cache: 'no-store',\n    headers: {\n      'Content-Type': 'application/json'\n    },\n    method: 'POST'\n  };\n\n  let response;\n  try {\n    response = await fetch(url, init);\n  } catch (error) {\n    decorateError(error, 'input', input);\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!isResponse(response)) {\n    const error: any = new Error('fetch did not return a response changing cart');\n    decorateError(error, 'request_body', input);\n    decorateError(error, 'response', response);\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!response.ok) {\n    const error: any = new Error(`Received status ${response.status} changing cart`);\n    decorateError(error, 'request_body', input);\n    decorateError(error, 'request_id', getRequestId(response));\n\n    try {\n      decorateError(error, 'response_body', await response.json());\n    } catch (consumeError) {\n      // ignore\n    }\n\n    decorateError(error, 'response_status', response.status);\n    decorateError(error, 'response_url', response.url);\n\n    // For certain error responses, Shopify provides a clearer error message in the response body.\n    // On some 422 responses a more specific description is present alongside the generic message,\n    // so prefer the description, fall back to the generic message, and otherwise leave the default\n    // received-status message in place. Shopify's generic message on these is literally \"Cart\n    // Error\", so promoting it over the description would discard the only meaningful text.\n    //\n    // The status property of the response body mirrors the HTTP status code (empirical).\n\n    let message;\n    if (response.status === 422 && typeof error.response_body?.description === 'string') {\n      message = error.response_body.description;\n    } else if (typeof error.response_body?.message === 'string') {\n      message = error.response_body.message;\n    }\n\n    if (message) {\n      decorateError(error, 'message', message);\n    }\n\n    throw error;\n  }\n\n  return response;\n}\n\n/**\n * Use the GET /{locale}/cart.js endpoint to get the cart as JSON.\n *\n * All monetary properties are returned in the customer's presentment currency. To check the\n * customer's presentment currency, you can use the currency field in the response.\n *\n * @see https://shopify.dev/docs/api/ajax/reference/cart#get-locale-cart-js\n */\nexport async function cartFetch() {\n  const url = resolveUrl(Shopify.routes.root + 'cart.js');\n\n  let response;\n  try {\n    response = await fetch(url, { cache: 'no-store' });\n  } catch (error) {\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!isResponse(response)) {\n    const error: any = new Error('fetching cart did not return a valid response');\n    decorateError(error, 'response', response);\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!response.ok) {\n    const error: any = new Error(`Received status ${response.status} fetching cart`);\n    decorateError(error, 'request_id', getRequestId(response));\n    decorateError(error, 'status', response.status);\n    decorateError(error, 'url', response.url);\n\n    try {\n      decorateError(error, 'response_body', await response.json());\n    } catch (consumeError) {\n      // ignore\n    }\n\n    throw error;\n  }\n\n  // Using response.json() obscures errors and prevents us from being able to see what the raw\n  // response body looks like. The usual cases of errant response bodies include:\n  //\n  // - default response bodies for errors, which are using plain text or html\n  // - firewall interceptions that produce html as a response, usually html\n  // - wifi connect interstitial page load, usually html\n  //\n  // So we are intentionally using response.text() and JSON.parse.\n\n  let responseBodyText;\n  try {\n    responseBodyText = await response.text();\n  } catch (error) {\n    decorateError(error, 'status', response.status);\n    decorateError(error, 'url', response.url);\n    throw error;\n  }\n\n  let responseBodyObject;\n  try {\n    responseBodyObject = JSON.parse(responseBodyText);\n  } catch (error) {\n    decorateError(error, 'response_body', responseBodyText);\n    decorateError(error, 'status', response.status);\n    decorateError(error, 'url', response.url);\n    throw error;\n  }\n\n  if (!isCart(responseBodyObject)) {\n    const error: any = new Error('Unexpected get cart response body');\n    decorateError(error, 'response_body', responseBodyObject);\n    decorateError(error, 'status', response.status);\n    decorateError(error, 'url', response.url);\n    throw error;\n  }\n\n  return responseBodyObject;\n}\n\nexport interface CartUpdateInput {\n  /**\n   * New cart attributes. Existing cart attributes are overwritten.\n   */\n  attributes?: Attributes;\n\n  /**\n   * Beta shopify feature. Essentially works like Storefront discount apply mutation.\n   *\n   * Pass an empty string to remove discount(s).\n   */\n  discount?: string;\n\n  /**\n   * Cart note.\n   */\n  note?: string;\n\n  /**\n   * Even though Shopify supports specifying variant ids as numbers, we impose the restriction that\n   * the property names of the updates object must be strings due to some strange looking errors\n   * when specifying numbers.\n   */\n  updates?: Record<string, any>;\n}\n\n/**\n * The response body of a cart update.\n *\n * The changelog is not part of the stored cart. It is absent from a get cart response, and a cart\n * change answers with two differently named arrays instead, so it describes what the update did\n * rather than what the cart holds.\n *\n * None of the functions here parse this body, because a caller that wants the resulting state\n * performs its own get cart afterward. So this type exists to describe what the endpoint answers\n * with rather than to annotate a return value.\n */\nexport interface CartUpdateResponseBody extends Cart {\n  /**\n   * A summary of what the update brought into the cart.\n   *\n   * This is present on every successful update, including one that only writes attributes or a\n   * note, in which case the arrays inside it are empty.\n   */\n  items_changelog: ItemsChangelog;\n}\n\n/**\n * The summary of what an update brought into the cart.\n *\n * This is undocumented, so the shape here is only what has been observed. The added array is the\n * only property seen so far. A removed counterpart would be the obvious sibling but no update\n * response has produced one, not even one that set a quantity to zero, so it is left out rather\n * than guessed at.\n */\nexport interface ItemsChangelog {\n  /**\n   * The variants the update brought into the cart, and the quantity each one landed at.\n   *\n   * Only a variant that was not in the cart beforehand shows up here. Raising or lowering the\n   * quantity of a line that already exists leaves this empty, as does removing one, so this reports\n   * newly created lines rather than every quantity change.\n   */\n  added: ItemsChangelogAddition[];\n}\n\n/**\n * An entry in the added array of an update's changelog.\n *\n * This is not a line item. It carries only the variant and the quantity, so it cannot stand in for\n * the corresponding entry in the cart's items array.\n */\nexport interface ItemsChangelogAddition {\n  /**\n   * The quantity the line item landed at, not the amount by which it changed.\n   */\n  quantity: number;\n\n  /**\n   * Note that this is a number here, unlike elsewhere in this module where a variant id is\n   * restricted to a string on the request side.\n   */\n  variant_id: number;\n}\n\n/**\n * Performs a cart update.\n *\n * This does not return the cart updated state. You will need to do a followup call to fetch the\n * updated state of the cart. One reason this does also return the updated state is that we must\n * perform a second call to fetch the updated state but not all callers are interested in doing so.\n *\n * This does not return the response body of the call. There is no need to fetch the body because\n * the body does not contain useful data. The response body can contain 0 quantity items and other\n * kinds of strange things.\n *\n * This must specify the content type header. Shopify supports both a JSON request body and a form\n * data request body. Shopify defaults to interpreting requests as form submissions. This uses the\n * JSON format so the header is required.\n *\n * Backend scripts may affect the output.\n *\n * @see https://shopify.dev/docs/api/ajax/reference/cart#post-locale-cart-update-js\n * @see https://shopify.dev/docs/api/ajax/reference/cart#update-line-item-quantities\n * @see https://shopify.dev/docs/api/ajax/reference/cart#private-cart-attributes\n */\nexport async function cartUpdate(input: CartUpdateInput) {\n  const url = resolveUrl(Shopify.routes.root + 'cart/update.js');\n\n  const init: RequestInit = {\n    body: JSON.stringify(input),\n    cache: 'no-store',\n    headers: {\n      'Content-Type': 'application/json'\n    },\n    method: 'POST'\n  };\n\n  let response;\n  try {\n    response = await fetch(url, init);\n  } catch (error) {\n    decorateError(error, 'input', input);\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!isResponse(response)) {\n    const error: any = new Error('fetch did not return a response updating cart');\n    decorateError(error, 'request_body', input);\n    decorateError(error, 'response', response);\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!response.ok) {\n    const error: any = new Error(`Received status ${response.status} updating cart`);\n    decorateError(error, 'request_body', input);\n    decorateError(error, 'request_id', getRequestId(response));\n    decorateError(error, 'response_status', response.status);\n\n    try {\n      const parsedErrorResponseBody = await response.json();\n      decorateError(error, 'response_body', parsedErrorResponseBody);\n    } catch (consumeError) {\n      // ignore\n    }\n\n    // For certain error responses, Shopify provides a clearer error message in the response body.\n    // On some 422 responses a more specific description is present alongside the generic message,\n    // so prefer the description, fall back to the generic message, and otherwise leave the default\n    // received-status message in place. Shopify's generic message on these is literally \"Cart\n    // Error\", so promoting it over the description would discard the only meaningful text.\n    //\n    // The status property of the response body mirrors the HTTP status code (empirical).\n\n    let message;\n    if (response.status === 422 && typeof error.response_body?.description === 'string') {\n      message = error.response_body.description;\n    } else if (typeof error.response_body?.message === 'string') {\n      message = error.response_body.message;\n    }\n\n    if (message) {\n      decorateError(error, 'message', message);\n    }\n\n    throw error;\n  }\n\n  return response;\n}\n\n/**\n * Returns whether the error indicates the add failed because one of the products is out of stock\n * (not available for sale). In this case Shopify provides an error response body that looks like\n * this:\n *\n * ```json\n * {\n *   \"description\": \"All 0 <product title> are in your cart\",\n *   \"message\": \"Cart Error\",\n *   \"status\": 422\n * }\n * ```\n *\n * ```json\n * {\n *   \"description\": \"The product '<title>' is already sold out.\",\n *   \"message\": \"Cart Error\",\n *   \"status\": 422\n * }\n * ```\n *\n * This error can happen both when adding an item or updating the quantity of an item.\n *\n * This assumes error messages are in English.\n */\nexport function isUnavailableError(error: any) {\n  if (error instanceof Error && 'response_body' in error && isObject(error.response_body) &&\n    error.response_body.status === 422) {\n    const description = error.response_body.description;\n    if (typeof description === 'string') {\n      // this one is only present in the add error response\n      if (description.startsWith('All 0') && description.includes('in your cart')) {\n        return true;\n      }\n\n      // this string is sometimes present in the update error response, not just the add response\n      if (description.includes('already sold out')) {\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\n/**\n * Returns whether the error indicates the add failed because the shopper already holds the item's\n * per-order purchase limit in their cart. Shopify provides an error response body that looks like\n * this:\n *\n * ```json\n * {\n *   \"description\": \"The maximum quantity of this item is already in your cart.\",\n *   \"message\": \"The maximum quantity of this item is already in your cart.\",\n *   \"status\": 422\n * }\n * ```\n *\n * This assumes error messages are in English.\n */\nexport function isMaxQuantityError(error: any) {\n  if (error instanceof Error && 'response_body' in error && isObject(error.response_body) &&\n    error.response_body.status === 422) {\n    const description = error.response_body.description;\n    if (typeof description === 'string') {\n      const normalizedDescription = description.toLowerCase();\n      if (normalizedDescription.includes('maximum quantity of this item is already in your cart')) {\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\n/**\n * Returns whether the error indicates the add failed because the request mixed a bundle product and\n * an add-on in the same `cart/add.js` call, which Shopify does not allow. Shopify provides an error\n * response body that looks like this:\n *\n * ```json\n * {\n *   \"description\": \"Bundle products and add-ons cannot be mixed\",\n *   \"message\": \"Bundle products and add-ons cannot be mixed\",\n *   \"status\": 422\n * }\n * ```\n *\n * This assumes error messages are in English.\n */\nexport function isBundleAddonMixError(error: any) {\n  if (error instanceof Error && 'response_body' in error && isObject(error.response_body) &&\n    error.response_body.status === 422) {\n    const description = error.response_body.description;\n    if (typeof description === 'string') {\n      const normalizedDescription = description.toLowerCase();\n      if (normalizedDescription.includes('bundle products and add-ons cannot be mixed')) {\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\n/**\n * Returns whether the error indicates the add failed because the selling plan sent with the item is\n * not one Shopify will accept for that variant. This happens when a rendered page carries a selling\n * plan ID (for example a pre-order deferred-payment plan) that has since become stale: the variant\n * came back in stock, or the plan was edited or removed, after the page was rendered but before the\n * shopper clicked add. Shopify responds with a body that looks like this:\n *\n * ```json\n * {\n *   \"description\": \"Cannot apply selling plan to variant\",\n *   \"message\": \"Cart Error\",\n *   \"status\": 422\n * }\n * ```\n *\n * This assumes error messages are in English.\n */\nexport function isInvalidSellingPlanError(error: any) {\n  if (error instanceof Error && 'response_body' in error && isObject(error.response_body) &&\n    error.response_body.status === 422) {\n    const description = error.response_body.description;\n    if (typeof description === 'string') {\n      if (description.includes('Cannot apply selling plan to variant')) {\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\n/**\n * Returns whether the given value looks like a cart object.\n *\n * This is not a complete validation. This is a shallow check that examines whether the value is an\n * object containing an items property that is an array. Any value that meets that criteria\n * satisfies the meaning of a cart.\n */\nexport function isCart(value: any): value is Cart {\n  return isObject(value) && Array.isArray(value.items);\n}\n\nfunction isObject(value: any): value is Record<string, any> {\n  return typeof value === 'object' && value !== null;\n}\n","const rnds8 = new Uint8Array(16);\nexport default function rng() {\n    return crypto.getRandomValues(rnds8);\n}\n","import validate from './validate.js';\nconst byteToHex = [];\nfor (let i = 0; i < 256; ++i) {\n    byteToHex.push((i + 0x100).toString(16).slice(1));\n}\nexport function unsafeStringify(arr, offset = 0) {\n    return (byteToHex[arr[offset + 0]] +\n        byteToHex[arr[offset + 1]] +\n        byteToHex[arr[offset + 2]] +\n        byteToHex[arr[offset + 3]] +\n        '-' +\n        byteToHex[arr[offset + 4]] +\n        byteToHex[arr[offset + 5]] +\n        '-' +\n        byteToHex[arr[offset + 6]] +\n        byteToHex[arr[offset + 7]] +\n        '-' +\n        byteToHex[arr[offset + 8]] +\n        byteToHex[arr[offset + 9]] +\n        '-' +\n        byteToHex[arr[offset + 10]] +\n        byteToHex[arr[offset + 11]] +\n        byteToHex[arr[offset + 12]] +\n        byteToHex[arr[offset + 13]] +\n        byteToHex[arr[offset + 14]] +\n        byteToHex[arr[offset + 15]]).toLowerCase();\n}\nfunction stringify(arr, offset = 0) {\n    const uuid = unsafeStringify(arr, offset);\n    if (!validate(uuid)) {\n        throw TypeError('Stringified UUID is invalid');\n    }\n    return uuid;\n}\nexport default stringify;\n","import rng from './rng.js';\nimport { unsafeStringify } from './stringify.js';\nfunction v4(options, buf, offset) {\n    if (!buf && !options && crypto.randomUUID) {\n        return crypto.randomUUID();\n    }\n    return _v4(options, buf, offset);\n}\nfunction _v4(options, buf, offset) {\n    options = options || {};\n    const rnds = options.random ?? options.rng?.() ?? rng();\n    if (rnds.length < 16) {\n        throw new Error('Random bytes length must be >= 16');\n    }\n    rnds[6] = (rnds[6] & 0x0f) | 0x40;\n    rnds[8] = (rnds[8] & 0x3f) | 0x80;\n    if (buf) {\n        offset = offset || 0;\n        if (offset < 0 || offset + 16 > buf.length) {\n            throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);\n        }\n        for (let i = 0; i < 16; ++i) {\n            buf[offset + i] = rnds[i];\n        }\n        return buf;\n    }\n    return unsafeStringify(rnds);\n}\nexport default v4;\n","/**\n * Returns a deep clone of the given value.\n *\n * Prefers structuredClone and falls back to a JSON round trip. The fallback covers both an\n * environment without structuredClone and one whose structuredClone cannot be trusted. On the\n * storefront a compromised browser can leave a monkey-patched structuredClone that returns\n * undefined for a real object rather than cloning it, which otherwise surfaces as confusing\n * property-access failures far from the cause. The values cloned through here are plain data that\n * round trips faithfully through JSON, so the fallback is equivalent rather than a degradation.\n */\nexport function copy<T>(value: T) {\n  if (typeof structuredClone === 'function') {\n    const cloned = structuredClone(value);\n\n    // A healthy structuredClone returns a nullish result only for a nullish input, so a nullish\n    // clone of a non-nullish value means the primitive cannot be trusted. Fall through to the\n    // serialization clone in that case.\n\n    const inputPresent = value !== null && value !== undefined;\n    const cloneMissing = cloned === null || cloned === undefined;\n\n    let trustworthy = true;\n    if (inputPresent && cloneMissing) {\n      trustworthy = false;\n    }\n\n    if (trustworthy) {\n      return cloned;\n    }\n  }\n\n  return <T>JSON.parse(JSON.stringify(value));\n}\n","import type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { containsJavascriptWord } from '../../untrusted-value.js';\n\n/**\n * Returns whether a discount code string is well-formed. This does not guarantee the discount code\n * is valid and in the applicable state. That is why this function is not named isValid.\n *\n * This tolerates any kind of input so as to provide a convenient API. The caller is not obligated\n * to check that the input is a string beforehand. This function exists to avoid that boilerplate.\n * However, callers should be providing values that are strings.\n */\nexport function isWellFormed(value: any) {\n  if (typeof value !== 'string') {\n    return false;\n  }\n\n  if (value.length === 0) {\n    return false;\n  }\n\n  // This catches abuse of path parameters\n  if (value.includes('/') || value.includes('\\\\')) {\n    return false;\n  }\n\n  if (value.includes(' ')) {\n    return false;\n  }\n\n  const upper = value.toUpperCase();\n\n  // This catches common js bugs in the calling code\n  if (upper === 'UNDEFINED' || upper === 'NULL') {\n    return false;\n  }\n\n  if (containsJavascriptWord(upper)) {\n    return false;\n  }\n\n  return true;\n}\n\n/**\n * Returns normalized array of discount code strings currently applied to the cart.\n *\n * This filters out codes from the cart's discount_codes array property where the applicable value\n * is not true. That is what distinguishes this function from {@link getDiscountCodes}.\n *\n * This normalizes the codes. Shopify does not guarantee the code strings are uppercase. This will\n * uppercase.\n *\n * Tolerates null/undefined cart. Returns an empty array. This is safe to use without checking\n * whether cart is defined.\n *\n * Tolerates invalid discount_codes property. This supports a rare case where the cart was SSRed\n * correctly, but then not fetched. The SSR state is missing the property. In this case an empty\n * array is returned.\n */\nexport function getApplicableDiscountCodes(cart: Cart) {\n  if (!cart) {\n    return [];\n  }\n\n  if (!Array.isArray(cart.discount_codes)) {\n    return [];\n  }\n\n  return cart.discount_codes\n    .filter(code => code.applicable)\n    .map(code => code.code)\n    .map(code => code.toUpperCase());\n}\n\n/**\n * Returns all codes currently in the cart's discount codes array.\n *\n * This includes applicable as well as inapplicable codes. This inclusion of inapplicable codes is\n * what distinguishes this from the function {@link getApplicableDiscountCodes}.\n *\n * The returned codes are always uppercase. This must uppercase the codes returned because Shopify\n * does not guarantee the codes are upper case. Codes applied at checkout are saved in whatever\n * input case was used. The logic that applies codes while shopping can guarantee the input codes\n * are in uppercase but the logic at checkout cannot because the checkout logic is managed by\n * Shopify.\n *\n * It is possible that the discount_codes array property in the input cart is undefined. Shopify\n * does not support rendering the discount_codes array in liquid. There is a situation where the\n * cart state was server side rendered without discount_codes and then we failed to update the state\n * and therefore failed to set the property dynamically during init. This tolerates that so as to\n * guarantee that an array is always returned.\n */\nexport function getDiscountCodes(cart: Cart) {\n  if (!cart) {\n    return [];\n  }\n\n  if (!Array.isArray(cart.discount_codes)) {\n    return [];\n  }\n\n  return cart.discount_codes.map(code => code.code).map(code => code.toUpperCase());\n}\n\n/**\n * Totals up the discount amounts the cart has allocated and returns them as a map. Values are in\n * cents.\n *\n * The keys are discount application titles in uppercase rather than discount codes. For an ordinary\n * code those are the same string, which is what lets a caller match this map against the cart's\n * discount codes. They are not guaranteed to agree though, because Shopify treats the title as the\n * customer facing name of the discount rather than as the code. So a caller can end up holding a\n * title here that matches none of the codes on the cart.\n *\n * A code missing from this map has not necessarily done nothing. It means the cart allocated no\n * amount to it. A free shipping code behaves that way. It sits among the cart's discount codes as\n * applicable while appearing in neither array read here, because shipping is not priced until\n * checkout.\n *\n * Tolerates a null or undefined cart, and a cart missing any of the arrays this reads. Returns an\n * empty map. This is safe to use without checking whether the cart is defined.\n */\nexport function getDiscountAmounts(cart: Cart) {\n  // Shopify keeps discounts in two separate places, split by scope, so we read both to find all of\n  // them. A discount covering the cart as a whole lands in cart_level_discount_applications. A\n  // discount covering particular products lands on each line item holding one of those products, in\n  // that item's line_level_discount_allocations.\n  //\n  // The two do not overlap, which is what makes it safe to add both into one map without counting a\n  // discount twice. We checked this in real carts from both directions. A cart wide code showed up\n  // only in the cart level array with the line allocations empty, and a product code showed up only\n  // on the lines with the cart level array empty.\n  //\n  // A product discount is allocated separately to every line it touches, so its total is the sum of\n  // those per line amounts. That is what the running total is for, and it is also why the same code\n  // can appear many times while we walk the lines.\n  //\n  // Note which field each loop reads, because the two arrays do not name their amounts the same\n  // way. The line loop reads the allocation's amount, which is the share allocated to that one\n  // line. The application sitting inside that allocation carries a total_allocated_amount too, and\n  // in the carts we looked at it happened to equal the same share, but it describes the application\n  // rather than the line, so we do not read it there.\n  //\n  // We skip anything that is not a discount code. Automatically applied discounts and script\n  // applied discounts land in these same arrays, but they carry no code for a caller to match\n  // against, so they do not belong in a map keyed this way.\n\n  const discountAmounts = new Map<string, number>();\n\n  if (!cart) {\n    return discountAmounts;\n  }\n\n  // The isArray checks in the following code are considered acceptable paranoia. We want this to\n  // work in bad environments with partial carts, which we have surprisingly encountered.\n\n  // Discounts covering the cart as a whole\n\n  if (Array.isArray(cart.cart_level_discount_applications)) {\n    for (const application of cart.cart_level_discount_applications) {\n      if (application.type === 'discount_code') {\n        const code = application.title.toUpperCase();\n        const discount = application.total_allocated_amount;\n        const currentTotal = discountAmounts.get(code) || 0;\n        discountAmounts.set(code, currentTotal + discount);\n      }\n    }\n  }\n\n  // Discounts covering particular products, spread over the lines holding those products\n\n  if (Array.isArray(cart.items)) {\n    for (const lineItem of cart.items) {\n      if (!Array.isArray(lineItem.line_level_discount_allocations)) {\n        continue;\n      }\n\n      for (const allocation of lineItem.line_level_discount_allocations) {\n        if (allocation.discount_application.type === 'discount_code') {\n          const code = allocation.discount_application.title.toUpperCase();\n          const discount = allocation.amount;\n          const currentTotal = discountAmounts.get(code) || 0;\n          discountAmounts.set(code, currentTotal + discount);\n        }\n      }\n    }\n  }\n\n  return discountAmounts;\n}\n","import { assert } from '@integrabeauty/assert';\nimport { type Cart, cartFetch as cartFetchOnce, isCart } from '@integrabeauty/shopify-ajax-api';\nimport { v4 as uuidv4 } from 'uuid';\nimport { copy } from '../object.js';\nimport { getApplicableDiscountCodes } from './discount/discount.js';\nimport type { Transaction } from './transaction.js';\n\n/**\n * This event is generated when the state of the cart changes. The event includes the old state and\n * the new state and some deltas.\n *\n * Various cart api calls can result in unexpected behavior. For example, adding an item to cart\n * could cause the line key of unrelated cart lines to change. Therefore, this does not generate\n * deltas based on line key. In addition, each time cart state is obtained, there is no guarantee\n * that the state only changed because of the specified change. For example, a visitor interacting\n * with the same cart in multiple tabs may update the state in one tab, but because the deltas are\n * based on the prior state per tab, the next update in one of the tabs would diff against older\n * state. Therefore, listeners should refrain from operating on deltas and always treat each cart\n * update holistically.\n */\ntype CartUpdatedEvent = CustomEvent<{\n  /**\n   * The new cart state.\n   */\n  cart: Cart;\n\n  /**\n   * Discount codes present in the new cart state that are not present in the old cart state.\n   *\n   * This only includes discount codes. This does not include automatically applied discounts or\n   * script applied discounts.\n   */\n  discount_codes_added: string[];\n\n  /**\n   * Discount codes present in the old cart state that are not present in the new cart state.\n   *\n   * This only includes discount codes. This does not include automatically applied discounts or\n   * script applied discounts.\n   */\n  discount_codes_removed: string[];\n\n  /**\n   * A unique event id. This is useful if tracking this event client side and server side and there\n   * is a need to correlate front end and backend events.\n   */\n  event_id: string;\n\n  /**\n   * Whether this event represents a cart initialization.\n   *\n   * The diffs for an initial update are not useful. In this case the transition is from the empty\n   * state. Do not treat an initial update as a non-initial update.\n   */\n  is_initial?: boolean;\n\n  /**\n   * The previous state of the cart.\n   *\n   * This may be null. For example, when initializing the cart.\n   */\n  previous: Cart;\n\n  /**\n   * Certain transactions provide a description of the context in which the transaction occurred.\n   * For example, two add to cart elements might specify two scenarios when adding to cart, which\n   * then generates an update event with different scenarios, which can then be relayed downstream\n   * to analytics to differentiate in reporting.\n   *\n   * This is primarily an analytics label, but be aware that some consumers also branch on specific\n   * values as behavior, for example suppressing UI reactions to updates the shopper did not\n   * initiate in this tab. Certain values are therefore effectively part of behavior, not only\n   * reporting, so renaming one can change how the UI reacts, not just how a change is reported.\n   *\n   * This is intentionally a broad type. This is not meant to be a type safe enumeration. There are\n   * no heavy safeguards here.\n   */\n  scenario?: string;\n\n  /**\n   * Details of the cart service transaction that triggered this update event.\n   */\n  transaction: {\n    /**\n     * Transaction id\n     */\n    id: number;\n\n    /**\n     * Transaction name\n     */\n    name: string;\n  };\n\n  /**\n   * An array of variant units added to cart. This includes both variants added as new line items\n   * and quantity increases of existing line items.\n   *\n   * A product variant in this array does not contain all the properties of a cart item because we\n   * cannot trivially discern which lines changed when a cart updates.\n   */\n  variants_added: ProductVariant[];\n\n  /**\n   * Variants removed from cart. This can be removal of lines or quantity decreases.\n   *\n   * A product variant does not contain all the properties of a cart item because we cannot\n   * trivially discern which lines changed as a result of a state change.\n   */\n  variants_removed: ProductVariant[];\n}>;\n\n/**\n * Represents a product variant. A Shopify product has one or more variants. A Shopify product with\n * no explicitly configured variant still has a default variant.\n *\n * This should not be confused with a canonical product variant that exists in a single line item in\n * a cart object. This is an amalgamation, a composition of the variant as it exists in one or more\n * line items in a cart. This assumes that price and other properties are the same, apart from the\n * special merged_properties property, which combines the line item properties of the lines\n * containing the variant together.\n */\ninterface ProductVariant {\n  /**\n   * Path to variant image\n   *\n   * @example \"//langehair.com/cdn/shop/products/filename.jpg?v=1680740790\"\n   */\n  image_url: string;\n\n  /**\n   * The combined line item properties of the line items containing this variant\n   *\n   * The key of this object is the property name.\n   *\n   * The value for each key in this object are the distinct values that were merged together.\n   */\n  merged_properties: Record<string, string[]>;\n\n  price: number;\n  product_id: number;\n  product_title: string;\n  product_type: string;\n  quantity: number;\n  sku: string;\n\n  /**\n   * Product variant url (relative)\n   *\n   * @example \"/products/salt-sea-texturizing-spray?variant=41345560477796\"\n   */\n  url: string;\n\n  variant_id: number;\n  variant_title: string;\n}\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'cart-updated': CartUpdatedEvent;\n  }\n}\n\n/**\n * Sets the current state of the cart. This is the in memory state that is stored as the value of a\n * global variable, not the server side state.\n *\n * In memory state is PER TAB PER WINDOW/APP. If someone is browsing in multiple tabs concurrently\n * the state may conflict. The cart token corresponds to a server side cart id and is stored in a\n * cookie that is shared among all tabs, so it is possible for multiple tabs to refer to the same\n * cart object.\n *\n * Always use this method to update the state of the global cart variable. Do not update the state\n * of the global cart variable directly.\n *\n * Throws an error if the given cart is not a cart. This also indicates a more insidious problem\n * where there is no longer a guarantee that client side state reflects server side state, so\n * consider some error handling.\n *\n * This saves a copy of the cart so that future updates by reference to the cart object do not\n * modify the global cart state.\n *\n * Emit a cart updated event that contains some of the differences. This emits the updated event\n * even when there are no changes detected.\n *\n * Any update can result in unrelated cart changes. Changing one line's properties could cause line\n * merging. Also, prior state comes from page state, which is per tab, so interactions in multiple\n * tabs can produce strange behavior.\n *\n * The dispatcher is the target the updated event is dispatched from. It is required but may be\n * null, and it may be any event target, not necessarily an element or window. The target is used as\n * given, with two exceptions that exist only to keep the event reaching window level listeners: a\n * null target dispatches from window, and so does a detached element. A detached element only\n * bubbles up to its detached subtree root, which is not window, so dispatching from it directly\n * would not reach those listeners. Callers should prefer the element most directly responsible for\n * the change, so a connected element carries truthful provenance and bubbles, crossing shadow\n * boundaries, up to the global listeners. Genuinely ambient updates with no originating element,\n * such as init or a cross tab sync, pass window (or null).\n */\nexport function write(dispatcher: EventTarget | null, cart: Cart, transaction: Transaction,\n  scenario?: string, initial = false) {\n  const previous = initial ? null : read();\n\n  // Update global state.\n  assert(isCart(cart), 'Cannot write invalid cart');\n  cart_lifecycle.cart = copy(cart);\n\n  // Prepare for diffing. We group by variant because line keys change in unexpected ways.\n  const oldVariants = groupByVariant(previous);\n  const newVariants = groupByVariant(cart);\n\n  // For discount code deltas, we only examine deltas in codes that both exist and are in the\n  // applicable state in the cart's discount_codes array.\n\n  const prevCodes = getApplicableDiscountCodes(previous);\n  const currCodes = getApplicableDiscountCodes(cart);\n\n  // Emit an event with diffs\n\n  type Detail = CartUpdatedEvent['detail'];\n  const event = new CustomEvent<Detail>('cart-updated', {\n    detail: {\n      cart,\n      discount_codes_added: complement(currCodes, prevCodes),\n      discount_codes_removed: complement(prevCodes, currCodes),\n      event_id: uuidv4(),\n      is_initial: initial,\n      previous,\n      scenario,\n      variants_added: findVariantsAdded(oldVariants, newVariants),\n      variants_removed: findVariantsRemoved(oldVariants, newVariants),\n      transaction: {\n        id: transaction.id,\n        name: transaction.name\n      }\n    },\n    bubbles: true,\n    composed: true\n  });\n\n  // Dispatch the update from whatever target the caller designates; this forms no opinion about\n  // what kind of target it is. The only special handling exists to keep the event reaching the\n  // global listeners: a detached element has no live ancestor chain to bubble through, so it is\n  // swapped for the global scope, and a null target means the global scope outright. Any other\n  // target, including a connected element, is used as given, so it carries truthful provenance and\n  // bubbles, crossing shadow boundaries, up to the global listeners.\n\n  let target: EventTarget;\n  if (dispatcher instanceof Element) {\n    if (dispatcher.isConnected) {\n      target = dispatcher;\n    } else {\n      target = window;\n    }\n  } else if (dispatcher) {\n    target = dispatcher;\n  } else {\n    target = window;\n  }\n\n  target.dispatchEvent(event);\n}\n\n/**\n * Returns a copy of the global cart state.\n *\n * Throws an error if the global cart object is not a cart object.\n */\nexport function read() {\n  assert(isCart(cart_lifecycle.cart), 'Global cart state is invalid on read');\n  return copy(cart_lifecycle.cart);\n}\n\ninterface FetchOptions {\n  /**\n   * The total number of requests to make, counting the first one.\n   */\n  attempts?: number;\n\n  /**\n   * How long to wait in milliseconds before each retry, one entry per retry.\n   */\n  delays?: number[];\n\n  /**\n   * How long in milliseconds to wait on a single attempt before counting it as failed. Leaving it\n   * out means each attempt waits as long as the request takes.\n   */\n  timeout?: number;\n}\n\n/**\n * Reads the server side cart state.\n *\n * This wraps the ajax api call of the same name and is what cart code here should reach for\n * instead. The only things it adds are a few attempts and an optional time limit on each one, so it\n * behaves the same otherwise.\n *\n * A cart read is idempotent and it sits in the middle of things the shopper is waiting on, like\n * adding to cart, so a single failed request is worth another go rather than turning into an error\n * the shopper sees. We make up to a few attempts, waiting a little longer before each retry. If\n * every attempt fails then the error from the last one is thrown and the caller decides what that\n * means. Some callers treat it as fatal, some stay quiet about it, so this forms no opinion.\n *\n * Do not use this to react to a mutation you just made unless you are fine with a repeat request,\n * because each attempt sends a fresh one.\n *\n * Callers can pass options to change how many attempts are made, how long to wait between them, and\n * how long a single attempt gets before it counts as failed. Leaving them out gets the defaults\n * below, and no time limit.\n *\n * There is no limit on the total time across all attempts. The caller is responsible for picking\n * options that keep the worst case acceptable, which is every attempt using its full time limit\n * plus every wait between them.\n */\nexport async function cartFetch(options: FetchOptions = {}) {\n  // How many times we are willing to ask the server before giving up. The first attempt counts, so\n  // this is the total number of requests and not the number of retries on top of one.\n  const limit = options.attempts || 3;\n\n  // How long we wait before each retry. Shopify throttles cart reads, so going right back at it\n  // just piles more requests onto something that already told us to slow down. These stay short as\n  // a balance between waiting long enough to avoid another 429 and waiting so long the user\n  // perceives the UI as broken.\n  const delays = options.delays || [100, 250];\n\n  let lastError: Error;\n\n  for (let attempts = 0; attempts < limit; attempts++) {\n    if (attempts > 0) {\n      const delay = delays[attempts - 1];\n      await resolveAfterTimeout(delay);\n    }\n\n    try {\n      const cart = await cartFetchOnceWithTimeout(options.timeout);\n      return cart;\n    } catch (error) {\n      lastError = error;\n    }\n  }\n\n  throw lastError;\n}\n\n/**\n * Reads the cart once, giving up after the given number of milliseconds. Leaving out the timeout\n * means there is no limit.\n *\n * Giving up does not stop the request. It keeps going in the background and whatever it eventually\n * returns is ignored. That is fine because reading the cart changes nothing, but it means this must\n * never be used for a cart write, since a write that times out here can still land later.\n */\nasync function cartFetchOnceWithTimeout(timeout: number) {\n  if (!timeout) {\n    return cartFetchOnce();\n  }\n\n  let timer: ReturnType<typeof setTimeout>;\n  const timeoutPromise = new Promise<never>((_resolve, reject) => {\n    const error = new Error('Timed out fetching cart');\n    timer = setTimeout(reject, timeout, error);\n  });\n\n  try {\n    const cart = await Promise.race([cartFetchOnce(), timeoutPromise]);\n    return cart;\n  } finally {\n    clearTimeout(timer);\n  }\n}\n\n/**\n * Returns a promise which resolves after the given timeout\n */\nfunction resolveAfterTimeout(milliseconds: number) {\n  return new Promise(resolve => {\n    setTimeout(resolve, milliseconds);\n  });\n}\n\ntype ProductVariantMap = Record<string, ProductVariant>;\n\n/**\n * Aggregates cart line items by variant. Returns the aggregated variants as an object where each\n * key of the object is a variant id and each value is the corresponding variant object.\n *\n * This does some complex stuff. One key thing to constantly point out is that a variant can appear\n * in more than one line item in a cart. There are several reasons why. Shopify generally refers to\n * this as a variant split. Variant splits happen for reasons such as having different line item\n * properties, or different prices as a result of allocated discounted applications, or because of\n * more rare reasons like product subscriptions. Because we are grouping by variant id, we have to\n * invent some novel ways of combining the different values of the line items containing the same\n * variant.\n *\n * To combine the line item properties, we invent a new property, merged_properties, that contains\n * the properties of each line item. In each merged properties value, there is an array of values.\n * When appending to the values array, we only append unique values (using exact value comparison).\n *\n * There are many downstream concerns. Here is one example. When relaying an add to cart event to a\n * third party analytics system, we sometimes want the product's image url. This function's output\n * is the input to the variants_added array of the cart updated event emitted as a result of a cart\n * write. So we have to embed the image url here in the variant object so that it can be accessed\n * without having to do some kind of later lookup.\n *\n * @param cart the input cart to aggregate\n */\nfunction groupByVariant(cart: Cart) {\n  const output: ProductVariantMap = {};\n\n  // It is important to return null here, and not the empty object, so that later functions that use\n  // this output can trivially distinguish between transitions from null state and transitions from\n  // empty cart state. The transition from null state happens on init.\n\n  if (!cart) {\n    return null;\n  }\n\n  for (const item of cart.items) {\n    const variant = output[item.variant_id.toString()];\n    if (variant) {\n      // Merge the item's properties into the variant's properties.\n      for (const property in item.properties || {}) {\n        variant.merged_properties[property] = variant.merged_properties[property] || [];\n        const value = item.properties[property];\n        if (!variant.merged_properties[property].includes(value)) {\n          variant.merged_properties[property].push(value);\n        }\n      }\n\n      variant.quantity += item.quantity;\n    } else {\n      // Merge in the initial properties, where each value is a one value array.\n      const properties: Record<string, string[]> = {};\n      for (const key in item.properties || {}) {\n        properties[key] = [item.properties[key]];\n      }\n\n      output[item.variant_id.toString()] = {\n        image_url: item.image,\n        merged_properties: properties,\n        // eslint-disable-next-line @typescript-eslint/no-deprecated\n        price: item.price,\n        product_id: item.product_id,\n        product_title: item.product_title,\n        product_type: item.product_type,\n        quantity: item.quantity,\n        sku: item.sku,\n        url: item.url,\n        variant_id: item.variant_id,\n        variant_title: item.variant_title\n      };\n    }\n  }\n\n  return output;\n}\n\n/**\n * Returns an array of variant data objects that were added as a result of the write mutation.\n *\n * This tolerates nulls\n *\n * @param c0map variants in old cart state\n * @param c1map variants in new cart state\n * @returns variants added array\n */\nfunction findVariantsAdded(c0map: ProductVariantMap, c1map: ProductVariantMap) {\n  // When the cart transitions from the null state, there are no previous variants. This means that\n  // all variants in the new state have been added. Transitioning from null can mean multiple\n  // things. It can mean either that cart state was initialized on page load, or that cart state was\n  // recovered from error. Because of the ambiguity we are not emitting adds for transitions from\n  // the null state.\n\n  if (!c0map) {\n    return [];\n  }\n\n  // Figure out which variants had quantity increases in any of the line items containing those\n  // variants. Any variant that had a quantity increase is referred to here as an increment. In\n  // addition, look for any variants that are present in the new state but not in the old state, in\n  // which case their full quantity is the quantity added.\n\n  const increments: ProductVariantMap = {};\n  for (const key in c1map) {\n    if (c0map[key]) {\n      // In this branch, the variant was present in the old map and is present also in the new map.\n\n      if (c1map[key].quantity > c0map[key].quantity) {\n        increments[key] = copy(c1map[key]);\n        increments[key].quantity = c1map[key].quantity - c0map[key].quantity;\n      } else {\n        // ignore 0 quantity change and negative quantity change (decrements).\n      }\n    } else {\n      // In this branch, the variant is present in the new map but not in the old map.\n\n      increments[key] = copy(c1map[key]);\n    }\n  }\n\n  return Object.keys(increments).map(key => increments[key]);\n}\n\nfunction findVariantsRemoved(c0map: ProductVariantMap, c1map: ProductVariantMap) {\n  if (!c0map) {\n    return [];\n  }\n\n  const decrements: ProductVariantMap = {};\n  for (const key in c0map) {\n    if (c1map[key]) {\n      if (c1map[key].quantity < c0map[key].quantity) {\n        decrements[key] = copy(c1map[key]);\n        decrements[key].quantity = c0map[key].quantity - c1map[key].quantity;\n      } else {\n        // ignore 0 quantity change\n      }\n    } else {\n      // if the old variant is not found in the updated cart, all quantity was removed\n      decrements[key] = copy(c0map[key]);\n    }\n  }\n\n  return Object.keys(decrements).map(key => decrements[key]);\n}\n\n/**\n * Return all values present in array a that are not present in array b.\n *\n * The complement of a set is the set that includes all the elements of the universal set that are\n * not present in the given set. Here, a is the universal set and b is the given set.\n */\nfunction complement(a: string[], b: string[]) {\n  assert(Array.isArray(a), 'complement parameter \"a\" is not an array');\n  assert(Array.isArray(b), 'complement parameter \"b\" is not an array');\n  return a.filter(value => !b.includes(value));\n}\n","/**\n * Returns a timestamp in milliseconds for measuring durations.\n *\n * This decorates performance.now().\n *\n * This tries to use the native high-resolution epoch, falling back to Date.now().\n *\n * If native throws, this still returns a value, but it logs a message as a side effect.\n *\n * This happens in a try/catch, so it is safe.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance/now\n */\nexport function now() {\n  try {\n    if (typeof performance?.now === 'function') {\n      return performance.now();\n    }\n  } catch (error) {\n    console.debug(error);\n  }\n\n  return Date.now();\n}\n\n/**\n * Records a named point on the User Timing timeline in devtools.\n *\n * This decorates performance.mark within a try/catch that suppresses errors.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance/mark\n */\nexport function mark(name: string) {\n  try {\n    if (typeof performance?.mark === 'function') {\n      performance.mark(name);\n    }\n  } catch (error) {\n    console.debug(error);\n  }\n}\n\n/**\n * Records a named span between two timestamps on the User Timing timeline in devtools.\n *\n * This decorates performance.measure within a try/catch that suppresses errors.\n *\n * The bounds are explicit timestamps rather than named marks, which avoids the error the marked\n * name form throws when a start mark was never recorded.\n *\n * The detail param is optional and must be structured-cloneable. It should be a plain object. If\n * the value cannot be cloned, then performance.measure throws, which is caught and logged by this\n * wrapper. If specified, the detail shows up in the tooltip in the devtools panel.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance/measure\n */\nexport function measure(name: string, start: number, end: number, detail?: unknown) {\n  try {\n    if (typeof performance?.measure === 'function') {\n      performance.measure(name, { start, end, detail });\n    }\n  } catch (error) {\n    console.debug(error);\n  }\n}\n","import { measure, now } from '../perf.js';\n\ntype CartTransactionStartedEvent = CustomEvent<Transaction>;\ntype CartTransactionCompletedEvent = CustomEvent<Transaction>;\n\n/**\n * The bookkeeping shared by every copy of this module. Each entry bundles its own copy, so the\n * numbers and the timeline have to sit on the window for the copies to agree on them.\n */\ninterface State {\n  /** The number of currently active cart service transactions. */\n  activeCount: number;\n\n  /** Number of cart operations started so far, and the source of transaction ids. */\n  count: number;\n\n  /** A rolling log of recent transaction start and end entries. */\n  timeline: Record<string, any>[];\n}\n\ndeclare global {\n  var cart_transaction: State;\n\n  interface WindowEventMap {\n    'cart-transaction-completed': CartTransactionCompletedEvent;\n    'cart-transaction-started': CartTransactionStartedEvent;\n  }\n}\n\n/**\n * Obtain the shared state, initializing it as needed.\n *\n * We do not assume the global state is reliably present and instead access state via this function\n * every time because we have seen very surprising errors in the wild to which we wish to remain\n * resilient, even at the cost of generating artificial and inaccurate state.\n */\nfunction getSharedState() {\n  window.cart_transaction = window.cart_transaction || { activeCount: 0, count: 0, timeline: [] };\n\n  const shared = window.cart_transaction;\n\n  if (!Number.isInteger(shared.activeCount) || shared.activeCount < 0) {\n    shared.activeCount = 0;\n  }\n\n  if (!Number.isInteger(shared.count)) {\n    shared.count = 0;\n  }\n\n  if (!Array.isArray(shared.timeline)) {\n    shared.timeline = [];\n  }\n\n  return shared;\n}\n\n/**\n * A transaction represents an action carried out by the cart service. For example, an add to cart\n * transaction or a remove from cart transaction.\n *\n * A transaction is a plain record minted by the start function and completed through the handle it\n * carries. The bookkeeping fields live on the record because completion is a module function that\n * operates on the record rather than a method that closes over it.\n */\nexport interface Transaction {\n  /** The arguments to the start of the transaction */\n  args: any[];\n\n  /** Complete this transaction. Idempotent, and reachable only through the handle. */\n  complete?(didTimeOut?: boolean): void;\n\n  /** Whether completion has already run, so a second call is a no-op */\n  completed: boolean;\n\n  /** The transaction start time in milliseconds */\n  epoch: number;\n\n  /** Transaction unique global id */\n  id: number;\n\n  /** Transaction name, e.g. function name */\n  name: string;\n\n  /**\n   * The transaction start time on the monotonic clock, used only to compute duration.\n   *\n   * This is separate from epoch, which stays wall-clock so it still answers when the transaction\n   * happened. The two clocks have different origins, so never mix them in one subtraction.\n   */\n  startMillis: number;\n\n  /** Timeout timer id */\n  timer?: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Start a cart service transaction and return its handle. Use the handle's complete method to end\n * the transaction.\n */\nexport function start(name: string, ...args: any[]) {\n  const shared = getSharedState();\n\n  // Increment the shared transaction id. A simple monotonic counter ensures that no two\n  // transactions have the same id (but see the caveats mentioned in getting shared state).\n\n  shared.count++;\n\n  const transaction: Transaction = {\n    args,\n    epoch: Date.now(),\n    id: shared.count,\n    name,\n    completed: false,\n    startMillis: now()\n  };\n\n  // Expose complete as a \"method\" of the returned object. Callers are expected to complete the\n  // transaction via its own method. This is better than exporting the top level complete function\n  // as a free function where callers could mistakenly complete the wrong transaction.\n\n  transaction.complete = (didTimeOut = false) => complete(transaction, didTimeOut);\n\n  // Increment the number of active transactions. This should be a transition from 0 to 1.\n\n  shared.activeCount++;\n\n  // If as a result of the increment there is now more than one active transaction, report the\n  // problem but continue. The goal is not to error. Many transactions are able to reconcile from\n  // overlapping flights.\n\n  if (shared.activeCount > 1) {\n    reportConcurrency(transaction);\n  }\n\n  // Record the transaction in the active transactions timeline. This is a fresh object instead of\n  // the original transaction so that it snapshots.\n\n  append({ epoch: transaction.epoch, id: transaction.id, name: transaction.name, type: 'start' });\n\n  // Start a timer that ensures the transaction settles eventually. Ensuring this is important\n  // because the upper layers typically lock down the UI during a transaction, and we want to\n  // prevent locking the UI indefinitely.\n\n  transaction.timer = setTimeout(complete, 30 * 1000, transaction, true);\n\n  console.log('Cart transaction started', transaction.name);\n\n  type Detail = CartTransactionStartedEvent['detail'];\n  const event = new CustomEvent<Detail>('cart-transaction-started', { detail: transaction });\n  dispatchEvent(event);\n\n  return transaction;\n}\n\n/**\n * Complete a transaction.\n *\n * Appends the transaction to the transaction history log.\n *\n * Emits a transaction completed event. Various transactions should complete when done to signal the\n * completion to listeners.\n *\n * This runs either manually whenever the transaction completes, or automatically, if the\n * transaction did not complete quickly enough (e.g. because of an error, omission, or a genuinely\n * long operation that timed out).\n *\n * Transactions should be short-lived. Transactions are automatically considered complete after a\n * certain amount of time so as to guarantee completion. Higher layer interfaces rely on transaction\n * state to lock interfaces for periods of time. Locking must be definite.\n *\n * Completion is idempotent. The timeout safety net and the caller's own completion can both fire\n * for one transaction when a tab is suspended long enough for the operation to outlive the timeout,\n * as happens on a backgrounded mobile tab that resumes. Whichever fires first records the outcome\n * and any later call is a no-op, so the active transaction count decrements exactly once per start\n * and cannot drift negative and defeat the interface locks that read it.\n */\nfunction complete(transaction: Transaction, didTimeOut = false) {\n  // A transaction that already timed out lands here when its operation finally finishes, so we log\n  // how long it really took. That tells a stalled request that eventually came back apart from one\n  // that never did.\n\n  if (transaction.completed) {\n    const duration = Math.round(now() - transaction.startMillis);\n    console.log('Cart transaction already completed', transaction.name, didTimeOut, duration);\n    return;\n  }\n\n  transaction.completed = true;\n\n  clearTimeout(transaction.timer);\n  transaction.timer = undefined;\n\n  append({\n    epoch: Date.now(),\n    id: transaction.id,\n    name: transaction.name,\n    timeout: didTimeOut,\n    type: 'end'\n  });\n\n  const shared = getSharedState();\n\n  if (shared.activeCount > 0) {\n    shared.activeCount--;\n  }\n\n  if (didTimeOut) {\n    console.log('Cart transaction timed out', transaction.name);\n  }\n\n  console.log('Cart transaction completed', transaction.name);\n\n  // Capture performance data representing each transaction's duration.\n\n  measure(`cart-${transaction.name}-${transaction.id}`, transaction.startMillis, now(), {\n    id: transaction.id,\n    name: transaction.name,\n    timeout: didTimeOut\n  });\n\n  type Detail = CartTransactionCompletedEvent['detail'];\n  const event = new CustomEvent<Detail>('cart-transaction-completed', { detail: transaction });\n  dispatchEvent(event);\n}\n\n/**\n * Capture a concurrent transaction in way that observers can see it\n */\nfunction reportConcurrency(transaction: Transaction) {\n  const state = getSharedState();\n\n  // Snapshot the state. The reason for this is because the state that is rendered in the devtools\n  // console is the state of the live reference when the value is viewed in the log, which is after\n  // the time it was recorded. Snapshotting saves the value at the time of recording it.\n  //\n  // This is a shallow snapshot. Since the timeline is append only, we are basically just recording\n  // the timeline up to the time of the snapshot, e.g. basically this is just a partial view of an\n  // array that may have more entries by the time it is expanded but those entries are hidden.\n\n  const snapshot = [...state.timeline];\n\n  console.warn('Concurrent transaction started', {\n    id: transaction.id,\n    name: transaction.name,\n    timeline: snapshot\n  });\n}\n\n/**\n * Add a timeline entry.\n *\n * The type of entry here is intentionally general because this has a relaxed opinion on what gets\n * stored.\n */\nfunction append(entry: Record<string, any>) {\n  const state = getSharedState();\n  const timeline = state.timeline;\n\n  timeline.push(entry);\n\n  const capacity = 50;\n\n  // Keep only the most recent entries so the timeline does not grow without bound over a long\n  // session. Removing from the front drops the oldest; the clamp guards against a negative removal\n  // count while the timeline is still under the cap.\n  // TODO: the clamp may be unnecessary, since splice already treats a negative delete count as\n  // zero, so it is likely defensive rather than required. Verify and drop it if so.\n  timeline.splice(0, Math.max(timeline.length - capacity, 0));\n}\n","import { type Cart } from '@integrabeauty/shopify-ajax-api';\nimport { report } from '../../lib/cart/error.js';\nimport { cartFetch, read, write } from '../../lib/cart/state.js';\nimport { start, type Transaction } from '../../lib/cart/transaction.js';\n\ntype Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;\n\ntype ShopifyActions = NonNullable<typeof Shopify.actions>;\ntype UpdateCartActionFunction = ShopifyActions['updateCart'];\ntype UpdateCartPayload = Parameters<UpdateCartActionFunction>[0];\ntype UpdateCartResult = Awaited<ReturnType<UpdateCartActionFunction>>;\n\ntype CollectionViewedEvent = GlobalEventHandlersEventMap['collection-viewed'];\ntype ProductVariantViewedEvent = WindowEventMap['product-variant-viewed'];\n\ndeclare global {\n  /**\n   * Holds the global state for the standard storefront actions snippet. Created in the snippet\n   * liquid before this module loads.\n   */\n  var standard_storefront_actions: {\n    listener: (event: Event) => void;\n\n    /**\n     * The document events caught so far by the parse-time listener.\n     */\n    queue: Event[];\n\n    template_name: string;\n\n    /**\n     * The event types the parse-time listener is registered for. We read this when we remove that\n     * listener, so the snippet liquid is the only place that lists the types.\n     */\n    types: string[];\n  };\n}\n\n/**\n * Shopify's action controller calls this to open the cart. This is our own custom behavior that\n * overrides the default behavior which is to navigate to the cart page.\n *\n * The default comes close to working with our drawer without any help from us. Shopify looks for a\n * cart-drawer-component element, then a cart-drawer element, and calls open on whichever one it\n * finds. We have a cart-drawer, so it gets that far. Ours carries no open method though, so Shopify\n * gives up and sends the shopper to the cart page. That is why we override. Read out of their\n * shipped standard-actions.js and checked against a real page.\n *\n * We do not use a global library for cart operations. We communicate instructions between webpack\n * bundles using dom events. Therefore this implementation is a simple broadcast.\n *\n * This dispatches from window because the thing triggering this action is not a DOM element.\n *\n * The listener for cart-open-requested handles the already open case. When this action is called\n * and the cart drawer is already open, this is effectively a noop.\n *\n * Due to outstanding technical debt, there is an open issue here when there is an open modal other\n * than the cart drawer. Ordinarily, most of the time (but not perfectly), we preclude the ability\n * to open the cart drawer while another modal is open. This bypasses that restriction. An AI agent\n * can naively bypass. It might run into problems like inert/disabled elements. We can eventually\n * think of a better way to reject the action but it is out of scope right now.\n */\nfunction openCart() {\n  const drawer = document.querySelector('cart-drawer');\n\n  // We are dispatching with animate false intentionally. This is different than what a normal human\n  // visitor experiences. However, it guarantees that the handler is completed by the time it\n  // resolves. I am not yet entirely clear on how agents drive actions, so I am being extra careful\n  // to prevent the agent from running into sequential action issues.\n\n  if (drawer) {\n    console.log('Running openCart action');\n\n    type Detail = WindowEventMap['cart-open-requested']['detail'];\n\n    const event = new CustomEvent<Detail>('cart-open-requested', {\n      detail: {\n        animate: false,\n        source: 'standard-action'\n      }\n    });\n\n    dispatchEvent(event);\n  }\n\n  // The cart-open-requested listener receives the event and opens the cart synchronously so by the\n  // time we are resolving here, the drawer is open, so there is no need to wait for the drawer to\n  // open or check for the drawer to open.\n\n  return Promise.resolve();\n}\n\n/**\n * Shopify's action controller calls this when something outside our code changes the cart, such as\n * an AI agent adding an item. If we leave this alone, Shopify runs its own fallback, which tries an\n * in-place refresh using markup patterns from the Horizon and Dawn themes and reloads the page when\n * neither one matches. Our markup matches neither, so the shopper gets a page reload. Handing\n * Shopify a handler is the only thing that switches that fallback off.\n *\n * We checked both of those tests rather than taking the reload on faith. The Horizon one wants a\n * cart-icon element carrying a renderCartBubble method alongside a cart-items-component carrying an\n * updateQuantity method, and we have neither element. The Dawn one wants cart-drawer, cart-items,\n * cart-drawer-items or cart-notification registered as a custom element whose prototype carries\n * getSectionsToRender. We do register cart-drawer, so that one nearly matches, but ours has no such\n * method. Both tests are read out of Shopify's shipped standard-actions.js and both were evaluated\n * against a real page.\n *\n * We let defaultHandler do the actual mutation, so we are not reimplementing the cart change, only\n * taking over what happens afterward. Then we refetch /cart.js and push that through the same write\n * as any other cart operation, so the drawer and the totals update the way they always do.\n *\n * Going back through /cart.js is deliberate. defaultHandler resolves with the Storefront API's own\n * cart shape, with gid:// ids and cost.totalAmount, which we have watched it do on a live call, and\n * everything downstream of us expects the AJAX cart shape instead. Converting by hand would be easy\n * to get subtly wrong. It is the same cart and the same token either way, and /cart.js has the\n * change by the time we ask, so the extra request costs us a round trip and nothing else.\n *\n * We dispatch from window because there is no originating element. An agent driving the cart is not\n * acting through anything on the page.\n *\n * Shopify passes one more argument than we take, the options, which land third. That comes from\n * reading their shipped standard-actions.js, not from the declaration file, which leaves it easy to\n * miss. Nothing here wants them.\n *\n * We take the payload only to attach it to a failure report, so that whoever reads the report can\n * see what was asked for. The cart write does not need it, since refetching /cart.js already tells\n * us everything. The payload that arrives is normalized, so the cart id is filled in from the\n * cookie by then rather than being whatever the caller passed, and the ids are gid:// by the time\n * we see them.\n *\n * Two kinds of failure arrive here and only one of them is ours to handle.\n *\n * A call that could not run at all throws, a network failure being the usual one, and so does a\n * mutation that came back with no cart. We do not catch those. They propagate, and Shopify's action\n * wraps this, fires shopify:cart:error and hands the failure to the caller. That is what we want,\n * and it is unchanged.\n *\n * A refused mutation is the other kind and it does not throw. It resolves like any other call, with\n * the problem sitting in userErrors on the result, so nothing upstream sees a failure at all. We\n * have to handle that one ourselves.\n *\n * So we read userErrors ourselves. When the mutation was refused we skip the cart write, because\n * nothing changed and writing anyway would fire a whole cart transaction for a change that never\n * happened. We report the refusal the way every other cart failure here is reported, which puts it\n * in front of the drawer's messaging and in front of error monitoring.\n *\n * Our own refetch of /cart.js sits outside both of those. The mutation has already gone through by\n * the time we ask, so a failure there costs us our copy of the cart and nothing else. We swallow it\n * and hand the result back. Letting it out would tell the caller its change failed when it actually\n * succeeded, and Shopify's result carries the real cart anyway.\n *\n * We hand Shopify's result back rather than throwing. Their own default handler resolves on a\n * refused mutation too, so an agent talking to any storefront reads userErrors off the result and\n * never sees a shopify:cart:error for this. Throwing would fire that event and reject the agent's\n * call, and no unmodified store behaves that way.\n *\n * Shopify emits its own shopify:cart:* events around this and it does that before we run, not\n * after. Their docs say the events go out on a successful call, which reads like they come out the\n * far end, but they do not. Shopify dispatches up front and each event carries a promise that their\n * runtime resolves with the result once we are done. So theirs lands first and the transaction we\n * start below fires after it. Read out of their shipped standard-actions.js and watched on a page.\n *\n * Those events are not CustomEvent instances and have no detail property. Shopify uses its own\n * event classes and puts the data straight on the instance, so an add arrives as { action: 'add',\n * context: 'standard-action', lines: [...], promise }. The lines come back normalized to gid:// ids\n * even when the caller passed a raw numeric variant id.\n *\n * There is one event per operation rather than one per call. A payload carrying lines and a note\n * and attributes emits three of them. A payload that both adds and removes emits two events, one\n * for each action, and both of them are the lines update one.\n */\nasync function updateCart(defaultHandler: () => Promise<UpdateCartResult>,\n  payload: UpdateCartPayload) {\n  console.log('Running updateCart action');\n\n  const transaction = start('updateCartAction', payload, 'standard-action');\n\n  let result;\n  try {\n    result = await updateCartWithTransaction(transaction, defaultHandler, payload);\n  } finally {\n    transaction.complete();\n  }\n\n  return result;\n}\n\nasync function updateCartWithTransaction(transaction: Transaction,\n  defaultHandler: () => Promise<UpdateCartResult>, payload: UpdateCartPayload) {\n  const result = await defaultHandler();\n\n  if (result.userErrors?.length > 0) {\n    // We classify by the first entry. A refused mutation normally comes back with a single error,\n    // and when there are several they describe the same refusal, so the rest ride along as inputs\n    // for whoever reads the report.\n\n    const [first] = result.userErrors;\n\n    report(window, {\n      code: mapActionErrorCodeToInternalErrorCode(first.code),\n      error: new Error(first.message),\n      inputs: { payload, user_errors: result.userErrors, warnings: result.warnings },\n      scenario: 'standard-action',\n      transaction\n    });\n\n    return result;\n  }\n\n  let cart: Cart;\n\n  try {\n    cart = await cartFetch();\n  } catch (error) {\n    console.log('Could not fetch the cart after the updateCart action', error);\n    return result;\n  }\n\n  write(window, cart, transaction, 'standard-action');\n\n  return result;\n}\n\n/**\n * Translate a Shopify cart mutation error code into one of our own cart error codes.\n *\n * We reuse the codes our own cart operations already produce instead of minting new ones. That way\n * the shopper gets the message the drawer already shows for the same situation, and the everyday\n * refusals land on codes error monitoring already knows to ignore. A refusal that came through here\n * is still tellable apart from a drawer one, because the transaction name and the scenario ride\n * along on the report.\n *\n * Too many units comes back as MAXIMUM_EXCEEDED rather than as an out of stock code, but the cause\n * is the same inventory limit, so it lands on the same code.\n *\n * A discount code we cannot find maps to the code the discount input uses. The drawer only messages\n * on that one for a cart that had no codes on it already, which it decides from an input we do not\n * set, so nothing is shown. An agent is not typing into the discount field, so there is no shopper\n * mistake to explain here.\n *\n * Anything else falls through to the generic code and reaches error monitoring. Watch for INVALID\n * there. It means the caller asked for something that does not exist, which is a real bug worth\n * seeing rather than an everyday shopper condition.\n */\nfunction mapActionErrorCodeToInternalErrorCode(code: string) {\n  switch (code) {\n    case 'MAXIMUM_EXCEEDED':\n    case 'MERCHANDISE_OUT_OF_STOCK':\n      return 'UPDATE_CART_OOS';\n    case 'DISCOUNT_NOT_FOUND':\n      return 'APPLY_DISCOUNTS';\n    default:\n      return 'UPDATE_CART';\n  }\n}\n\n/**\n * Reconfigure Shopify.actions on document load, and emit certain events on load.\n *\n * In reality, we see that Shopify.actions is not always set.\n *\n * Testing environments can use the test_native_standard_actions to observe what happens without the\n * action overrides.\n *\n * @see https://shopify.dev/docs/api/storefront-events-and-actions/actions\n */\nfunction onDOMContentLoaded(_event?: Event) {\n  removeEventListener('DOMContentLoaded', onDOMContentLoaded);\n\n  if (Shopify.actions && !/test_native_standard_actions/.test(location.search)) {\n    Shopify.actions.openCart.configure({ handler: openCart });\n    Shopify.actions.updateCart.configure({\n      eventTarget: () => document,\n      handler: updateCart\n    });\n  }\n\n  dispatchShopifyPageView();\n\n  // We are taking over from here, so we remove the parse-time listener from every type it was\n  // registered for. That includes DOMContentLoaded, which main already removed. Removing a listener\n  // twice does nothing.\n\n  for (const type of standard_storefront_actions.types) {\n    removeEventListener(type, standard_storefront_actions.listener);\n  }\n\n  for (const event of standard_storefront_actions.queue) {\n    if (isCartViewedEvent(event)) {\n      dispatchShopifyCartView(event.detail.surface);\n    } else if (isCollectionViewedEvent(event)) {\n      dispatchShopifyCollectionView(event);\n    } else if (isProductVariantViewedEvent(event)) {\n      dispatchShopifyProductView(event);\n    }\n  }\n\n  addEventListener('cart-viewed', onCartViewed);\n  addEventListener('collection-viewed', onCollectionViewed);\n  addEventListener('product-variant-viewed', onProductVariantViewed);\n  addEventListener('cart-update-erred', onCartUpdateErred);\n}\n\n/**\n * Following the example set by Shopify Horizon and Dawn themes, we emit a page view event. However,\n * we are not using Shopify's library based approach as it is heavyweight. We only need the type for\n * the event and we can do the rest. We do not expect apps listening for this event to be checking\n * the event using instanceof, so not using their class should be ok. The only real caveat is that\n * the library can change at any time, so we have to keep in sync manually.\n *\n * This event is not supposed to be emitted until after the DOMContentLoaded event. At least, that\n * is what the other themes do, and what some of the docs say.\n *\n * Shopify's themes dispatch from document with bubbles and cancelable.\n *\n * The way that Shopify chose to include custom data in events is awkward. Shopify inserts non-\n * standard properties into the event.\n */\nfunction dispatchShopifyPageView() {\n  type PageViewEvent = Optional<DocumentEventMap['shopify:page:view'], 'page'>;\n\n  const pageViewEvent: PageViewEvent = new Event('shopify:page:view', {\n    bubbles: true,\n    cancelable: true\n  });\n\n  pageViewEvent.page = {\n    template: standard_storefront_actions.template_name,\n    title: document.title,\n    url: location.href\n  };\n\n  document.dispatchEvent(pageViewEvent);\n}\n\nfunction onCartViewed(event: GlobalEventHandlersEventMap['cart-viewed']) {\n  dispatchShopifyCartView(event.detail.surface);\n}\n\n/**\n * Builds and emits the standard Shopify cart view event.\n *\n * Shopify's events library is large and weird and hard to load, so we hand roll.\n */\nfunction dispatchShopifyCartView(surface: string) {\n  let cart: Cart;\n\n  try {\n    cart = read();\n  } catch (error) {\n    console.debug(error);\n    return;\n  }\n\n  type CartViewEvent = Optional<DocumentEventMap['shopify:cart:view'], 'cart' | 'context'>;\n\n  const cartViewEvent: CartViewEvent = new Event('shopify:cart:view', {\n    bubbles: true,\n    cancelable: true\n  });\n\n  // Mimic the values used by Shopify's Horizon and Dawn themes\n  cartViewEvent.context = surface === 'drawer' ? 'dialog' : 'page';\n  cartViewEvent.cart = {\n    cost: {\n      totalAmount: {\n        amount: formatSubunits(cart.total_price, cart.currency),\n        currencyCode: cart.currency\n      }\n    },\n    discountCodes: cart.discount_codes?.map(discountCode => ({\n      applicable: discountCode.applicable,\n      code: discountCode.code\n    })),\n    id: `gid://shopify/Cart/${cart.token}`,\n    lines: cart.items.map(item => ({\n      cost: {\n        totalAmount: {\n          amount: formatSubunits(item.final_line_price, cart.currency),\n          currencyCode: cart.currency\n        }\n      },\n      id: item.key,\n      quantity: item.quantity\n    })),\n    totalQuantity: cart.item_count\n  };\n\n  document.dispatchEvent(cartViewEvent);\n}\n\nfunction onCollectionViewed(event: CollectionViewedEvent) {\n  dispatchShopifyCollectionView(event);\n}\n\n/**\n * Builds and emits the standard Shopify collection view event.\n *\n * Shopify's events library is large and weird and hard to load, so we hand roll.\n *\n * Our own event carries both product counts and this one only has room for a single number.\n * Shopify's declaration does not say which they mean, so we send the filtered one, on the reasoning\n * that a listener hearing about a view wants what the shopper is looking at. If we ever emit their\n * collection update event, that is the one that reports filter changes, and it would be a reason to\n * revisit this.\n *\n * Their payload allows a null id. We always have one, since liquid gives us the collection.\n */\nfunction dispatchShopifyCollectionView(event: CollectionViewedEvent) {\n  type CollectionViewEvent = Optional<DocumentEventMap['shopify:collection:view'], 'collection'>;\n\n  const shopifyEvent: CollectionViewEvent = new Event('shopify:collection:view', {\n    bubbles: true,\n    cancelable: true\n  });\n\n  shopifyEvent.collection = {\n    handle: event.detail.handle,\n    id: `gid://shopify/Collection/${event.detail.id}`,\n    productsCount: event.detail.products_count\n  };\n\n  document.dispatchEvent(shopifyEvent);\n}\n\nfunction onProductVariantViewed(event: ProductVariantViewedEvent) {\n  dispatchShopifyProductView(event);\n}\n\n/**\n * Emits the standard Shopify product view event.\n *\n * Shopify's events library is large and weird and hard to load, so we hand roll.\n *\n * @todo issue 7196 regarding options support and variant price\n */\nfunction dispatchShopifyProductView(event: ProductVariantViewedEvent) {\n  type ProductViewEvent = Optional<DocumentEventMap['shopify:product:view'],\n    'context' | 'product' | 'selectedOptions'>;\n\n  // Intl throws without a currency, so skip the event rather than let that escape the listener.\n  if (!event.detail.currency) {\n    return;\n  }\n\n  // Falls back to the product price, which is the lowest across the variants. See issue 7197.\n  const price = event.detail.variant_price ?? event.detail.price ?? 0;\n\n  const shopifyEvent: ProductViewEvent = new Event('shopify:product:view', {\n    bubbles: true,\n    cancelable: true\n  });\n\n  // There are other context values but we only care about pdps so assume page.\n  shopifyEvent.context = 'page';\n  shopifyEvent.selectedOptions = [];\n  shopifyEvent.product = {\n    handle: event.detail.handle,\n    id: `gid://shopify/Product/${event.detail.product_id}`,\n    selectedVariant: {\n      availableForSale: event.detail.available,\n      id: `gid://shopify/ProductVariant/${event.detail.variant_id}`,\n      price: {\n        amount: formatSubunits(price, event.detail.currency),\n        currencyCode: event.detail.currency\n      },\n      selectedOptions: [],\n      title: event.detail.variant_title\n    },\n    title: event.detail.product_title\n  };\n\n  document.dispatchEvent(shopifyEvent);\n}\n\n/**\n * Relays a cart failure our own cart code reported as the standard Shopify cart error event.\n *\n * A failure that came out of the updateCart standard action is skipped. Shopify already decides\n * what the caller sees for that call, and our override hands their result back instead of throwing\n * so that no error event fires for a refused mutation. Relaying it here would put that event right\n * back. This event is for the rest, meaning a shopper clicking things in our own interface.\n */\nfunction onCartUpdateErred(event: GlobalEventHandlersEventMap['cart-update-erred']) {\n  if (!event.detail || event.detail.scenario === 'standard-action') {\n    return;\n  }\n\n  dispatchShopifyCartError(event.detail.code, event.detail.error);\n}\n\n/**\n * Builds and emits the standard Shopify cart error event.\n *\n * Shopify's events library is large and weird and hard to load, so we hand roll.\n *\n * Their payload wants a plain string for the message. Almost every failure we report carries a real\n * error, but nothing guarantees it, so whatever we were handed gets turned into a string.\n */\nfunction dispatchShopifyCartError(code: string, error: any) {\n  type CartErrorEvent = Optional<DocumentEventMap['shopify:cart:error'], 'code' | 'error'>;\n\n  const cartErrorEvent: CartErrorEvent = new Event('shopify:cart:error', {\n    bubbles: true,\n    cancelable: true\n  });\n\n  cartErrorEvent.code = mapInternalErrorCodeToStandardErrorCode(code);\n  cartErrorEvent.error = error instanceof Error ? error.message : `${error}`;\n\n  document.dispatchEvent(cartErrorEvent);\n}\n\n/**\n * Translate one of our own cart error codes into the code Shopify's cart error event carries.\n *\n * Shopify's codes are a closed list of about forty values and ours are our own vocabulary, so\n * something has to translate. Ours are coarser than theirs and several of ours have no honest match\n * at all, so those land on their catch all rather than on a code that would say something we do not\n * actually know. This covers the codes our cart operations really produce and nothing else, so the\n * long tail of their list, the address validation codes and the delivery ones, never comes up.\n *\n * Running out of stock has no code of its own on their list. The nearest one says the shopper asked\n * for more units than they can have, which is the same inventory limit, so that is where the out of\n * stock codes go. That also matches the mapping we already do in the other direction.\n *\n * A discount we could not apply lands on their missing discount code, which is the only discount\n * code they have.\n *\n * A selling plan the variant refuses lands on their selling plan code, which matches closely, since\n * we are the ones who attached the plan.\n */\nfunction mapInternalErrorCodeToStandardErrorCode(code: string) {\n  switch (code) {\n    case 'ADD_ITEMS_OOS':\n    case 'CHANGE_CART_OOS':\n    case 'UPDATE_CART_OOS':\n      return 'MAXIMUM_EXCEEDED';\n    case 'ADD_ITEMS_INVALID_BACKORDER_SELLING_PLAN':\n    case 'ADD_ITEMS_INVALID_SELLING_PLAN':\n    case 'ADD_ITEMS_INVALID_TBYB_SELLING_PLAN':\n      return 'SELLING_PLAN_NOT_APPLICABLE';\n    case 'APPLY_DISCOUNTS':\n      return 'MISSING_DISCOUNT_CODE';\n    default:\n      return 'INVALID';\n  }\n}\n\n/**\n * Renders an amount in a currency's subunits as the decimal string Shopify's cart events carry, so\n * 1999 US cents comes out as \"19.99\".\n *\n * How many decimal places to show depends on the currency, and asking Intl to format the amount as\n * currency and then reading back what it decided is how Shopify works that out. We do the same. The\n * en locale is theirs too, and it is what keeps the separator a period rather than following\n * whatever the shopper's browser prefers. Grouping is off because this is a machine readable value\n * rather than something a shopper sees.\n */\nfunction formatSubunits(subunits: number, currencyCode: string) {\n  const currencyFormat = new Intl.NumberFormat('en', {\n    currency: currencyCode,\n    style: 'currency'\n  });\n\n  const fractionDigits = currencyFormat.resolvedOptions().maximumFractionDigits;\n\n  const decimalFormat = new Intl.NumberFormat('en', {\n    maximumFractionDigits: fractionDigits,\n    minimumFractionDigits: fractionDigits,\n    style: 'decimal',\n    useGrouping: false\n  });\n\n  return decimalFormat.format(subunits / 100);\n}\n\nfunction isCartViewedEvent(value: any): value is GlobalEventHandlersEventMap['cart-viewed'] {\n  return value instanceof Event && value.type === 'cart-viewed';\n}\n\nfunction isCollectionViewedEvent(value: any): value is CollectionViewedEvent {\n  return value instanceof Event && value.type === 'collection-viewed';\n}\n\nfunction isProductVariantViewedEvent(value: any): value is ProductVariantViewedEvent {\n  return value instanceof Event && value.type === 'product-variant-viewed';\n}\n\n/**\n * This snippet's webpack bundle is loaded async. It can run before, or after, the dom content\n * loaded event. If it loads before, we listen. If it loads after, we replay.\n *\n * This code is intentionally not coupled to the DOM ready library we normally use. This situation\n * is relatively exotic. Shopify injects a script tag that does a module import that defines the\n * Shopify.actions property. That code runs after the document ready state indicates the document is\n * interactive. The dom ready library treats interactive as ready. We cannot treat that situation as\n * ready here. So we avoid that by reacting to DOMContentLoaded with replay for unknown load order,\n * as this approach guarantees Shopify's code has actually run. This avoids the bug where\n * Shopify.actions is sometimes defined or undefined depending on timing.\n *\n * This is particularly hairy because of the non-standard way Shopify injects this code. It is not\n * through content_for_layout. They are doing some strange server-side insert-before-body-end HTML\n * rewriting thing. We cannot even reach for defer because that is in document order and we cannot\n * attach this snippet after theirs because of that wacky rewriting hack.\n */\nfunction main() {\n  removeEventListener('DOMContentLoaded', standard_storefront_actions.listener);\n\n  let loaded = false;\n\n  for (const event of standard_storefront_actions.queue) {\n    if (event.type === 'DOMContentLoaded') {\n      onDOMContentLoaded();\n      loaded = true;\n      break;\n    }\n  }\n\n  if (!loaded) {\n    addEventListener('DOMContentLoaded', onDOMContentLoaded);\n  }\n}\n\nmain();\n"],"names":["decorateError","error","key","value","assignmentError","reason","Error","message","console","log","name","tag","Object","prototype","toString","call","report","dispatcher","detail","target","Element","isConnected","window","event","CustomEvent","bubbles","composed","dispatchEvent","dispatchCartUpdateErred","ignoredCodes","includes","code","_detail$transaction","_detail$transaction2","inputs","scenario","cart","transaction","id","reportError","setTimeout","raise","escalateCartError","assert","expression","AssertionError","constructor","super","this","getRequestId","response","headers","get","isResponse","ok","status","text","json","cart_decorateError","resolveUrl","path","URL","location","protocol","host","async","cartFetch","url","Shopify","routes","root","responseBodyText","responseBodyObject","fetch","cache","href","consumeError","JSON","parse","isCart","isObject","Array","isArray","items","rnds8","Uint8Array","byteToHex","i","push","slice","unsafeStringify","arr","offset","toLowerCase","dist_v4","options","buf","crypto","randomUUID","_ref","_options$random","rnds","random","rng","getRandomValues","length","RangeError","_v4","copy","structuredClone","cloned","trustworthy","stringify","getApplicableDiscountCodes","discount_codes","filter","applicable","map","toUpperCase","read","cart_lifecycle","cartFetchOnceWithTimeout","timeout","timer","timeoutPromise","Promise","_resolve","reject","race","clearTimeout","resolveAfterTimeout","milliseconds","resolve","groupByVariant","output","item","variant","variant_id","property","properties","merged_properties","quantity","image_url","image","price","product_id","product_title","product_type","sku","variant_title","findVariantsAdded","c0map","c1map","increments","keys","findVariantsRemoved","decrements","complement","a","b","now","_performance","performance","debug","Date","getSharedState","cart_transaction","activeCount","count","timeline","shared","Number","isInteger","start","_len","arguments","args","_key","epoch","completed","startMillis","didTimeOut","complete","snapshot","warn","reportConcurrency","append","type","duration","Math","round","undefined","end","_performance3","measure","entry","splice","max","openCart","document","querySelector","animate","source","updateCart","defaultHandler","payload","result","_result$userErrors","userErrors","first","mapActionErrorCodeToInternalErrorCode","user_errors","warnings","limit","attempts","delays","lastError","delay","state_cartFetch","initial","previous","oldVariants","newVariants","prevCodes","currCodes","discount_codes_added","discount_codes_removed","event_id","is_initial","variants_added","variants_removed","write","updateCartWithTransaction","onDOMContentLoaded","_event","removeEventListener","actions","test","search","configure","handler","eventTarget","pageViewEvent","Event","cancelable","page","template","standard_storefront_actions","template_name","title","dispatchShopifyPageView","types","listener","queue","isCartViewedEvent","dispatchShopifyCartView","surface","isCollectionViewedEvent","dispatchShopifyCollectionView","isProductVariantViewedEvent","dispatchShopifyProductView","addEventListener","onCartViewed","onCollectionViewed","onProductVariantViewed","onCartUpdateErred","_cart$discount_codes","cartViewEvent","context","cost","totalAmount","amount","formatSubunits","total_price","currency","currencyCode","discountCodes","discountCode","token","lines","final_line_price","totalQuantity","item_count","shopifyEvent","collection","handle","productsCount","products_count","_event$detail$variant","variant_price","selectedOptions","product","selectedVariant","availableForSale","available","cartErrorEvent","mapInternalErrorCodeToStandardErrorCode","dispatchShopifyCartError","subunits","fractionDigits","Intl","NumberFormat","style","resolvedOptions","maximumFractionDigits","minimumFractionDigits","useGrouping","format","loaded","main"],"sourceRoot":""}