@shopify/hydrogen 1.4.1 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,7 +17,7 @@ export declare function useCartActions({ numCartLines, cartFragment, countryCode
17
17
  /** Maximum number of cart lines to fetch. Defaults to 250 cart lines. */
18
18
  numCartLines?: number;
19
19
  /** A fragment used to query the Storefront API's [Cart object](https://shopify.dev/api/storefront/latest/objects/cart) for all queries and mutations. A default value is used if no argument is provided. */
20
- cartFragment?: string;
20
+ cartFragment: string;
21
21
  /** The ISO country code for i18n. */
22
22
  countryCode?: CountryCode;
23
23
  }): {
@@ -59,4 +59,3 @@ export declare function useCartActions({ numCartLines, cartFragment, countryCode
59
59
  }>;
60
60
  cartFragment: string;
61
61
  };
62
- export declare const defaultCartFragment = "\nfragment CartFragment on Cart {\n id\n checkoutUrl\n totalQuantity\n buyerIdentity {\n countryCode\n customer {\n id\n email\n firstName\n lastName\n displayName\n }\n email\n phone\n }\n lines(first: $numCartLines) {\n edges {\n node {\n id\n quantity\n attributes {\n key\n value\n }\n cost {\n totalAmount {\n amount\n currencyCode\n }\n compareAtAmountPerQuantity {\n amount\n currencyCode\n }\n }\n merchandise {\n ... on ProductVariant {\n id\n availableForSale\n compareAtPriceV2 {\n ...MoneyFragment\n }\n priceV2 {\n ...MoneyFragment\n }\n requiresShipping\n title\n image {\n ...ImageFragment\n }\n product {\n handle\n title\n }\n selectedOptions {\n name\n value\n }\n }\n }\n }\n }\n }\n cost {\n subtotalAmount {\n ...MoneyFragment\n }\n totalAmount {\n ...MoneyFragment\n }\n totalDutyAmount {\n ...MoneyFragment\n }\n totalTaxAmount {\n ...MoneyFragment\n }\n }\n note\n attributes {\n key\n value\n }\n discountCodes {\n code\n }\n}\n\nfragment MoneyFragment on MoneyV2 {\n currencyCode\n amount\n}\nfragment ImageFragment on Image {\n id\n url\n altText\n width\n height\n}\n";
@@ -7,7 +7,7 @@ import { useCartFetch } from './hooks.client.js';
7
7
  *
8
8
  * See [cart API graphql mutations](https://shopify.dev/api/storefront/2022-07/objects/Cart)
9
9
  */
10
- export function useCartActions({ numCartLines, cartFragment = defaultCartFragment, countryCode = CountryCode.Us, }) {
10
+ export function useCartActions({ numCartLines, cartFragment, countryCode = CountryCode.Us, }) {
11
11
  const fetchCart = useCartFetch();
12
12
  const cartFetch = useCallback((cartId) => {
13
13
  return fetchCart({
@@ -130,103 +130,3 @@ export function useCartActions({ numCartLines, cartFragment = defaultCartFragmen
130
130
  cartFragment,
131
131
  ]);
132
132
  }
133
- export const defaultCartFragment = `
134
- fragment CartFragment on Cart {
135
- id
136
- checkoutUrl
137
- totalQuantity
138
- buyerIdentity {
139
- countryCode
140
- customer {
141
- id
142
- email
143
- firstName
144
- lastName
145
- displayName
146
- }
147
- email
148
- phone
149
- }
150
- lines(first: $numCartLines) {
151
- edges {
152
- node {
153
- id
154
- quantity
155
- attributes {
156
- key
157
- value
158
- }
159
- cost {
160
- totalAmount {
161
- amount
162
- currencyCode
163
- }
164
- compareAtAmountPerQuantity {
165
- amount
166
- currencyCode
167
- }
168
- }
169
- merchandise {
170
- ... on ProductVariant {
171
- id
172
- availableForSale
173
- compareAtPriceV2 {
174
- ...MoneyFragment
175
- }
176
- priceV2 {
177
- ...MoneyFragment
178
- }
179
- requiresShipping
180
- title
181
- image {
182
- ...ImageFragment
183
- }
184
- product {
185
- handle
186
- title
187
- }
188
- selectedOptions {
189
- name
190
- value
191
- }
192
- }
193
- }
194
- }
195
- }
196
- }
197
- cost {
198
- subtotalAmount {
199
- ...MoneyFragment
200
- }
201
- totalAmount {
202
- ...MoneyFragment
203
- }
204
- totalDutyAmount {
205
- ...MoneyFragment
206
- }
207
- totalTaxAmount {
208
- ...MoneyFragment
209
- }
210
- }
211
- note
212
- attributes {
213
- key
214
- value
215
- }
216
- discountCodes {
217
- code
218
- }
219
- }
220
-
221
- fragment MoneyFragment on MoneyV2 {
222
- currencyCode
223
- amount
224
- }
225
- fragment ImageFragment on Image {
226
- id
227
- url
228
- altText
229
- width
230
- height
231
- }
232
- `;
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { CartFragmentFragment } from './graphql/CartFragment.js';
3
3
  import { CartBuyerIdentityInput, CountryCode } from '../../storefront-api-types.js';
4
- export declare function CartProviderV2({ children, numCartLines, onCreate, onLineAdd, onLineRemove, onLineUpdate, onNoteUpdate, onBuyerIdentityUpdate, onAttributesUpdate, onDiscountCodesUpdate, onCreateComplete, onLineAddComplete, onLineRemoveComplete, onLineUpdateComplete, onNoteUpdateComplete, onBuyerIdentityUpdateComplete, onAttributesUpdateComplete, onDiscountCodesUpdateComplete, data, cartFragment, customerAccessToken, countryCode, }: {
4
+ export declare function CartProviderV2({ children, numCartLines, onCreate, onLineAdd, onLineRemove, onLineUpdate, onNoteUpdate, onBuyerIdentityUpdate, onAttributesUpdate, onDiscountCodesUpdate, onCreateComplete, onLineAddComplete, onLineRemoveComplete, onLineUpdateComplete, onNoteUpdateComplete, onBuyerIdentityUpdateComplete, onAttributesUpdateComplete, onDiscountCodesUpdateComplete, data: cart, cartFragment, customerAccessToken, countryCode, }: {
5
5
  /** Any `ReactNode` elements. */
6
6
  children: React.ReactNode;
7
7
  /** Maximum number of cart lines to fetch. Defaults to 250 cart lines. */
@@ -47,3 +47,4 @@ export declare function CartProviderV2({ children, numCartLines, onCreate, onLin
47
47
  /** The ISO country code for i18n. */
48
48
  countryCode?: CountryCode;
49
49
  }): JSX.Element;
50
+ export declare const defaultCartFragment = "\nfragment CartFragment on Cart {\n id\n checkoutUrl\n totalQuantity\n buyerIdentity {\n countryCode\n customer {\n id\n email\n firstName\n lastName\n displayName\n }\n email\n phone\n }\n lines(first: $numCartLines) {\n edges {\n node {\n id\n quantity\n attributes {\n key\n value\n }\n cost {\n totalAmount {\n amount\n currencyCode\n }\n compareAtAmountPerQuantity {\n amount\n currencyCode\n }\n }\n merchandise {\n ... on ProductVariant {\n id\n availableForSale\n compareAtPriceV2 {\n ...MoneyFragment\n }\n priceV2 {\n ...MoneyFragment\n }\n requiresShipping\n title\n image {\n ...ImageFragment\n }\n product {\n handle\n title\n }\n selectedOptions {\n name\n value\n }\n }\n }\n }\n }\n }\n cost {\n subtotalAmount {\n ...MoneyFragment\n }\n totalAmount {\n ...MoneyFragment\n }\n totalDutyAmount {\n ...MoneyFragment\n }\n totalTaxAmount {\n ...MoneyFragment\n }\n }\n note\n attributes {\n key\n value\n }\n discountCodes {\n code\n }\n}\n\nfragment MoneyFragment on MoneyV2 {\n currencyCode\n amount\n}\nfragment ImageFragment on Image {\n id\n url\n altText\n width\n height\n}\n";
@@ -1,11 +1,10 @@
1
1
  import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
2
  import { CountryCode, } from '../../storefront-api-types.js';
3
3
  import { CartContext } from './context.js';
4
- import { useCartActions } from './CartActions.client.js';
5
4
  import { useCartAPIStateMachine } from './useCartAPIStateMachine.client.js';
6
5
  import { CART_ID_STORAGE_KEY } from './constants.js';
7
6
  import { ClientAnalytics } from '../../foundation/Analytics/ClientAnalytics.js';
8
- export function CartProviderV2({ children, numCartLines, onCreate, onLineAdd, onLineRemove, onLineUpdate, onNoteUpdate, onBuyerIdentityUpdate, onAttributesUpdate, onDiscountCodesUpdate, onCreateComplete, onLineAddComplete, onLineRemoveComplete, onLineUpdateComplete, onNoteUpdateComplete, onBuyerIdentityUpdateComplete, onAttributesUpdateComplete, onDiscountCodesUpdateComplete, data: cart, cartFragment, customerAccessToken, countryCode = CountryCode.Us, }) {
7
+ export function CartProviderV2({ children, numCartLines, onCreate, onLineAdd, onLineRemove, onLineUpdate, onNoteUpdate, onBuyerIdentityUpdate, onAttributesUpdate, onDiscountCodesUpdate, onCreateComplete, onLineAddComplete, onLineRemoveComplete, onLineUpdateComplete, onNoteUpdateComplete, onBuyerIdentityUpdateComplete, onAttributesUpdateComplete, onDiscountCodesUpdateComplete, data: cart, cartFragment = defaultCartFragment, customerAccessToken, countryCode = CountryCode.Us, }) {
9
8
  if (countryCode)
10
9
  countryCode = countryCode.toUpperCase();
11
10
  const [prevCountryCode, setPrevCountryCode] = useState(countryCode);
@@ -17,32 +16,33 @@ export function CartProviderV2({ children, numCartLines, onCreate, onLineAdd, on
17
16
  setPrevCustomerAccessToken(customerAccessToken);
18
17
  customerOverridesCountryCode.current = false;
19
18
  }
20
- const { cartFragment: usedCartFragment } = useCartActions({
21
- numCartLines,
22
- cartFragment,
23
- countryCode,
24
- });
25
19
  const [cartState, cartSend] = useCartAPIStateMachine({
26
20
  numCartLines,
27
21
  cartFragment,
22
+ countryCode,
28
23
  onCartActionEntry(context, event) {
29
- switch (event.type) {
30
- case 'CART_CREATE':
31
- return onCreate?.();
32
- case 'CARTLINE_ADD':
33
- return onLineAdd?.();
34
- case 'CARTLINE_REMOVE':
35
- return onLineRemove?.();
36
- case 'CARTLINE_UPDATE':
37
- return onLineUpdate?.();
38
- case 'NOTE_UPDATE':
39
- return onNoteUpdate?.();
40
- case 'BUYER_IDENTITY_UPDATE':
41
- return onBuyerIdentityUpdate?.();
42
- case 'CART_ATTRIBUTES_UPDATE':
43
- return onAttributesUpdate?.();
44
- case 'DISCOUNT_CODES_UPDATE':
45
- return onDiscountCodesUpdate?.();
24
+ try {
25
+ switch (event.type) {
26
+ case 'CART_CREATE':
27
+ return onCreate?.();
28
+ case 'CARTLINE_ADD':
29
+ return onLineAdd?.();
30
+ case 'CARTLINE_REMOVE':
31
+ return onLineRemove?.();
32
+ case 'CARTLINE_UPDATE':
33
+ return onLineUpdate?.();
34
+ case 'NOTE_UPDATE':
35
+ return onNoteUpdate?.();
36
+ case 'BUYER_IDENTITY_UPDATE':
37
+ return onBuyerIdentityUpdate?.();
38
+ case 'CART_ATTRIBUTES_UPDATE':
39
+ return onAttributesUpdate?.();
40
+ case 'DISCOUNT_CODES_UPDATE':
41
+ return onDiscountCodesUpdate?.();
42
+ }
43
+ }
44
+ catch (error) {
45
+ console.error('Cart entry action failed', error);
46
46
  }
47
47
  },
48
48
  onCartActionOptimisticUI(context, event) {
@@ -81,44 +81,75 @@ export function CartProviderV2({ children, numCartLines, onCreate, onLineAdd, on
81
81
  },
82
82
  onCartActionComplete(context, event) {
83
83
  const cartActionEvent = event.payload.cartActionEvent;
84
- switch (event.type) {
85
- case 'RESOLVE':
86
- switch (cartActionEvent.type) {
87
- case 'CART_CREATE':
88
- publishCreateAnalytics(context, cartActionEvent);
89
- return onCreateComplete?.();
90
- case 'CARTLINE_ADD':
91
- publishLineAddAnalytics(context, cartActionEvent);
92
- return onLineAddComplete?.();
93
- case 'CARTLINE_REMOVE':
94
- publishLineRemoveAnalytics(context, cartActionEvent);
95
- return onLineRemoveComplete?.();
96
- case 'CARTLINE_UPDATE':
97
- publishLineUpdateAnalytics(context, cartActionEvent);
98
- return onLineUpdateComplete?.();
99
- case 'NOTE_UPDATE':
100
- return onNoteUpdateComplete?.();
101
- case 'BUYER_IDENTITY_UPDATE':
102
- if (countryCodeNotUpdated(context, cartActionEvent)) {
103
- customerOverridesCountryCode.current = true;
104
- }
105
- return onBuyerIdentityUpdateComplete?.();
106
- case 'CART_ATTRIBUTES_UPDATE':
107
- return onAttributesUpdateComplete?.();
108
- case 'DISCOUNT_CODES_UPDATE':
109
- publishDiscountCodesUpdateAnalytics(context, cartActionEvent);
110
- return onDiscountCodesUpdateComplete?.();
111
- }
84
+ try {
85
+ switch (event.type) {
86
+ case 'RESOLVE':
87
+ switch (cartActionEvent.type) {
88
+ case 'CART_CREATE':
89
+ publishCreateAnalytics(context, cartActionEvent);
90
+ return onCreateComplete?.();
91
+ case 'CARTLINE_ADD':
92
+ publishLineAddAnalytics(context, cartActionEvent);
93
+ return onLineAddComplete?.();
94
+ case 'CARTLINE_REMOVE':
95
+ publishLineRemoveAnalytics(context, cartActionEvent);
96
+ return onLineRemoveComplete?.();
97
+ case 'CARTLINE_UPDATE':
98
+ publishLineUpdateAnalytics(context, cartActionEvent);
99
+ return onLineUpdateComplete?.();
100
+ case 'NOTE_UPDATE':
101
+ return onNoteUpdateComplete?.();
102
+ case 'BUYER_IDENTITY_UPDATE':
103
+ if (countryCodeNotUpdated(context, cartActionEvent)) {
104
+ customerOverridesCountryCode.current = true;
105
+ }
106
+ return onBuyerIdentityUpdateComplete?.();
107
+ case 'CART_ATTRIBUTES_UPDATE':
108
+ return onAttributesUpdateComplete?.();
109
+ case 'DISCOUNT_CODES_UPDATE':
110
+ publishDiscountCodesUpdateAnalytics(context, cartActionEvent);
111
+ return onDiscountCodesUpdateComplete?.();
112
+ }
113
+ }
114
+ }
115
+ catch (error) {
116
+ console.error('onCartActionComplete failed', error);
112
117
  }
113
118
  },
114
119
  });
115
- const [cartReady, setCartReady] = useState(false);
120
+ const cartReady = useRef(false);
116
121
  const cartCompleted = cartState.matches('cartCompleted');
117
122
  const countryChanged = (cartState.value === 'idle' ||
118
123
  cartState.value === 'error' ||
119
124
  cartState.value === 'cartCompleted') &&
120
125
  countryCode !== cartState?.context?.cart?.buyerIdentity?.countryCode &&
121
126
  !cartState.context.errors;
127
+ /**
128
+ * Initializes cart with priority in this order:
129
+ * 1. cart props
130
+ * 2. localStorage cartId
131
+ */
132
+ useEffect(() => {
133
+ if (!cartReady.current) {
134
+ if (cart) {
135
+ cartSend({ type: 'CART_SET', payload: { cart } });
136
+ }
137
+ else if (storageAvailable('localStorage')) {
138
+ try {
139
+ const cartId = window.localStorage.getItem(CART_ID_STORAGE_KEY);
140
+ if (cartId) {
141
+ cartSend({ type: 'CART_FETCH', payload: { cartId } });
142
+ }
143
+ }
144
+ catch (error) {
145
+ console.warn('error fetching cartId');
146
+ console.warn(error);
147
+ }
148
+ }
149
+ cartReady.current = true;
150
+ }
151
+ }, [cart, cartReady, cartSend]);
152
+ // Update cart country code if cart and props countryCode's as different
122
153
  useEffect(() => {
123
154
  if (!countryChanged || customerOverridesCountryCode.current)
124
155
  return;
@@ -135,11 +166,11 @@ export function CartProviderV2({ children, numCartLines, onCreate, onLineAdd, on
135
166
  ]);
136
167
  // send cart events when ready
137
168
  const onCartReadySend = useCallback((cartEvent) => {
138
- if (!cartReady) {
169
+ if (!cartReady.current) {
139
170
  return console.warn("Cart isn't ready yet");
140
171
  }
141
172
  cartSend(cartEvent);
142
- }, [cartReady, cartSend]);
173
+ }, [cartSend]);
143
174
  // save cart id to local storage
144
175
  useEffect(() => {
145
176
  if (cartState?.context?.cart?.id && storageAvailable('localStorage')) {
@@ -162,22 +193,6 @@ export function CartProviderV2({ children, numCartLines, onCreate, onLineAdd, on
162
193
  }
163
194
  }
164
195
  }, [cartCompleted]);
165
- // fetch cart from local storage if cart id present and set cart as ready for use
166
- useEffect(() => {
167
- if (!cartReady && storageAvailable('localStorage')) {
168
- try {
169
- const cartId = window.localStorage.getItem(CART_ID_STORAGE_KEY);
170
- if (cartId) {
171
- cartSend({ type: 'CART_FETCH', payload: { cartId } });
172
- }
173
- }
174
- catch (error) {
175
- console.warn('error fetching cartId');
176
- console.warn(error);
177
- }
178
- setCartReady(true);
179
- }
180
- }, [cartReady, cartSend]);
181
196
  const cartCreate = useCallback((cartInput) => {
182
197
  if (countryCode && !cartInput.buyerIdentity?.countryCode) {
183
198
  if (cartInput.buyerIdentity == null) {
@@ -263,15 +278,15 @@ export function CartProviderV2({ children, numCartLines, onCreate, onLineAdd, on
263
278
  },
264
279
  });
265
280
  },
266
- cartFragment: usedCartFragment,
281
+ cartFragment,
267
282
  };
268
283
  }, [
269
284
  cartCreate,
285
+ cartFragment,
270
286
  cartState?.context?.cart,
271
287
  cartState?.context?.errors,
272
288
  cartState.value,
273
289
  onCartReadySend,
274
- usedCartFragment,
275
290
  ]);
276
291
  return (React.createElement(CartContext.Provider, { value: cartContextValue }, children));
277
292
  }
@@ -368,3 +383,103 @@ function publishDiscountCodesUpdateAnalytics(context, event) {
368
383
  prevCart: context.prevCart,
369
384
  });
370
385
  }
386
+ export const defaultCartFragment = `
387
+ fragment CartFragment on Cart {
388
+ id
389
+ checkoutUrl
390
+ totalQuantity
391
+ buyerIdentity {
392
+ countryCode
393
+ customer {
394
+ id
395
+ email
396
+ firstName
397
+ lastName
398
+ displayName
399
+ }
400
+ email
401
+ phone
402
+ }
403
+ lines(first: $numCartLines) {
404
+ edges {
405
+ node {
406
+ id
407
+ quantity
408
+ attributes {
409
+ key
410
+ value
411
+ }
412
+ cost {
413
+ totalAmount {
414
+ amount
415
+ currencyCode
416
+ }
417
+ compareAtAmountPerQuantity {
418
+ amount
419
+ currencyCode
420
+ }
421
+ }
422
+ merchandise {
423
+ ... on ProductVariant {
424
+ id
425
+ availableForSale
426
+ compareAtPriceV2 {
427
+ ...MoneyFragment
428
+ }
429
+ priceV2 {
430
+ ...MoneyFragment
431
+ }
432
+ requiresShipping
433
+ title
434
+ image {
435
+ ...ImageFragment
436
+ }
437
+ product {
438
+ handle
439
+ title
440
+ }
441
+ selectedOptions {
442
+ name
443
+ value
444
+ }
445
+ }
446
+ }
447
+ }
448
+ }
449
+ }
450
+ cost {
451
+ subtotalAmount {
452
+ ...MoneyFragment
453
+ }
454
+ totalAmount {
455
+ ...MoneyFragment
456
+ }
457
+ totalDutyAmount {
458
+ ...MoneyFragment
459
+ }
460
+ totalTaxAmount {
461
+ ...MoneyFragment
462
+ }
463
+ }
464
+ note
465
+ attributes {
466
+ key
467
+ value
468
+ }
469
+ discountCodes {
470
+ code
471
+ }
472
+ }
473
+
474
+ fragment MoneyFragment on MoneyV2 {
475
+ currencyCode
476
+ amount
477
+ }
478
+ fragment ImageFragment on Image {
479
+ id
480
+ url
481
+ altText
482
+ width
483
+ height
484
+ }
485
+ `;
@@ -121,6 +121,12 @@ export declare type CartCreateEvent = {
121
121
  type: 'CART_CREATE';
122
122
  payload: CartInput;
123
123
  };
124
+ export declare type CartSetEvent = {
125
+ type: 'CART_SET';
126
+ payload: {
127
+ cart: CartFragmentFragment;
128
+ };
129
+ };
124
130
  export declare type CartLineAddEvent = {
125
131
  type: 'CARTLINE_ADD';
126
132
  payload: {
@@ -163,7 +169,7 @@ export declare type DiscountCodesUpdateEvent = {
163
169
  discountCodes: string[];
164
170
  };
165
171
  };
166
- export declare type CartMachineActionEvent = CartFetchEvent | CartCreateEvent | CartLineAddEvent | CartLineRemoveEvent | CartLineUpdateEvent | NoteUpdateEvent | BuyerIdentityUpdateEvent | CartAttributesUpdateEvent | DiscountCodesUpdateEvent;
172
+ export declare type CartMachineActionEvent = CartFetchEvent | CartCreateEvent | CartSetEvent | CartLineAddEvent | CartLineRemoveEvent | CartLineUpdateEvent | NoteUpdateEvent | BuyerIdentityUpdateEvent | CartAttributesUpdateEvent | DiscountCodesUpdateEvent;
167
173
  export declare type CartMachineFetchResultEvent = {
168
174
  type: 'CART_COMPLETED';
169
175
  payload: {
@@ -11,12 +11,11 @@ export declare function useCartAPIStateMachine({ numCartLines, onCartActionEntry
11
11
  onCartActionOptimisticUI?: (context: CartMachineContext, event: CartMachineEvent) => Partial<CartMachineContext>;
12
12
  /** A callback that is invoked after a Cart API completes. */
13
13
  onCartActionComplete?: (context: CartMachineContext, event: CartMachineFetchResultEvent) => void;
14
- /** A callback that is invoked after a Cart API completes. */
15
14
  /** An object with fields that correspond to the Storefront API's [Cart object](https://shopify.dev/api/storefront/latest/objects/cart). */
16
15
  data?: CartFragmentFragment;
17
16
  /** A fragment used to query the Storefront API's [Cart object](https://shopify.dev/api/storefront/latest/objects/cart) for all queries and mutations. A default value is used if no argument is provided. */
18
- cartFragment?: string;
17
+ cartFragment: string;
19
18
  /** The ISO country code for i18n. */
20
19
  countryCode?: CountryCode;
21
- }): readonly [StateMachine.State<CartMachineContext, CartMachineEvent, CartMachineTypeState>, (event: "CART_FETCH" | "CART_CREATE" | "CARTLINE_ADD" | "CARTLINE_REMOVE" | "CARTLINE_UPDATE" | "NOTE_UPDATE" | "BUYER_IDENTITY_UPDATE" | "CART_ATTRIBUTES_UPDATE" | "DISCOUNT_CODES_UPDATE" | "CART_COMPLETED" | "RESOLVE" | "ERROR" | CartMachineEvent) => void, StateMachine.Service<CartMachineContext, CartMachineEvent, CartMachineTypeState>];
20
+ }): readonly [StateMachine.State<CartMachineContext, CartMachineEvent, CartMachineTypeState>, (event: "CART_FETCH" | "CART_CREATE" | "CART_SET" | "CARTLINE_ADD" | "CARTLINE_REMOVE" | "CARTLINE_UPDATE" | "NOTE_UPDATE" | "BUYER_IDENTITY_UPDATE" | "CART_ATTRIBUTES_UPDATE" | "DISCOUNT_CODES_UPDATE" | "CART_COMPLETED" | "RESOLVE" | "ERROR" | CartMachineEvent) => void, StateMachine.Service<CartMachineContext, CartMachineEvent, CartMachineTypeState>];
22
21
  export declare function cartFromGraphQL(cart: CartFragmentFragment): Cart;
@@ -53,6 +53,15 @@ const INITIALIZING_CART_EVENTS = {
53
53
  CART_CREATE: {
54
54
  target: 'cartCreating',
55
55
  },
56
+ CART_SET: {
57
+ target: 'idle',
58
+ actions: [
59
+ assign({
60
+ rawCartResult: (_, event) => event.payload.cart,
61
+ cart: (_, event) => cartFromGraphQL(event.payload.cart),
62
+ }),
63
+ ],
64
+ },
56
65
  };
57
66
  const UPDATING_CART_EVENTS = {
58
67
  CARTLINE_ADD: {
@@ -91,10 +100,10 @@ const cartMachine = createMachine({
91
100
  on: INITIALIZING_CART_EVENTS,
92
101
  },
93
102
  idle: {
94
- on: UPDATING_CART_EVENTS,
103
+ on: { ...INITIALIZING_CART_EVENTS, ...UPDATING_CART_EVENTS },
95
104
  },
96
105
  error: {
97
- on: UPDATING_CART_EVENTS,
106
+ on: { ...INITIALIZING_CART_EVENTS, ...UPDATING_CART_EVENTS },
98
107
  },
99
108
  cartFetching: invokeCart('cartFetchAction', {
100
109
  errorTarget: 'initializationError',
@@ -152,9 +152,12 @@ async function processRequest(handleRequest, App, url, request, sessionApi, opti
152
152
  const rsc = runRSC({ App, state, log, request, response });
153
153
  if (isRSCRequest) {
154
154
  const buffered = await bufferReadableStream(rsc.readable.getReader());
155
- postRequestTasks('rsc', 200, request, response);
156
- response.headers.set('cache-control', response.cacheControlHeader);
157
- cacheResponse(response, request, [buffered], revalidate);
155
+ const rscDidError = !!rsc.didError();
156
+ postRequestTasks('rsc', rscDidError ? 500 : 200, request, response, rscDidError);
157
+ if (rscDidError) {
158
+ response.headers.set('cache-control', response.cacheControlHeader);
159
+ cacheResponse(response, request, [buffered], revalidate);
160
+ }
158
161
  return new Response(buffered, {
159
162
  headers: response.headers,
160
163
  });
@@ -319,7 +322,7 @@ async function runSSR({ rsc, state, request, response, nodeResponse, nonce, dev,
319
322
  // Last SSR write might be pending, delay closing the writable one tick
320
323
  setTimeout(() => {
321
324
  writable.close();
322
- postRequestTasks('str', responseOptions.status, request, response);
325
+ postRequestTasks('str', responseOptions.status, request, response, !!didError());
323
326
  response.status = responseOptions.status;
324
327
  cacheResponse(response, request, savedChunks, revalidate);
325
328
  }, 0);
@@ -328,7 +331,7 @@ async function runSSR({ rsc, state, request, response, nodeResponse, nonce, dev,
328
331
  else {
329
332
  // Redirects do not write body
330
333
  writable.close();
331
- postRequestTasks('str', responseOptions.status, request, response);
334
+ postRequestTasks('str', responseOptions.status, request, response, !!didError());
332
335
  }
333
336
  if (response.canStream()) {
334
337
  return new Response(transform.readable, responseOptions);
@@ -376,7 +379,7 @@ async function runSSR({ rsc, state, request, response, nodeResponse, nonce, dev,
376
379
  log.trace('node complete ssr');
377
380
  if (!revalidate &&
378
381
  (response.canStream() || nodeResponse.writableEnded)) {
379
- postRequestTasks('str', nodeResponse.statusCode, request, response);
382
+ postRequestTasks('str', nodeResponse.statusCode, request, response, !!didError());
380
383
  return;
381
384
  }
382
385
  writeHeadToNodeResponse(nodeResponse, response, log, didError());
@@ -396,8 +399,8 @@ async function runSSR({ rsc, state, request, response, nodeResponse, nonce, dev,
396
399
  let html = template;
397
400
  if (!error) {
398
401
  html = assembleHtml({ ssrHtml, rscPayload, request, template });
399
- postRequestTasks('ssr', nodeResponse.statusCode, request, response);
400
402
  }
403
+ postRequestTasks('ssr', nodeResponse.statusCode, request, response, !!didError());
401
404
  if (!nodeResponse.writableEnded) {
402
405
  nodeResponse.write(html);
403
406
  nodeResponse.end();
@@ -506,8 +509,8 @@ function isRedirect(response) {
506
509
  function flightContainer(chunk) {
507
510
  return `<meta data-flight="${htmlEncode(chunk)}" />`;
508
511
  }
509
- function postRequestTasks(type, status, request, response) {
510
- logServerResponse(type, request, status);
512
+ function postRequestTasks(type, status, request, response, didError) {
513
+ logServerResponse(type, request, status, didError);
511
514
  logCacheControlHeaders(type, request, response);
512
515
  logQueryTimings(type, request);
513
516
  request.savePreloadQueries();
@@ -74,7 +74,9 @@ function getCookieDomain(cookieDomain) {
74
74
  function trackPageView(payload) {
75
75
  microSessionCount += 1;
76
76
  try {
77
- sendToServer(storefrontPageViewSchema(payload));
77
+ payload &&
78
+ payload.shopify &&
79
+ sendToServer(storefrontPageViewSchema(payload));
78
80
  }
79
81
  catch (error) {
80
82
  console.error(`Error Shopify analytics: ${ClientAnalytics.eventNames.PAGE_VIEW}`, error);
@@ -94,7 +96,7 @@ function buildStorefrontPageViewPayload(payload) {
94
96
  const shopify = payload.shopify;
95
97
  let formattedData = {
96
98
  appClientId: '6167201',
97
- hydrogenSubchannelId: shopify.storefrontId,
99
+ hydrogenSubchannelId: shopify.storefrontId || '0',
98
100
  isPersistentCookie: shopify.isPersistentCookie,
99
101
  uniqToken: shopify.userId,
100
102
  visitToken: shopify.sessionId,
@@ -10,7 +10,7 @@ import { CacheLong } from '../../../Cache/strategies/index.js';
10
10
  import { gql } from '../../../../utilities/graphql-tag.js';
11
11
  import { SHOPIFY_Y, SHOPIFY_S } from '../../../../constants.js';
12
12
  export function ShopifyAnalytics({ cookieDomain }) {
13
- const { storeDomain } = useShop();
13
+ const { storeDomain, storefrontId } = useShop();
14
14
  const request = useServerRequest();
15
15
  const cookies = parse(request.headers.get('Cookie') || '');
16
16
  const domain = cookieDomain || storeDomain;
@@ -23,7 +23,7 @@ export function ShopifyAnalytics({ cookieDomain }) {
23
23
  shopify: {
24
24
  shopId: id,
25
25
  currency: currencyCode,
26
- storefrontId: globalThis.Oxygen?.env?.SHOPIFY_STOREFRONT_ID || '0',
26
+ storefrontId,
27
27
  acceptedLanguage: request.headers.get('Accept-Language')?.replace(/-.*/, '') || 'en',
28
28
  isPersistentCookie: !!cookies[SHOPIFY_S] || !!cookies[SHOPIFY_Y],
29
29
  },
@@ -16,7 +16,8 @@ export const CLIENT_CONTEXT_ALLOW_LIST = [
16
16
  function makeShopifyContext(shopifyConfig) {
17
17
  const countryCode = shopifyConfig.defaultCountryCode ?? DEFAULT_COUNTRY;
18
18
  const languageCode = shopifyConfig.defaultLanguageCode ?? DEFAULT_LANGUAGE;
19
- const storefrontId = getOxygenVariable(SHOPIFY_STOREFRONT_ID_VARIABLE);
19
+ const storefrontId = shopifyConfig.storefrontId ??
20
+ getOxygenVariable(SHOPIFY_STOREFRONT_ID_VARIABLE);
20
21
  const shopifyProviderServerValue = {
21
22
  defaultCountryCode: countryCode.toUpperCase(),
22
23
  defaultLanguageCode: languageCode.toUpperCase(),
@@ -40,5 +40,5 @@ export { CartQuery } from './components/CartProvider/cart-queries.js';
40
40
  export { fetchSync } from './foundation/fetchSync/server/fetchSync.js';
41
41
  export { type HydrogenRequest } from './foundation/HydrogenRequest/HydrogenRequest.server.js';
42
42
  export { type HydrogenResponse } from './foundation/HydrogenResponse/HydrogenResponse.server.js';
43
- export { type HydrogenRouteProps } from './types.js';
43
+ export { type HydrogenRouteProps, type CachingStrategy } from './types.js';
44
44
  export { type ResourceGetter as HydrogenApiRoute, RequestOptions as HydrogenApiRouteOptions, } from './utilities/apiRoutes.js';
@@ -161,7 +161,7 @@ export async function renderApiRoute(request, route, hydrogenConfig, { session,
161
161
  });
162
162
  }
163
163
  if (!suppressLog) {
164
- logServerResponse('api', request, response.status ?? 200);
164
+ logServerResponse('api', request, response.status ?? 200, false);
165
165
  }
166
166
  if (response instanceof Request) {
167
167
  const url = new URL(request.url);
@@ -24,5 +24,5 @@ export declare type RenderType = 'str' | 'rsc' | 'ssr' | 'api';
24
24
  export declare function getLoggerWithContext(context: Partial<HydrogenRequest>): Logger;
25
25
  export declare const log: Logger;
26
26
  export declare function setLogger(config?: LoggerConfig): void;
27
- export declare function logServerResponse(type: RenderType, request: HydrogenRequest, responseStatus: number): void;
27
+ export declare function logServerResponse(type: RenderType, request: HydrogenRequest, responseStatus: number, didError: boolean): void;
28
28
  export {};
@@ -72,7 +72,7 @@ const SERVER_RESPONSE_MAP = {
72
72
  rsc: 'Server Components',
73
73
  ssr: 'buffered SSR',
74
74
  };
75
- export function logServerResponse(type, request, responseStatus) {
75
+ export function logServerResponse(type, request, responseStatus, didError) {
76
76
  const log = getLoggerWithContext(request);
77
77
  const coloredResponseStatus = responseStatus >= 500
78
78
  ? red(responseStatus)
@@ -85,5 +85,5 @@ export function logServerResponse(type, request, responseStatus) {
85
85
  const styledType = italic(fullType.padEnd(17));
86
86
  const paddedTiming = ((getTime() - request.time).toFixed(2) + ' ms').padEnd(10);
87
87
  const url = parseUrl(type, request.url);
88
- log.debug(`${request.method} ${styledType} ${coloredResponseStatus} ${paddedTiming} ${url}`);
88
+ log.debug(`${request.method} ${styledType} ${coloredResponseStatus} ${didError || responseStatus >= 400 ? red('error') : green('ok ')} ${paddedTiming} ${url}`);
89
89
  }
@@ -13,7 +13,7 @@ export function getStorefrontApiRequestHeaders({ buyerIp, publicStorefrontToken,
13
13
  log.error('No secret Shopify storefront API token was defined. This means your app will be rate limited!\nSee how to add the token: ');
14
14
  }
15
15
  else if (privateStorefrontToken) {
16
- log.warn('The private shopify storefront API token was loaded implicitly by an environment variable. This is deprecated, and instead the variable should be defined directly in the Hydrogen Config.\nFor more information: ');
16
+ log.warn('The private shopify storefront API token was loaded implicitly by an environment variable. This is deprecated, and instead the variable should be defined directly in the Hydrogen Config.\nFor more information: https://shopify.dev/custom-storefronts/hydrogen/framework/hydrogen-config');
17
17
  }
18
18
  }
19
19
  }
@@ -21,11 +21,8 @@ export function getStorefrontApiRequestHeaders({ buyerIp, publicStorefrontToken,
21
21
  storefrontId = getOxygenVariable(SHOPIFY_STOREFRONT_ID_VARIABLE);
22
22
  if (!storefrontIdWarned) {
23
23
  storefrontIdWarned = true;
24
- if (!storefrontId && !__HYDROGEN_DEV__) {
25
- log.warn('No storefrontId was defined. This means the analytics on your admin dashboard will be broken!\nSee how to fix it: ');
26
- }
27
- else if (storefrontId) {
28
- log.warn('The storefrontId was loaded implicitly by an environment variable. This is deprecated, and instead the variable should be defined directly in the Hydrogen Config.\nFor more information: ');
24
+ if (storefrontId) {
25
+ log.warn('The storefrontId was loaded implicitly by an environment variable. This is deprecated, and instead the variable should be defined directly in the Hydrogen Config.\nFor more information: https://shopify.dev/custom-storefronts/hydrogen/framework/hydrogen-config');
29
26
  }
30
27
  }
31
28
  }
@@ -1 +1 @@
1
- export declare const LIB_VERSION = "1.4.1";
1
+ export declare const LIB_VERSION = "1.4.2";
@@ -1 +1 @@
1
- export const LIB_VERSION = '1.4.1';
1
+ export const LIB_VERSION = '1.4.2';
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "engines": {
8
8
  "node": ">=14"
9
9
  },
10
- "version": "1.4.1",
10
+ "version": "1.4.2",
11
11
  "description": "Modern custom Shopify storefronts",
12
12
  "license": "MIT",
13
13
  "main": "dist/esnext/index.js",