@akinon/next 2.0.59-beta.0 → 2.0.59

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/CHANGELOG.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # @akinon/next
2
2
 
3
- ## 2.0.59-beta.0
3
+ ## 2.0.59
4
4
 
5
5
  ### Patch Changes
6
6
 
7
- - cbbbfd75: ZERO-4376: Bootstrap beta cycle (next-main pre-release motor)
7
+ - 9a1a2df: ZERO-4804: Port the deterministic checkout fix to v2 by removing the shared checkout abort controller and the 250 ms delayed validation, and guarding pre-order auto-select middlewares with pre_order preconditions
8
8
 
9
9
  ## 2.0.58
10
10
 
@@ -33,6 +33,15 @@ const customBaseQuery: BaseQueryFn<
33
33
  ).length > 1
34
34
  ) {
35
35
  api.abort('Mutation already in progress.');
36
+
37
+ // Short-circuit explicitly instead of relying on the abort to settle
38
+ // before the request reaches the network (ZERO-4638).
39
+ return {
40
+ error: {
41
+ status: 'CUSTOM_ERROR',
42
+ error: 'Mutation already in progress.'
43
+ } as FetchBaseQueryError
44
+ };
36
45
  }
37
46
 
38
47
  const baseQuery = fetchBaseQuery({
@@ -36,12 +36,6 @@ import {
36
36
  import { devLogger } from '@akinon/next/hooks/use-logger-context';
37
37
  import { LogLevel } from '@akinon/next/hooks/use-logger';
38
38
 
39
- const getLatestState = async (getState: () => any): Promise<any> => {
40
- await new Promise((resolve) => setTimeout(resolve, 250));
41
-
42
- return getState();
43
- };
44
-
45
39
  const recentLogMessages = new Map<string, number>();
46
40
 
47
41
  const getStore = async (): Promise<AppStore> => {
@@ -59,6 +53,10 @@ interface CheckoutResponse {
59
53
  redirect_url?: string;
60
54
  }
61
55
 
56
+ // Log-only diagnostics: checkout mutations are never aborted based on these
57
+ // checks anymore. Invalid auto-dispatches are prevented at their source in
58
+ // redux/middlewares/pre-order, and Commerce stays the authority for manual
59
+ // dispatches (ZERO-4638).
62
60
  const validateCheckoutState = (
63
61
  state: any,
64
62
  validations: Array<{
@@ -66,17 +64,10 @@ const validateCheckoutState = (
66
64
  errorMessage: string;
67
65
  severity?: LogLevel;
68
66
  data?: any;
69
- action?: () => void;
70
67
  }>
71
68
  ) => {
72
69
  validations.forEach(
73
- ({
74
- condition,
75
- errorMessage,
76
- severity = 'error',
77
- data: logData,
78
- action
79
- }) => {
70
+ ({ condition, errorMessage, severity = 'error', data: logData }) => {
80
71
  if (condition(state)) {
81
72
  const now = Date.now();
82
73
  const lastLogged = recentLogMessages.get(errorMessage) || 0;
@@ -90,15 +81,12 @@ const validateCheckoutState = (
90
81
  errorMessage,
91
82
  logData || state.checkout?.preOrder
92
83
  );
93
- action?.();
94
84
  break;
95
85
  case 'warn':
96
86
  devLogger.warn(errorMessage, logData || state.checkout?.preOrder);
97
- action?.();
98
87
  break;
99
88
  default:
100
89
  devLogger.info(errorMessage, logData || state.checkout?.preOrder);
101
- action?.();
102
90
  }
103
91
  }
104
92
  }
@@ -254,6 +242,12 @@ const completeMasterpassPayment = async (
254
242
 
255
243
  let checkoutAbortController = new AbortController();
256
244
 
245
+ /**
246
+ * @deprecated Checkout mutations no longer share an abort signal; nothing in
247
+ * the core aborts this controller anymore. Premature auto-dispatches are
248
+ * prevented in redux/middlewares/pre-order instead (ZERO-4638). Kept only so
249
+ * existing external imports keep compiling.
250
+ */
257
251
  export const getCheckoutAbortSignal = () => {
258
252
  if (checkoutAbortController.signal.aborted) {
259
253
  checkoutAbortController = new AbortController();
@@ -261,11 +255,6 @@ export const getCheckoutAbortSignal = () => {
261
255
  return checkoutAbortController.signal;
262
256
  };
263
257
 
264
- const abortCheckout = () => {
265
- checkoutAbortController.abort();
266
- checkoutAbortController = new AbortController();
267
- };
268
-
269
258
  export const checkoutApi = api.injectEndpoints({
270
259
  endpoints: (build) => ({
271
260
  fetchCheckout: build.query<CheckoutResponse, void>({
@@ -452,13 +441,12 @@ export const checkoutApi = api.injectEndpoints({
452
441
  method: 'POST',
453
442
  body: {
454
443
  delivery_option: String(pk)
455
- },
456
- signal: getCheckoutAbortSignal()
444
+ }
457
445
  }),
458
446
  async onQueryStarted(arg, { dispatch, queryFulfilled, getState }) {
459
447
  dispatch(setShippingStepBusy(true));
460
448
 
461
- const state = await getLatestState(getState);
449
+ const state = getState();
462
450
 
463
451
  validateCheckoutState(state, [
464
452
  {
@@ -468,8 +456,7 @@ export const checkoutApi = api.injectEndpoints({
468
456
  return preOrder?.basket?.basketitem_set?.length === 0;
469
457
  },
470
458
  errorMessage:
471
- 'Your shopping basket is empty. Please add items to your basket before selecting a delivery option.',
472
- action: () => abortCheckout()
459
+ 'Your shopping basket is empty. Please add items to your basket before selecting a delivery option.'
473
460
  }
474
461
  ]);
475
462
 
@@ -490,13 +477,12 @@ export const checkoutApi = api.injectEndpoints({
490
477
  body: {
491
478
  shipping_address: String(shippingAddressPk),
492
479
  billing_address: String(billingAddressPk)
493
- },
494
- signal: getCheckoutAbortSignal()
480
+ }
495
481
  }),
496
482
  async onQueryStarted(arg, { dispatch, queryFulfilled, getState }) {
497
483
  dispatch(setShippingStepBusy(true));
498
484
 
499
- const state = await getLatestState(getState);
485
+ const state = getState();
500
486
 
501
487
  validateCheckoutState(state, [
502
488
  {
@@ -509,8 +495,7 @@ export const checkoutApi = api.injectEndpoints({
509
495
  : false;
510
496
  },
511
497
  errorMessage:
512
- 'You need to select a delivery option before setting your addresses. Dispatch setAddresses action after delivery option selection.',
513
- action: () => abortCheckout()
498
+ 'You need to select a delivery option before setting your addresses. Dispatch setAddresses action after delivery option selection.'
514
499
  }
515
500
  ]);
516
501
 
@@ -530,13 +515,12 @@ export const checkoutApi = api.injectEndpoints({
530
515
  method: 'POST',
531
516
  body: {
532
517
  shipping_option: String(pk)
533
- },
534
- signal: getCheckoutAbortSignal()
518
+ }
535
519
  }),
536
520
  async onQueryStarted(arg, { dispatch, queryFulfilled, getState }) {
537
521
  dispatch(setShippingStepBusy(true));
538
522
 
539
- const state = await getLatestState(getState);
523
+ const state = getState();
540
524
 
541
525
  validateCheckoutState(state, [
542
526
  {
@@ -546,8 +530,7 @@ export const checkoutApi = api.injectEndpoints({
546
530
  return !preOrder?.billing_address;
547
531
  },
548
532
  errorMessage:
549
- 'You need to provide a billing address before selecting a shipping option. Dispatch setShippingOption action after billing address selection.',
550
- action: () => abortCheckout()
533
+ 'You need to provide a billing address before selecting a shipping option. Dispatch setShippingOption action after billing address selection.'
551
534
  },
552
535
  {
553
536
  condition: (state) => {
@@ -556,8 +539,7 @@ export const checkoutApi = api.injectEndpoints({
556
539
  return !preOrder?.shipping_address;
557
540
  },
558
541
  errorMessage:
559
- 'You need to provide a shipping address before selecting a shipping option. Dispatch setShippingOption action after shipping address selection.',
560
- action: () => abortCheckout()
542
+ 'You need to provide a shipping address before selecting a shipping option. Dispatch setShippingOption action after shipping address selection.'
561
543
  }
562
544
  ]);
563
545
 
@@ -614,8 +596,7 @@ export const checkoutApi = api.injectEndpoints({
614
596
  method: 'POST',
615
597
  body: {
616
598
  payment_option: String(pk)
617
- },
618
- signal: getCheckoutAbortSignal()
599
+ }
619
600
  }),
620
601
  async onQueryStarted(arg, { dispatch, queryFulfilled, getState }) {
621
602
  dispatch(setPaymentStepBusy(true));
@@ -624,7 +605,7 @@ export const checkoutApi = api.injectEndpoints({
624
605
  dispatch(setSelectedBankAccountPk(null));
625
606
  dispatch(setCardType(null));
626
607
 
627
- const state = await getLatestState(getState);
608
+ const state = getState();
628
609
 
629
610
  validateCheckoutState(state, [
630
611
  {
@@ -634,8 +615,7 @@ export const checkoutApi = api.injectEndpoints({
634
615
  return !preOrder?.shipping_option?.pk;
635
616
  },
636
617
  errorMessage:
637
- 'You need to select a shipping option before choosing a payment method. Dispatch setPaymentOption action after shipping option selection.',
638
- action: () => abortCheckout()
618
+ 'You need to select a shipping option before choosing a payment method. Dispatch setPaymentOption action after shipping option selection.'
639
619
  }
640
620
  ]);
641
621
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@akinon/next",
3
3
  "description": "Core package for Project Zero Next",
4
- "version": "2.0.59-beta.0",
4
+ "version": "2.0.59",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -36,7 +36,7 @@
36
36
  "set-cookie-parser": "2.6.0"
37
37
  },
38
38
  "devDependencies": {
39
- "@akinon/eslint-plugin-projectzero": "2.0.59-beta.0",
39
+ "@akinon/eslint-plugin-projectzero": "2.0.59",
40
40
  "@babel/core": "7.26.10",
41
41
  "@babel/preset-env": "7.26.9",
42
42
  "@babel/preset-typescript": "7.27.0",
@@ -14,7 +14,7 @@ export const setAddressMiddleware: Middleware = ({
14
14
  return result;
15
15
  }
16
16
 
17
- const { addressList: addresses } = getState().checkout;
17
+ const { addressList: addresses, deliveryOptions } = getState().checkout;
18
18
  const { endpoints: apiEndpoints } = checkoutApi;
19
19
 
20
20
  const hasNoAddresses =
@@ -31,6 +31,14 @@ export const setAddressMiddleware: Middleware = ({
31
31
  isCustomerDelivery &&
32
32
  firstAddressPk
33
33
  ) {
34
+ // Commerce requires a delivery option on the pre_order before addresses
35
+ // can be selected when delivery options are offered. Returning null
36
+ // keeps the pre-fix suppression of outer middlewares for this action
37
+ // (ZERO-4638).
38
+ if (deliveryOptions?.length > 0 && !preOrder.delivery_option?.pk) {
39
+ return null;
40
+ }
41
+
34
42
  dispatch(
35
43
  apiEndpoints.setAddresses.initiate({
36
44
  shippingAddressPk: firstAddressPk,
@@ -23,6 +23,13 @@ export const deliveryOptionMiddleware: Middleware = ({
23
23
  )?.pk;
24
24
 
25
25
  if (customerDeliveryOption) {
26
+ // A delivery option cannot be selected for an empty basket. Returning
27
+ // null keeps the pre-fix suppression of outer middlewares for this
28
+ // action (ZERO-4638).
29
+ if (preOrder.basket?.basketitem_set?.length === 0) {
30
+ return null;
31
+ }
32
+
26
33
  dispatch(
27
34
  apiEndpoints.setDeliveryOption.initiate(customerDeliveryOption)
28
35
  );
@@ -18,6 +18,13 @@ export const paymentOptionMiddleware: Middleware = ({
18
18
  const { endpoints: apiEndpoints } = checkoutApi;
19
19
 
20
20
  if (!preOrder?.payment_option && paymentOptions.length > 0) {
21
+ // Commerce requires a shipping option on the pre_order before a payment
22
+ // option can be selected. Returning null keeps the pre-fix suppression
23
+ // of outer middlewares for this action (ZERO-4638).
24
+ if (!preOrder.shipping_option?.pk) {
25
+ return null;
26
+ }
27
+
21
28
  const firstPaymentOptionPk = paymentOptions[0]?.pk;
22
29
 
23
30
  dispatch(apiEndpoints.setPaymentOption.initiate(firstPaymentOptionPk));
@@ -27,6 +27,16 @@ export const shippingOptionMiddleware: Middleware = ({
27
27
  const defaultShippingOption = shippingOptions[0]?.pk;
28
28
 
29
29
  if (defaultShippingOption) {
30
+ // Commerce requires both addresses on the pre_order before a shipping
31
+ // option can be selected; auto-dispatching earlier (e.g. from the
32
+ // early Checkout refetch during a guest's first address save) races
33
+ // the in-flight AddressSelectionPage request. Returning null keeps
34
+ // the pre-fix suppression of outer middlewares for this action
35
+ // (ZERO-4638).
36
+ if (!preOrder.billing_address || !preOrder.shipping_address) {
37
+ return null;
38
+ }
39
+
30
40
  dispatch(
31
41
  apiEndpoints.setShippingOption.initiate(defaultShippingOption)
32
42
  );