@thorprovider/adapters 5.0.11 → 5.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _thorprovider_types from '@thorprovider/types';
2
- import { Order, Cart, BackendCapabilities, Money, PaymentMethod, FulfillmentOption, CartItem, StockLocation, InventoryLevel, StorefrontConfig, DashboardMetrics, ProviderTypeString, CommerceProvider, AuthProvider, ProviderConfig, AuthConfig, Product, GetProductsOptions, CatalogQuery, CatalogQueryOutcome, FacetSupport, AdvancedSearchProductsOptions, SearchResultMeta, FilterConfig, Collection, GetCollectionsOptions, CollectionProductsOptions, ProductCategory, GetCategoriesOptions, CartLineInput, CartLineUpdate, GetOrdersOptions, GetCustomersOptions, Customer, Address, Region, AdminProduct, PaymentMethodsOptions, StockValidation, StorefrontContext, DesignerConfig, DesignerHistoryEntry, ShippingMethod, Country } from '@thorprovider/types';
2
+ import { Order, Cart, BackendCapabilities, Money, PaymentMethod, FulfillmentOption, CartItem, StockLocation, InventoryLevel, StorefrontConfig, DashboardMetrics, ProviderTypeString, CommerceProvider, AuthProvider, ProviderConfig, AuthConfig, Product, GetProductsOptions, CatalogQuery, CatalogQueryOutcome, FacetSupport, AdvancedSearchProductsOptions, SearchResultMeta, FilterConfig, Collection, GetCollectionsOptions, CollectionProductsOptions, ProductCategory, GetCategoriesOptions, CartLineInput, CartLineUpdate, GetOrdersOptions, GetCustomerOrdersPageOptions, CustomerOrdersPage, GetCustomersOptions, Customer, Address, Region, AdminProduct, PaymentMethodsOptions, StockValidation, StorefrontContext, DesignerConfig, DesignerHistoryEntry, ShippingMethod, Country } from '@thorprovider/types';
3
3
  export * from '@thorprovider/types';
4
4
  export { BackendCapabilities, CommerceProvider, DesignerConfig, DesignerHistoryEntry, DesignerNavItem, DesignerThemeConfig, DesignerThemePreset, ProviderConfig, StorefrontConfig, StorefrontContext, StorefrontPlatformType, StorefrontSeoDefaults } from '@thorprovider/types';
5
5
  import Medusa from '@medusajs/js-sdk';
@@ -1905,13 +1905,17 @@ declare class MedusaProvider implements CommerceProvider, CartCompletionCapable
1905
1905
  }>;
1906
1906
  }>>;
1907
1907
  /**
1908
- * Get all orders for a customer (storefront).
1908
+ * Get all orders for a customer (storefront) as an array (legacy surface).
1909
1909
  *
1910
- * Note: This retrieves orders from the storefront API (store.order.list),
1911
- * which returns orders only for the authenticated customer in the JWT.
1910
+ * Delegates to the channel-scoped paginated contract
1911
+ * ({@link customerOrdersPage}) and returns that window's orders. The backend
1912
+ * (`GET /store/thor/orders`) scopes by BOTH the authenticated customer and
1913
+ * the sales channel resolved from the publishable key, so cross-channel
1914
+ * orders are excluded by the backend before pagination and **no client-side
1915
+ * channel filter runs**.
1912
1916
  *
1913
- * @param options - Order query options (supports callbacks for custom backends)
1914
- * @returns Array of orders for the authenticated customer
1917
+ * @param options - Order query options (supports a custom fetcher)
1918
+ * @returns Array of orders for the authenticated customer on the store channel
1915
1919
  *
1916
1920
  * @example
1917
1921
  * ```typescript
@@ -1920,6 +1924,25 @@ declare class MedusaProvider implements CommerceProvider, CartCompletionCapable
1920
1924
  * ```
1921
1925
  */
1922
1926
  getOrders(options?: GetOrdersOptions): Promise<Order[]>;
1927
+ /**
1928
+ * Channel-scoped, paginated order history for the authenticated customer.
1929
+ *
1930
+ * Calls the backend route `GET /store/thor/orders`, which requires the
1931
+ * customer session and scopes `order` by `customer_id` **and** the
1932
+ * `sales_channel_id` resolved from the publishable key, returning
1933
+ * `{ orders, count, limit, offset }` with the backend's real total computed
1934
+ * before pagination.
1935
+ *
1936
+ * The `salesChannelId` option is the store's explicitly configured channel.
1937
+ * When supplied, every returned order is asserted to belong to it; a
1938
+ * foreign-channel order (for example from a multi-channel publishable key that
1939
+ * silently resolved an arbitrary first id) fails closed with a typed error
1940
+ * rather than being presented as the customer's history.
1941
+ *
1942
+ * @param options - Bounded pagination window and the store's sales channel
1943
+ * @returns The backend's `{ orders, count, limit, offset }`
1944
+ */
1945
+ customerOrdersPage(options?: GetCustomerOrdersPageOptions): Promise<CustomerOrdersPage>;
1923
1946
  /**
1924
1947
  * Get a single order by ID
1925
1948
  *
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _thorprovider_types from '@thorprovider/types';
2
- import { Order, Cart, BackendCapabilities, Money, PaymentMethod, FulfillmentOption, CartItem, StockLocation, InventoryLevel, StorefrontConfig, DashboardMetrics, ProviderTypeString, CommerceProvider, AuthProvider, ProviderConfig, AuthConfig, Product, GetProductsOptions, CatalogQuery, CatalogQueryOutcome, FacetSupport, AdvancedSearchProductsOptions, SearchResultMeta, FilterConfig, Collection, GetCollectionsOptions, CollectionProductsOptions, ProductCategory, GetCategoriesOptions, CartLineInput, CartLineUpdate, GetOrdersOptions, GetCustomersOptions, Customer, Address, Region, AdminProduct, PaymentMethodsOptions, StockValidation, StorefrontContext, DesignerConfig, DesignerHistoryEntry, ShippingMethod, Country } from '@thorprovider/types';
2
+ import { Order, Cart, BackendCapabilities, Money, PaymentMethod, FulfillmentOption, CartItem, StockLocation, InventoryLevel, StorefrontConfig, DashboardMetrics, ProviderTypeString, CommerceProvider, AuthProvider, ProviderConfig, AuthConfig, Product, GetProductsOptions, CatalogQuery, CatalogQueryOutcome, FacetSupport, AdvancedSearchProductsOptions, SearchResultMeta, FilterConfig, Collection, GetCollectionsOptions, CollectionProductsOptions, ProductCategory, GetCategoriesOptions, CartLineInput, CartLineUpdate, GetOrdersOptions, GetCustomerOrdersPageOptions, CustomerOrdersPage, GetCustomersOptions, Customer, Address, Region, AdminProduct, PaymentMethodsOptions, StockValidation, StorefrontContext, DesignerConfig, DesignerHistoryEntry, ShippingMethod, Country } from '@thorprovider/types';
3
3
  export * from '@thorprovider/types';
4
4
  export { BackendCapabilities, CommerceProvider, DesignerConfig, DesignerHistoryEntry, DesignerNavItem, DesignerThemeConfig, DesignerThemePreset, ProviderConfig, StorefrontConfig, StorefrontContext, StorefrontPlatformType, StorefrontSeoDefaults } from '@thorprovider/types';
5
5
  import Medusa from '@medusajs/js-sdk';
@@ -1905,13 +1905,17 @@ declare class MedusaProvider implements CommerceProvider, CartCompletionCapable
1905
1905
  }>;
1906
1906
  }>>;
1907
1907
  /**
1908
- * Get all orders for a customer (storefront).
1908
+ * Get all orders for a customer (storefront) as an array (legacy surface).
1909
1909
  *
1910
- * Note: This retrieves orders from the storefront API (store.order.list),
1911
- * which returns orders only for the authenticated customer in the JWT.
1910
+ * Delegates to the channel-scoped paginated contract
1911
+ * ({@link customerOrdersPage}) and returns that window's orders. The backend
1912
+ * (`GET /store/thor/orders`) scopes by BOTH the authenticated customer and
1913
+ * the sales channel resolved from the publishable key, so cross-channel
1914
+ * orders are excluded by the backend before pagination and **no client-side
1915
+ * channel filter runs**.
1912
1916
  *
1913
- * @param options - Order query options (supports callbacks for custom backends)
1914
- * @returns Array of orders for the authenticated customer
1917
+ * @param options - Order query options (supports a custom fetcher)
1918
+ * @returns Array of orders for the authenticated customer on the store channel
1915
1919
  *
1916
1920
  * @example
1917
1921
  * ```typescript
@@ -1920,6 +1924,25 @@ declare class MedusaProvider implements CommerceProvider, CartCompletionCapable
1920
1924
  * ```
1921
1925
  */
1922
1926
  getOrders(options?: GetOrdersOptions): Promise<Order[]>;
1927
+ /**
1928
+ * Channel-scoped, paginated order history for the authenticated customer.
1929
+ *
1930
+ * Calls the backend route `GET /store/thor/orders`, which requires the
1931
+ * customer session and scopes `order` by `customer_id` **and** the
1932
+ * `sales_channel_id` resolved from the publishable key, returning
1933
+ * `{ orders, count, limit, offset }` with the backend's real total computed
1934
+ * before pagination.
1935
+ *
1936
+ * The `salesChannelId` option is the store's explicitly configured channel.
1937
+ * When supplied, every returned order is asserted to belong to it; a
1938
+ * foreign-channel order (for example from a multi-channel publishable key that
1939
+ * silently resolved an arbitrary first id) fails closed with a typed error
1940
+ * rather than being presented as the customer's history.
1941
+ *
1942
+ * @param options - Bounded pagination window and the store's sales channel
1943
+ * @returns The backend's `{ orders, count, limit, offset }`
1944
+ */
1945
+ customerOrdersPage(options?: GetCustomerOrdersPageOptions): Promise<CustomerOrdersPage>;
1923
1946
  /**
1924
1947
  * Get a single order by ID
1925
1948
  *
package/dist/index.js CHANGED
@@ -1139,6 +1139,12 @@ function transformProduct(product, currencyCode) {
1139
1139
  title: product.collection.title ?? "",
1140
1140
  handle: product.collection.handle ?? ""
1141
1141
  }] : void 0,
1142
+ categories: product.categories?.map((category) => ({
1143
+ id: category.id,
1144
+ name: category.name ?? "",
1145
+ handle: category.handle ?? "",
1146
+ parentCategoryId: category.parent_category_id ?? void 0
1147
+ })),
1142
1148
  productType: product.type?.value ?? void 0,
1143
1149
  vendor: void 0,
1144
1150
  // Medusa doesn't have vendor field
@@ -1911,6 +1917,7 @@ var MedusaAuthProvider = class {
1911
1917
  if (this.authConfig.method === "jwt") {
1912
1918
  await this.storageManager.setToken(token);
1913
1919
  }
1920
+ await this.linkCustomerChannel();
1914
1921
  const customer = await this.getCurrentCustomer();
1915
1922
  this.logger.info("Login successful", { customerId: customer?.id });
1916
1923
  return {
@@ -1918,6 +1925,7 @@ var MedusaAuthProvider = class {
1918
1925
  customer: customer ?? void 0
1919
1926
  };
1920
1927
  } catch (error) {
1928
+ if (error instanceof ProviderAPIError) throw error;
1921
1929
  this.logger.error("Login failed", error);
1922
1930
  throw new ProviderAPIError(
1923
1931
  `Authentication failed: ${error.message}`,
@@ -1929,16 +1937,14 @@ var MedusaAuthProvider = class {
1929
1937
  async register(data) {
1930
1938
  this.logger.debug("register", { email: data.email });
1931
1939
  try {
1932
- await this.sdk.auth.register("customer", "emailpass", {
1940
+ const registrationToken = await this.sdk.auth.register("customer", "emailpass", {
1933
1941
  email: data.email,
1934
1942
  password: data.password
1935
1943
  });
1936
- const { customer } = await this.sdk.store.customer.create({
1937
- email: data.email,
1938
- first_name: data.first_name,
1939
- last_name: data.last_name,
1940
- phone: data.phone
1941
- });
1944
+ if (this.authConfig.method === "jwt" && typeof registrationToken === "string") {
1945
+ await this.storageManager.setToken(registrationToken);
1946
+ }
1947
+ const linked = await this.createChannelLinkedCustomer(data);
1942
1948
  const loginResult = await this.sdk.auth.login("customer", "emailpass", {
1943
1949
  email: data.email,
1944
1950
  password: data.password
@@ -1964,12 +1970,20 @@ var MedusaAuthProvider = class {
1964
1970
  if (this.authConfig.method === "jwt") {
1965
1971
  await this.storageManager.setToken(token);
1966
1972
  }
1973
+ const customer = await this.getCurrentCustomer();
1974
+ if (!customer || customer.id !== linked.id) {
1975
+ throw new ProviderAPIError(
1976
+ "Registration created an auth identity without a linked customer.",
1977
+ "CUSTOMER_LINK_INCOMPLETE"
1978
+ );
1979
+ }
1967
1980
  this.logger.info("Registration successful", { customerId: customer.id });
1968
1981
  return {
1969
1982
  token: this.authConfig.method === "jwt" ? token : void 0,
1970
- customer: transformCustomer(customer, customer.email)
1983
+ customer
1971
1984
  };
1972
1985
  } catch (error) {
1986
+ if (error instanceof ProviderAPIError) throw error;
1973
1987
  this.logger.error("Registration failed", error);
1974
1988
  throw new ProviderAPIError(
1975
1989
  `Registration failed: ${error.message}`,
@@ -1978,6 +1992,79 @@ var MedusaAuthProvider = class {
1978
1992
  );
1979
1993
  }
1980
1994
  }
1995
+ /**
1996
+ * Create and channel-link a customer through `POST /store/thor/customers`.
1997
+ *
1998
+ * The request carries the registration token (read by the SDK from the
1999
+ * request-scoped custom storage) and the publishable key; the backend links
2000
+ * the customer to the publishable-key sales channel. A route that is
2001
+ * unavailable fails closed with a typed error — there is no silent native
2002
+ * fallback that would leave the customer unlinked.
2003
+ */
2004
+ async createChannelLinkedCustomer(data) {
2005
+ const response = await this.fetchChannelAccountRoute(
2006
+ "/store/thor/customers",
2007
+ {
2008
+ method: "POST",
2009
+ body: {
2010
+ email: data.email,
2011
+ first_name: data.first_name,
2012
+ last_name: data.last_name,
2013
+ phone: data.phone
2014
+ }
2015
+ }
2016
+ );
2017
+ const id = response?.customer?.id;
2018
+ if (typeof id !== "string" || id.length === 0) {
2019
+ throw new ProviderAPIError(
2020
+ "Channel-linked customer creation returned no customer id.",
2021
+ "CUSTOMER_LINK_INCOMPLETE"
2022
+ );
2023
+ }
2024
+ return { id };
2025
+ }
2026
+ /**
2027
+ * Link the authenticated customer to the publishable-key sales channel
2028
+ * through `POST /store/thor/customers/me/channels` (idempotent). Called after
2029
+ * login so history is channel-scoped for the current storefront.
2030
+ */
2031
+ async linkCustomerChannel() {
2032
+ const response = await this.fetchChannelAccountRoute("/store/thor/customers/me/channels", { method: "POST" });
2033
+ if (response?.linked === false && response?.reason !== "already_linked") {
2034
+ throw new ProviderAPIError(
2035
+ "Customer channel link did not complete.",
2036
+ "CUSTOMER_LINK_INCOMPLETE"
2037
+ );
2038
+ }
2039
+ }
2040
+ /**
2041
+ * Call a Thor channel account route through the SDK client, which attaches
2042
+ * the request-scoped customer/registration token and the publishable key. A
2043
+ * missing client or an unavailable route (404/405/501) fails closed with a
2044
+ * typed `CUSTOMER_LINK_UNAVAILABLE` error.
2045
+ */
2046
+ async fetchChannelAccountRoute(path, init) {
2047
+ const client = this.sdk.client;
2048
+ if (!client || typeof client.fetch !== "function") {
2049
+ throw new ProviderAPIError(
2050
+ `The channel account route ${path} is unavailable in this SDK.`,
2051
+ "CUSTOMER_LINK_UNAVAILABLE"
2052
+ );
2053
+ }
2054
+ try {
2055
+ return await client.fetch(path, init);
2056
+ } catch (error) {
2057
+ const status = error?.status || error?.statusCode;
2058
+ if (status === 404 || status === 405 || status === 501) {
2059
+ throw new ProviderAPIError(
2060
+ `The channel account route ${path} is unavailable (status ${status}).`,
2061
+ "CUSTOMER_LINK_UNAVAILABLE",
2062
+ status
2063
+ );
2064
+ }
2065
+ throw error;
2066
+ }
2067
+ }
1981
2068
  async logout() {
1982
2069
  this.logger.debug("logout");
1983
2070
  try {
@@ -2324,6 +2411,14 @@ async function getPaymentMethods(sdk, options) {
2324
2411
 
2325
2412
  // src/providers/medusa/index.ts
2326
2413
  var MEDUSA_CART_RETRIEVE_FIELDS = "+items,+items.variant,+items.variant.product,+items.variant.product.thumbnail,+items.variant.options,+items.variant.options.option,+items.total,+items.unit_price,+items.item_subtotal,+items.item_total,+shipping_methods,+shipping_methods.id,+shipping_methods.shipping_option_id";
2414
+ var CUSTOMER_ORDERS_DEFAULT_PAGE_SIZE = 10;
2415
+ var CUSTOMER_ORDERS_MAX_PAGE_SIZE = 100;
2416
+ function normalizeCustomerOrdersPage(options) {
2417
+ const page = Number.isInteger(options.page) && options.page > 0 ? options.page : 1;
2418
+ const requested = Number.isInteger(options.pageSize) && options.pageSize > 0 ? options.pageSize : CUSTOMER_ORDERS_DEFAULT_PAGE_SIZE;
2419
+ const pageSize = Math.min(requested, CUSTOMER_ORDERS_MAX_PAGE_SIZE);
2420
+ return { page, pageSize, limit: pageSize, offset: (page - 1) * pageSize };
2421
+ }
2327
2422
  var MEDUSA_ORDER_COMPLETION_FIELDS = "*items";
2328
2423
  var DEFAULT_CATALOG_PAGE_SIZE = 50;
2329
2424
  function resolveCatalogPageSize(configured) {
@@ -2687,7 +2782,7 @@ var MedusaProvider = class {
2687
2782
  }
2688
2783
  /** Fields needed to price a product variant for the current region. */
2689
2784
  buildProductFields(regionId) {
2690
- const baseFields = "+variants,+variants.inventory_quantity,+options,+images,+tags";
2785
+ const baseFields = "+variants,+variants.inventory_quantity,+options,+images,+tags,+categories.*";
2691
2786
  const pricingFields = ",+variants.calculated_price,+variants.prices";
2692
2787
  return regionId ? baseFields + pricingFields : baseFields;
2693
2788
  }
@@ -4245,13 +4340,17 @@ var MedusaProvider = class {
4245
4340
  // Order Operations
4246
4341
  // ============================================
4247
4342
  /**
4248
- * Get all orders for a customer (storefront).
4343
+ * Get all orders for a customer (storefront) as an array (legacy surface).
4249
4344
  *
4250
- * Note: This retrieves orders from the storefront API (store.order.list),
4251
- * which returns orders only for the authenticated customer in the JWT.
4345
+ * Delegates to the channel-scoped paginated contract
4346
+ * ({@link customerOrdersPage}) and returns that window's orders. The backend
4347
+ * (`GET /store/thor/orders`) scopes by BOTH the authenticated customer and
4348
+ * the sales channel resolved from the publishable key, so cross-channel
4349
+ * orders are excluded by the backend before pagination and **no client-side
4350
+ * channel filter runs**.
4252
4351
  *
4253
- * @param options - Order query options (supports callbacks for custom backends)
4254
- * @returns Array of orders for the authenticated customer
4352
+ * @param options - Order query options (supports a custom fetcher)
4353
+ * @returns Array of orders for the authenticated customer on the store channel
4255
4354
  *
4256
4355
  * @example
4257
4356
  * ```typescript
@@ -4265,32 +4364,68 @@ var MedusaProvider = class {
4265
4364
  this.logger.debug("Using custom fetcher for orders");
4266
4365
  return customFetcher(options);
4267
4366
  }
4268
- this.logger.info("Getting orders for authenticated customer");
4367
+ const page = await this.customerOrdersPage({
4368
+ page: 1,
4369
+ pageSize: options.limit ?? CUSTOMER_ORDERS_MAX_PAGE_SIZE,
4370
+ salesChannelId
4371
+ });
4372
+ return page.orders;
4373
+ }
4374
+ /**
4375
+ * Channel-scoped, paginated order history for the authenticated customer.
4376
+ *
4377
+ * Calls the backend route `GET /store/thor/orders`, which requires the
4378
+ * customer session and scopes `order` by `customer_id` **and** the
4379
+ * `sales_channel_id` resolved from the publishable key, returning
4380
+ * `{ orders, count, limit, offset }` with the backend's real total computed
4381
+ * before pagination.
4382
+ *
4383
+ * The `salesChannelId` option is the store's explicitly configured channel.
4384
+ * When supplied, every returned order is asserted to belong to it; a
4385
+ * foreign-channel order (for example from a multi-channel publishable key that
4386
+ * silently resolved an arbitrary first id) fails closed with a typed error
4387
+ * rather than being presented as the customer's history.
4388
+ *
4389
+ * @param options - Bounded pagination window and the store's sales channel
4390
+ * @returns The backend's `{ orders, count, limit, offset }`
4391
+ */
4392
+ async customerOrdersPage(options = {}) {
4393
+ const { page, pageSize, limit, offset } = normalizeCustomerOrdersPage(options);
4394
+ const { salesChannelId } = options;
4395
+ this.logger.info("Getting channel-scoped customer orders", { page, pageSize });
4269
4396
  try {
4270
- const result = await this.withRetry(
4271
- async () => this.sdk.store.order.list({
4272
- // Medusa v2 JS-SDK automatically includes authorization header if JWT is present
4273
- fields: "+items,+items.variant,+items.variant.product,+shipping_address,+billing_address,+customer"
4397
+ const response = await this.withRetry(
4398
+ async () => this.sdk.client.fetch("/store/thor/orders", {
4399
+ method: "GET",
4400
+ query: { limit, offset }
4274
4401
  }),
4275
- "getOrders",
4402
+ "customerOrdersPage",
4276
4403
  {}
4277
4404
  );
4278
- const orders = result.orders ?? [];
4279
- this.logger.debug(`Found ${orders.length} orders for customer`);
4280
- const transformed = orders.map((order) => transformOrder(order));
4281
- const channelId = salesChannelId ?? process.env.NEXT_PUBLIC_SALES_CHANNEL_ID;
4282
- if (channelId) {
4283
- const scoped = filterByChannel(transformed, channelId);
4284
- this.logger.debug(
4285
- `Channel scoping: ${scoped.length}/${transformed.length} orders match channel ${channelId}`
4405
+ const rawOrders = response.orders ?? [];
4406
+ this.logger.debug(`Found ${rawOrders.length} channel-scoped orders for customer`);
4407
+ if (salesChannelId) {
4408
+ const foreign = rawOrders.find(
4409
+ (order) => typeof order?.sales_channel_id === "string" && order.sales_channel_id !== salesChannelId
4286
4410
  );
4287
- return scoped;
4411
+ if (foreign) {
4412
+ throw new ProviderAPIError(
4413
+ `Channel-scoped order history returned an order outside the store's sales channel (${salesChannelId}).`,
4414
+ "medusa"
4415
+ );
4416
+ }
4288
4417
  }
4289
- return transformed;
4418
+ return {
4419
+ orders: rawOrders.map((order) => transformOrder(order)),
4420
+ count: typeof response.count === "number" ? response.count : rawOrders.length,
4421
+ limit: typeof response.limit === "number" ? response.limit : limit,
4422
+ offset: typeof response.offset === "number" ? response.offset : offset
4423
+ };
4290
4424
  } catch (error) {
4425
+ if (error instanceof ProviderAPIError) throw error;
4291
4426
  const status = error?.status || error?.statusCode;
4292
4427
  const errorMessage = extractErrorMessage(error);
4293
- this.logger.error("Error getting orders", { error: errorMessage });
4428
+ this.logger.error("Error getting channel-scoped orders", { error: errorMessage });
4294
4429
  throw new ProviderAPIError(
4295
4430
  `Failed to fetch orders: ${errorMessage}`,
4296
4431
  "medusa",
@@ -9937,7 +10072,7 @@ var categoryFixtures = [
9937
10072
  description: "Complementary products for cart and bundle flows."
9938
10073
  }
9939
10074
  ];
9940
- var productFixtures = [
10075
+ var heroProductFixtures = [
9941
10076
  {
9942
10077
  id: "mock-product-aurora-jacket",
9943
10078
  handle: "aurora-jacket",
@@ -10069,6 +10204,119 @@ var productFixtures = [
10069
10204
  ]
10070
10205
  }
10071
10206
  ];
10207
+ var generatedBlueprints = [
10208
+ {
10209
+ categoryHandle: "jackets",
10210
+ noun: "Jacket",
10211
+ productType: "outerwear",
10212
+ vendor: "Thor Atelier",
10213
+ tags: ["outerwear", "layer"],
10214
+ variantName: "Size",
10215
+ variantValues: ["S", "M"],
10216
+ names: ["Alpine", "Boreal", "Cinder", "Drift", "Ember", "Fjord", "Harbor", "Meridian", "Nomad", "Tundra", "Vantage"],
10217
+ description: "A considered layering piece in a brushed weave.",
10218
+ basePrice: 118,
10219
+ baseHue: 210
10220
+ },
10221
+ {
10222
+ categoryHandle: "dresses",
10223
+ noun: "Dress",
10224
+ productType: "dress",
10225
+ vendor: "Thor Atelier",
10226
+ tags: ["dress", "editorial"],
10227
+ variantName: "Size",
10228
+ variantValues: ["S", "M"],
10229
+ names: ["Amber", "Camille", "Dahlia", "Elara", "Iris", "Lumen", "Nova", "Rosalind", "Vera"],
10230
+ description: "Cut from a fluid cloth for editorial styling.",
10231
+ basePrice: 142,
10232
+ baseHue: 330
10233
+ },
10234
+ {
10235
+ categoryHandle: "home",
10236
+ noun: "Lamp",
10237
+ productType: "decor",
10238
+ vendor: "Thor Home",
10239
+ tags: ["home", "decor", "interior"],
10240
+ variantName: "Finish",
10241
+ variantValues: ["Brass"],
10242
+ names: ["Atlas", "Copper", "Dune", "Onyx", "Quill", "Slate", "Terra", "Verdant"],
10243
+ description: "A calm sculptural accent for modern interiors.",
10244
+ basePrice: 74,
10245
+ baseHue: 40
10246
+ },
10247
+ {
10248
+ categoryHandle: "accessories",
10249
+ noun: "Bag",
10250
+ productType: "bag",
10251
+ vendor: "Thor Atelier",
10252
+ tags: ["accessory", "everyday"],
10253
+ variantName: "Color",
10254
+ variantValues: ["Juniper"],
10255
+ names: ["Cove", "Fable", "Juniper", "Kestrel", "Marlowe", "Piper", "Rune"],
10256
+ description: "An everyday carry with a textured finish.",
10257
+ basePrice: 68,
10258
+ baseHue: 280
10259
+ }
10260
+ ];
10261
+ var GENERATED_CREATED_AT_BASE = Date.parse("2025-12-30T00:00:00.000Z");
10262
+ var GENERATED_COLLECTION_POOL = ["new-arrivals", "best-sellers", "studio-essentials"];
10263
+ function buildGeneratedProductFixtures() {
10264
+ const fixtures = [];
10265
+ let globalIndex = 0;
10266
+ for (const blueprint of generatedBlueprints) {
10267
+ const noun = blueprint.noun.toLowerCase();
10268
+ blueprint.names.forEach((name, localIndex) => {
10269
+ const handle = `${name.toLowerCase()}-${noun}`;
10270
+ const title = `${name} ${blueprint.noun}`;
10271
+ const price = blueprint.basePrice + localIndex * 7 + localIndex % 3 * 0.5;
10272
+ const quantityAvailable = globalIndex * 5 % 17;
10273
+ const primaryCollection = GENERATED_COLLECTION_POOL[globalIndex % GENERATED_COLLECTION_POOL.length];
10274
+ const collectionHandles = [primaryCollection];
10275
+ if (globalIndex % 5 === 0) {
10276
+ const secondary = GENERATED_COLLECTION_POOL[(globalIndex + 1) % GENERATED_COLLECTION_POOL.length];
10277
+ if (secondary !== primaryCollection) {
10278
+ collectionHandles.push(secondary);
10279
+ }
10280
+ }
10281
+ const createdAt = new Date(GENERATED_CREATED_AT_BASE - globalIndex * 864e5).toISOString();
10282
+ const variants = blueprint.variantValues.map((value, variantIndex) => ({
10283
+ id: `mock-variant-${handle}-${value.toLowerCase()}`,
10284
+ title: value,
10285
+ price,
10286
+ quantityAvailable: Math.max(0, quantityAvailable - variantIndex),
10287
+ selectedOptions: [{ name: blueprint.variantName, value }]
10288
+ }));
10289
+ fixtures.push({
10290
+ id: `mock-product-${handle}`,
10291
+ handle,
10292
+ title,
10293
+ description: `${title} - ${blueprint.description}`,
10294
+ imageLabel: title,
10295
+ imageHue: (blueprint.baseHue + localIndex * 13) % 360,
10296
+ collectionHandles,
10297
+ categoryHandle: blueprint.categoryHandle,
10298
+ productType: blueprint.productType,
10299
+ vendor: blueprint.vendor,
10300
+ tags: [...blueprint.tags],
10301
+ options: [
10302
+ {
10303
+ id: `${handle}-${blueprint.variantName.toLowerCase()}`,
10304
+ name: blueprint.variantName,
10305
+ values: [...blueprint.variantValues]
10306
+ }
10307
+ ],
10308
+ variants,
10309
+ createdAt
10310
+ });
10311
+ globalIndex += 1;
10312
+ });
10313
+ }
10314
+ return fixtures;
10315
+ }
10316
+ var productFixtures = [
10317
+ ...heroProductFixtures,
10318
+ ...buildGeneratedProductFixtures()
10319
+ ];
10072
10320
  var stockLocationFixture = {
10073
10321
  id: "mock-stock-primary",
10074
10322
  name: "Mock Main Warehouse",
@@ -10185,6 +10433,20 @@ function buildProducts(currencyCode) {
10185
10433
  title: collection.title,
10186
10434
  handle: collection.handle
10187
10435
  })),
10436
+ categories: (() => {
10437
+ const category = categoryFixtures.find(
10438
+ (item) => item.handle === product.categoryHandle
10439
+ );
10440
+ if (!category) return void 0;
10441
+ return [
10442
+ {
10443
+ id: category.id,
10444
+ name: category.name,
10445
+ handle: category.handle,
10446
+ parentCategoryId: category.parentCategoryId
10447
+ }
10448
+ ];
10449
+ })(),
10188
10450
  productType: product.productType,
10189
10451
  vendor: product.vendor,
10190
10452
  tags: product.tags,