@vtex/faststore 0.0.5 → 0.0.7
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 +101 -0
- package/dist/actions.json +181 -0
- package/dist/catalog.json +1673 -484
- package/dist/cli/UnreachableCaseError-Crd168Vu.js +10 -0
- package/dist/cli/UnreachableCaseError-Crd168Vu.js.map +1 -0
- package/dist/cli/cacheControl-CEojH_TM.js +158 -0
- package/dist/cli/cacheControl-CEojH_TM.js.map +1 -0
- package/dist/cli/cachePolicy-m2vASMRW.js +26 -0
- package/dist/cli/cachePolicy-m2vASMRW.js.map +1 -0
- package/dist/cli/computeCoreSchemaHash-DPJZj-qJ.js +10 -0
- package/dist/cli/computeCoreSchemaHash-DPJZj-qJ.js.map +1 -0
- package/dist/cli/default-BCQHcwbL.js +253 -0
- package/dist/cli/default-BCQHcwbL.js.map +1 -0
- package/dist/cli/disableSuggestions-yO9UqiWn.js +21 -0
- package/dist/cli/disableSuggestions-yO9UqiWn.js.map +1 -0
- package/dist/cli/dist-C1JhdHfo.js +2001 -0
- package/dist/cli/dist-C1JhdHfo.js.map +1 -0
- package/dist/cli/graphql-DG9thCDG.js +14589 -0
- package/dist/cli/graphql-DG9thCDG.js.map +1 -0
- package/dist/cli/index.d.ts +350 -0
- package/dist/cli/index.js +35047 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/index.min-BsIHDfFK.js +613 -0
- package/dist/cli/index.min-BsIHDfFK.js.map +1 -0
- package/dist/cli/inlineTrace-uLgOJ3TH.js +52 -0
- package/dist/cli/inlineTrace-uLgOJ3TH.js.map +1 -0
- package/dist/cli/internalPlugin-DnuwN4_4.js +11 -0
- package/dist/cli/internalPlugin-DnuwN4_4.js.map +1 -0
- package/dist/cli/packageVersion-DQAnIEil.js +6 -0
- package/dist/cli/packageVersion-DQAnIEil.js.map +1 -0
- package/dist/cli/rolldown-runtime-BMI-E3GI.js +44 -0
- package/dist/cli/schemaIsSubgraph-DoGh3OYj.js +17 -0
- package/dist/cli/schemaIsSubgraph-DoGh3OYj.js.map +1 -0
- package/dist/cli/schemaReporting-K-me-3-v.js +199 -0
- package/dist/cli/schemaReporting-K-me-3-v.js.map +1 -0
- package/dist/cli/server-runtime.js +49859 -0
- package/dist/cli/server-runtime.js.map +1 -0
- package/dist/cli/traceTreeBuilder-BIcV2U7b.js +9118 -0
- package/dist/cli/traceTreeBuilder-BIcV2U7b.js.map +1 -0
- package/dist/cli/usageReporting-Bpl8vSZX.js +10201 -0
- package/dist/cli/usageReporting-Bpl8vSZX.js.map +1 -0
- package/dist/cli/v4-CBhFvC49.js +42 -0
- package/dist/cli/v4-CBhFvC49.js.map +1 -0
- package/dist/client.d.ts +2 -1
- package/dist/client.js +2 -1
- package/dist/components.d.ts +32 -9
- package/dist/components.js +35 -14
- package/dist/content-types.json +93 -0
- package/dist/core/dist/define-component.d.ts +37 -0
- package/dist/core/dist/index.d.ts +2 -2
- package/dist/docs/access-code-sign-in.md +1 -1
- package/dist/docs/account-entry-point.md +1 -1
- package/dist/docs/account-menu-identity.md +160 -0
- package/dist/docs/account-menu.md +106 -23
- package/dist/docs/address-summary.md +24 -3
- package/dist/docs/alert-dialog.md +204 -0
- package/dist/docs/avatar.md +103 -0
- package/dist/docs/billing-address.md +284 -0
- package/dist/docs/card-entry.md +250 -0
- package/dist/docs/cardholder-document.md +142 -0
- package/dist/docs/client-preferences-fieldset.md +148 -0
- package/dist/docs/client-preferences.md +2 -1
- package/dist/docs/client-profile-fieldset.md +184 -0
- package/dist/docs/client-profile.md +1 -1
- package/dist/docs/document-field.md +58 -4
- package/dist/docs/field.md +317 -0
- package/dist/docs/form.md +307 -0
- package/dist/docs/identity-edit.md +6 -6
- package/dist/docs/identity-summary.md +5 -5
- package/dist/docs/installments.md +248 -0
- package/dist/docs/my-account-profile.md +2 -1
- package/dist/docs/order-placed-payment-summary.md +138 -0
- package/dist/docs/payment-method-badges.md +232 -0
- package/dist/docs/payment-methods.md +1 -0
- package/dist/docs/payment.md +218 -0
- package/dist/docs/phone-field.md +58 -4
- package/dist/docs/place-order.md +157 -0
- package/dist/docs/product-shelf.md +20 -13
- package/dist/docs/save-card.md +186 -0
- package/dist/docs/saved-cards.md +218 -0
- package/dist/docs/select.md +465 -0
- package/dist/docs/shipping-address.md +110 -258
- package/dist/index.d.ts +5 -2
- package/dist/internal/client.d.ts +3 -1
- package/dist/internal/client.js +4 -2
- package/dist/internal/content.d.ts +2 -2
- package/dist/internal/server.d.ts +4 -1
- package/dist/internal/server.js +4 -1
- package/dist/matter/access-code-sign-in/access-code-sign-in.js +9 -8
- package/dist/matter/access-code-sign-in/access-code-sign-in.js.map +1 -1
- package/dist/matter/account-access-gate/account-access-gate.js.map +1 -1
- package/dist/matter/account-entry-point/account-entry-point.js +1 -1
- package/dist/matter/account-entry-point/account-entry-point.js.map +1 -1
- package/dist/matter/account-menu/account-menu.d.ts +70 -0
- package/dist/matter/account-menu/account-menu.js +201 -9
- package/dist/matter/account-menu/account-menu.js.map +1 -1
- package/dist/matter/account-menu/sign-out-failure.js +50 -0
- package/dist/matter/account-menu/sign-out-failure.js.map +1 -0
- package/dist/matter/account-menu-identity/account-menu-identity.d.ts +21 -0
- package/dist/matter/account-menu-identity/account-menu-identity.js +88 -0
- package/dist/matter/account-menu-identity/account-menu-identity.js.map +1 -0
- package/dist/matter/billing-address/billing-address.d.ts +165 -0
- package/dist/matter/billing-address/billing-address.js +408 -0
- package/dist/matter/billing-address/billing-address.js.map +1 -0
- package/dist/matter/card-entry/card-capture.d.ts +25 -0
- package/dist/matter/card-entry/card-capture.js +25 -0
- package/dist/matter/card-entry/card-capture.js.map +1 -0
- package/dist/matter/card-entry/card-entry.d.ts +120 -0
- package/dist/matter/card-entry/card-entry.js +833 -0
- package/dist/matter/card-entry/card-entry.js.map +1 -0
- package/dist/matter/cardholder-document/cardholder-document.d.ts +45 -0
- package/dist/matter/cardholder-document/cardholder-document.js +70 -0
- package/dist/matter/cardholder-document/cardholder-document.js.map +1 -0
- package/dist/matter/cart/cart.js +1 -1
- package/dist/matter/client-preferences/client-preferences-fieldset.d.ts +77 -0
- package/dist/matter/client-preferences/client-preferences-fieldset.js +299 -0
- package/dist/matter/client-preferences/client-preferences-fieldset.js.map +1 -0
- package/dist/matter/client-preferences/client-preferences.d.ts +3 -5
- package/dist/matter/client-preferences/client-preferences.js +6 -6
- package/dist/matter/client-preferences/client-preferences.js.map +1 -1
- package/dist/matter/client-profile/client-profile.d.ts +4 -6
- package/dist/matter/client-profile/client-profile.js +9 -6
- package/dist/matter/client-profile/client-profile.js.map +1 -1
- package/dist/matter/client-profile-form/client-profile-fieldset.d.ts +119 -0
- package/dist/matter/client-profile-form/client-profile-fieldset.js +488 -0
- package/dist/matter/client-profile-form/client-profile-fieldset.js.map +1 -0
- package/dist/matter/identity-edit/identity-edit.js +2 -2
- package/dist/matter/identity-edit/identity-edit.js.map +1 -1
- package/dist/matter/identity-summary/identity-summary.js +3 -2
- package/dist/matter/identity-summary/identity-summary.js.map +1 -1
- package/dist/matter/installments/installments-select.d.ts +42 -0
- package/dist/matter/installments/installments-select.js +167 -0
- package/dist/matter/installments/installments-select.js.map +1 -0
- package/dist/matter/installments/installments.d.ts +58 -0
- package/dist/matter/installments/installments.js +176 -0
- package/dist/matter/installments/installments.js.map +1 -0
- package/dist/matter/installments/use-installments-selection.d.ts +29 -0
- package/dist/matter/installments/use-installments-selection.js +95 -0
- package/dist/matter/installments/use-installments-selection.js.map +1 -0
- package/dist/matter/my-account-profile/my-account-profile.d.ts +3 -0
- package/dist/matter/my-account-profile/my-account-profile.js +6 -5
- package/dist/matter/my-account-profile/my-account-profile.js.map +1 -1
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.d.ts +63 -0
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js +305 -0
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js.map +1 -0
- package/dist/matter/payment/payment-card-capture.d.ts +19 -0
- package/dist/matter/payment/payment-card-capture.js +129 -0
- package/dist/matter/payment/payment-card-capture.js.map +1 -0
- package/dist/matter/payment/payment-controller.d.ts +24 -0
- package/dist/matter/payment/payment-controller.js +108 -0
- package/dist/matter/payment/payment-controller.js.map +1 -0
- package/dist/matter/payment/payment-method-badges.d.ts +57 -0
- package/dist/matter/payment/payment-method-badges.js +192 -0
- package/dist/matter/payment/payment-method-badges.js.map +1 -0
- package/dist/matter/payment/payment.d.ts +89 -0
- package/dist/matter/payment/payment.js +264 -0
- package/dist/matter/payment/payment.js.map +1 -0
- package/dist/matter/payment/use-payment-method-selection.d.ts +302 -0
- package/dist/matter/payment/use-payment-method-selection.js +458 -0
- package/dist/matter/payment/use-payment-method-selection.js.map +1 -0
- package/dist/matter/payment/use-payment-submit-gate.d.ts +111 -0
- package/dist/matter/payment/use-payment-submit-gate.js +144 -0
- package/dist/matter/payment/use-payment-submit-gate.js.map +1 -0
- package/dist/matter/place-order/place-order.d.ts +74 -0
- package/dist/matter/place-order/place-order.js +379 -0
- package/dist/matter/place-order/place-order.js.map +1 -0
- package/dist/matter/product-list/product-list.d.ts +1 -0
- package/dist/matter/product-list/product-list.js.map +1 -1
- package/dist/matter/product-shelf/product-shelf.d.ts +62 -31
- package/dist/matter/product-shelf/product-shelf.js +159 -57
- package/dist/matter/product-shelf/product-shelf.js.map +1 -1
- package/dist/matter/region/region.js +1 -1
- package/dist/matter/region/region.js.map +1 -1
- package/dist/matter/save-card/save-card.d.ts +49 -0
- package/dist/matter/save-card/save-card.js +188 -0
- package/dist/matter/save-card/save-card.js.map +1 -0
- package/dist/matter/saved-cards/saved-cards.d.ts +79 -0
- package/dist/matter/saved-cards/saved-cards.js +388 -0
- package/dist/matter/saved-cards/saved-cards.js.map +1 -0
- package/dist/matter/shipping-address/country-descriptors.js +20 -10
- package/dist/matter/shipping-address/country-descriptors.js.map +1 -1
- package/dist/matter/shipping-address/shipping-address.d.ts +113 -174
- package/dist/matter/shipping-address/shipping-address.js +480 -791
- package/dist/matter/shipping-address/shipping-address.js.map +1 -1
- package/dist/matter/variant-selector/variant-selector.js +1 -1
- package/dist/particles/address-summary/address-summary.d.ts +24 -0
- package/dist/particles/address-summary/address-summary.js +83 -2
- package/dist/particles/address-summary/address-summary.js.map +1 -1
- package/dist/particles/alert-dialog/alert-dialog.d.ts +68 -0
- package/dist/particles/alert-dialog/alert-dialog.js +187 -0
- package/dist/particles/alert-dialog/alert-dialog.js.map +1 -0
- package/dist/particles/avatar/avatar.d.ts +17 -0
- package/dist/particles/avatar/avatar.js +65 -0
- package/dist/particles/avatar/avatar.js.map +1 -0
- package/dist/particles/checkbox/checkbox.d.ts +7 -3
- package/dist/particles/checkbox/checkbox.js +4 -1
- package/dist/particles/checkbox/checkbox.js.map +1 -1
- package/dist/particles/document-field/document-field.d.ts +48 -5
- package/dist/particles/document-field/document-field.js +189 -32
- package/dist/particles/document-field/document-field.js.map +1 -1
- package/dist/particles/field/bound-field-registry.js +52 -0
- package/dist/particles/field/bound-field-registry.js.map +1 -0
- package/dist/particles/field/field.d.ts +116 -0
- package/dist/particles/field/field.js +497 -6
- package/dist/particles/field/field.js.map +1 -1
- package/dist/particles/fieldset/fieldset.js +2 -2
- package/dist/particles/fieldset/fieldset.js.map +1 -1
- package/dist/particles/form/form.d.ts +104 -0
- package/dist/particles/form/form.js +485 -0
- package/dist/particles/form/form.js.map +1 -0
- package/dist/particles/phone-field/phone-field.d.ts +38 -10
- package/dist/particles/phone-field/phone-field.js +183 -39
- package/dist/particles/phone-field/phone-field.js.map +1 -1
- package/dist/particles/postal-code-field/postal-code-field.js +2 -2
- package/dist/particles/postal-code-field/postal-code-field.js.map +1 -1
- package/dist/particles/select/field-select.d.ts +43 -0
- package/dist/particles/select/field-select.js +150 -0
- package/dist/particles/select/field-select.js.map +1 -0
- package/dist/particles/select/select.d.ts +2 -0
- package/dist/particles/select/select.js.map +1 -1
- package/dist/particles/shared/compose-refs.js +21 -0
- package/dist/particles/shared/compose-refs.js.map +1 -0
- package/dist/schema.graphql +766 -28
- package/dist/sdk/actions/analytics-action-provider.d.ts +15 -0
- package/dist/sdk/actions/analytics-action-provider.js +19 -0
- package/dist/sdk/actions/analytics-action-provider.js.map +1 -0
- package/dist/sdk/actions/index.d.ts +3 -1
- package/dist/sdk/actions/index.js +3 -1
- package/dist/sdk/actions/payment-action-provider.d.ts +22 -0
- package/dist/sdk/actions/payment-action-provider.js +26 -0
- package/dist/sdk/actions/payment-action-provider.js.map +1 -0
- package/dist/sdk/actions/types.d.ts +6 -2
- package/dist/sdk/analytics/analytics-destination.d.ts +43 -0
- package/dist/sdk/analytics/analytics-destination.js +90 -0
- package/dist/sdk/analytics/analytics-destination.js.map +1 -0
- package/dist/sdk/analytics/use-analytics-actions.d.ts +57 -0
- package/dist/sdk/analytics/use-analytics-actions.js +109 -0
- package/dist/sdk/analytics/use-analytics-actions.js.map +1 -0
- package/dist/sdk/cart/use-cart-actions.js +10 -9
- package/dist/sdk/cart/use-cart-actions.js.map +1 -1
- package/dist/sdk/cart/use-cart-validate-on-open.js +2 -1
- package/dist/sdk/cart/use-cart-validate-on-open.js.map +1 -1
- package/dist/sdk/content/create-content-runtime.d.ts +22 -1
- package/dist/sdk/content/create-content-runtime.js +89 -1
- package/dist/sdk/content/create-content-runtime.js.map +1 -1
- package/dist/sdk/data/empty-content-value.js +16 -0
- package/dist/sdk/data/empty-content-value.js.map +1 -0
- package/dist/sdk/data/entity-resolution-context.js +31 -0
- package/dist/sdk/data/entity-resolution-context.js.map +1 -0
- package/dist/sdk/data/entity-subscriber.js +18 -8
- package/dist/sdk/data/entity-subscriber.js.map +1 -1
- package/dist/sdk/data/hot-data-provider.js +2 -1
- package/dist/sdk/data/hot-data-provider.js.map +1 -1
- package/dist/sdk/data/index.js +2 -1
- package/dist/sdk/data/page-reactive-data-root.d.ts +36 -0
- package/dist/sdk/data/page-reactive-data-root.js +4 -2
- package/dist/sdk/data/page-reactive-data-root.js.map +1 -1
- package/dist/sdk/data/reactive-data-provider.js +7 -3
- package/dist/sdk/data/reactive-data-provider.js.map +1 -1
- package/dist/sdk/data/reactive-list-item-data-provider.js +12 -4
- package/dist/sdk/data/reactive-list-item-data-provider.js.map +1 -1
- package/dist/sdk/data/use-reactive-data-settled.js +39 -0
- package/dist/sdk/data/use-reactive-data-settled.js.map +1 -0
- package/dist/sdk/data/use-reactive-entity.js +5 -1
- package/dist/sdk/data/use-reactive-entity.js.map +1 -1
- package/dist/sdk/data/variables-from-content.d.ts +43 -0
- package/dist/sdk/data/variables-from-content.js +57 -0
- package/dist/sdk/data/variables-from-content.js.map +1 -0
- package/dist/sdk/generated/fragment-types.js +2 -0
- package/dist/sdk/generated/fragment-types.js.map +1 -1
- package/dist/sdk/generated/gql.js +2 -1
- package/dist/sdk/generated/gql.js.map +1 -1
- package/dist/sdk/generated/graphql.js +85 -1
- package/dist/sdk/generated/graphql.js.map +1 -1
- package/dist/sdk/i18n/messages/en-US.js +3 -0
- package/dist/sdk/i18n/messages/en-US.json +3 -0
- package/dist/sdk/i18n/messages/en-US.json.d.ts +3 -0
- package/dist/sdk/i18n/messages/es-ES.js +3 -0
- package/dist/sdk/i18n/messages/es-ES.json +3 -0
- package/dist/sdk/i18n/messages/pt-BR.js +3 -0
- package/dist/sdk/i18n/messages/pt-BR.json +3 -0
- package/dist/sdk/identity/use-identity-actions.js +8 -6
- package/dist/sdk/identity/use-identity-actions.js.map +1 -1
- package/dist/sdk/payment/gateway-origin.js +35 -0
- package/dist/sdk/payment/gateway-origin.js.map +1 -0
- package/dist/sdk/payment/gateway.d.ts +85 -0
- package/dist/sdk/payment/gateway.js +183 -0
- package/dist/sdk/payment/gateway.js.map +1 -0
- package/dist/sdk/payment/operations.d.ts +52 -0
- package/dist/sdk/payment/operations.js +223 -0
- package/dist/sdk/payment/operations.js.map +1 -0
- package/dist/sdk/payment/security-code-mask.js +71 -0
- package/dist/sdk/payment/security-code-mask.js.map +1 -0
- package/dist/sdk/payment/submission-registry.d.ts +33 -0
- package/dist/sdk/payment/submission-registry.js +58 -0
- package/dist/sdk/payment/submission-registry.js.map +1 -0
- package/dist/sdk/payment/use-payment-actions.d.ts +78 -0
- package/dist/sdk/payment/use-payment-actions.js +276 -0
- package/dist/sdk/payment/use-payment-actions.js.map +1 -0
- package/dist/sdk/payment/use-place-order.d.ts +65 -0
- package/dist/sdk/payment/use-place-order.js +640 -0
- package/dist/sdk/payment/use-place-order.js.map +1 -0
- package/dist/sdk/region/use-region-actions.js +2 -1
- package/dist/sdk/region/use-region-actions.js.map +1 -1
- package/dist/sdk/session/read-server-session.d.ts +21 -0
- package/dist/sdk/session/read-server-session.js +61 -0
- package/dist/sdk/session/read-server-session.js.map +1 -0
- package/dist/sdk/session/use-session-actions.js +5 -4
- package/dist/sdk/session/use-session-actions.js.map +1 -1
- package/dist/sdk/session/use-session-revalidation.js +2 -1
- package/dist/sdk/session/use-session-revalidation.js.map +1 -1
- package/dist/sdk/shipping/use-shipping-actions.js +4 -3
- package/dist/sdk/shipping/use-shipping-actions.js.map +1 -1
- package/dist/utils/loggable-error-name.js +49 -0
- package/dist/utils/loggable-error-name.js.map +1 -0
- package/dist/utils/resolve-authored-copy.js +18 -0
- package/dist/utils/resolve-authored-copy.js.map +1 -0
- package/dist/utils/safe-href.d.ts +11 -0
- package/dist/utils/substitute-template.js +33 -0
- package/dist/utils/substitute-template.js.map +1 -0
- package/package.json +14 -2
- package/src/styles/tailwind.css +34 -0
- package/dist/docs/client-profile-form.md +0 -192
- package/dist/matter/client-profile-form/client-profile-form.d.ts +0 -126
- package/dist/matter/client-profile-form/client-profile-form.js +0 -662
- package/dist/matter/client-profile-form/client-profile-form.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-place-order.js","names":[],"sources":["../../../src/sdk/payment/use-place-order.ts"],"sourcesContent":["'use client';\n\nimport { useCallback, useMemo, useRef, useState } from 'react';\nimport { loggableErrorName } from '../../utils/loggable-error-name.js';\nimport type { ComponentActions } from '../actions/types.js';\nimport type { PaymentGatewayTransaction } from './gateway.js';\nimport { isTrustedGatewayOrigin } from './gateway-origin.js';\nimport { PAYMENT_NETWORK_ERROR_CODE, type PaymentMoney, type PaymentUserError } from './operations.js';\nimport { type PaymentPreflightCheck, type PaymentSubmission, usePaymentSubmission } from './submission-registry.js';\nimport type {\n PaymentActions,\n PaymentGatewayCallbackResult,\n PaymentStartTransactionResult,\n} from './use-payment-actions.js';\n\n/**\n * The place-order orchestration (specs/use-place-order.md, T13; docs/features/credit-card/rfc.md\n * § D9) — the PCI-critical core of the credit-card feature, and the only module that knows placing an\n * order is three calls rather than one mutation.\n *\n * **This hook never holds card data.** It resolves an opaque continuation from the submission channel\n * by the shopper's entry path and invokes it between the two backend legs; the card stays in the\n * holder's closure (T7 for a new card, T12 for a saved one), which calls\n * `$actions.payment.sendPayments` itself. So the actions this hook needs are `startTransaction`,\n * `gatewayCallback`, and `analytics.track` — never the gateway façade. Two tests assert the boundary\n * against this file's own source text: no card field is named in code, and the single import from\n * `./gateway.js` is the transaction descriptor's type.\n *\n * The order of the pre-flight is load-bearing, not stylistic. The path, the continuation, and the\n * selected method are all resolved *before* `startTransaction` runs, because a transaction that is\n * opened and then cannot be completed is dangling state on the shopper's order form.\n *\n * v1 is the direct-API mode: the gateway leg resolves inline and the shopper stays on the page. The\n * redirect mode — an external navigation with a resume-on-callback route, and with it 3D Secure — is\n * phase 2, which is why no `callbackUrl` reaches the descriptor.\n *\n * Every failure resolves as `userErrors`; nothing here throws for an expected outcome, so the CTA\n * (T14) renders one error path. Its one per-code branch is PLACE_ORDER_UNCONFIRMED, a pending outcome\n * it shows as a status rather than an error (T176).\n */\n\n// ---------------------------------------------------------------------------\n// Client-synthesized error codes\n// ---------------------------------------------------------------------------\n\n/** No entry path chosen, or no holder registered for the one that was. */\nexport const PAYMENT_MISSING_SUBMISSION_CODE = 'MISSING_PAYMENT_SUBMISSION';\n\n/**\n * No payment method selected. Reuses `PaymentStartTransactionErrorCode`'s own value for the same\n * state, so a consumer matches one code whichever side detected it.\n */\nexport const PAYMENT_MISSING_METHOD_CODE = 'MISSING_PAYMENT_METHOD_SELECTION';\n\n/**\n * The transaction opened but cannot be paid — an incomplete payload, or an origin this client will\n * not post a card to. Reuses the server's code for the same meaning (T18 Decision 5).\n */\nexport const PAYMENT_INVALID_TRANSACTION_CODE = 'INVALID_TRANSACTION_DATA';\n\n/**\n * The attempt's own sequence is still running — opened or finalizing, not yet settled. Not a\n * refusal: nothing failed, the record just belongs to a sequence this caller did not start.\n */\nexport const PLACE_ORDER_IN_FLIGHT_CODE = 'PLACE_ORDER_IN_FLIGHT';\n\n/**\n * The gatewayCallback leg ran after the gateway accepted the card POST, and this client could not confirm\n * its outcome — the payment may or may not have been authorized. Not a refusal (T176).\n */\nexport const PLACE_ORDER_UNCONFIRMED_CODE = 'PLACE_ORDER_UNCONFIRMED';\n\nconst MISSING_SUBMISSION_ERROR: PaymentUserError = {\n code: PAYMENT_MISSING_SUBMISSION_CODE,\n message: 'Enter your payment details before placing the order.',\n};\n\nconst MISSING_METHOD_ERROR: PaymentUserError = {\n code: PAYMENT_MISSING_METHOD_CODE,\n message: 'Choose a payment method before placing the order.',\n};\n\nconst INVALID_TRANSACTION_ERROR: PaymentUserError = {\n code: PAYMENT_INVALID_TRANSACTION_CODE,\n message: 'Unable to start the payment. Please try again.',\n};\n\nconst PLACE_ORDER_IN_FLIGHT_ERROR: PaymentUserError = {\n code: PLACE_ORDER_IN_FLIGHT_CODE,\n message: 'This order is already being placed. Please wait.',\n};\n\n/**\n * Worded as a pending state, never a failure: it must not tell the shopper to try again (FR-1).\n * `PlaceOrder`'s `unconfirmedMessage` schema default repeats this text verbatim.\n */\nconst PLACE_ORDER_UNCONFIRMED_ERROR: PaymentUserError = {\n code: PLACE_ORDER_UNCONFIRMED_CODE,\n message: 'We could not confirm your order yet. Your payment may still be processing, so please do not pay again.',\n};\n\n/** Mirrors `usePaymentActions`' own infrastructure-failure userError, which that module keeps private. */\nconst NETWORK_ERROR: PaymentUserError = {\n code: PAYMENT_NETWORK_ERROR_CODE,\n message: 'Unable to complete the request. Please try again.',\n};\n\n// ---------------------------------------------------------------------------\n// Data the descriptor needs from the read model\n// ---------------------------------------------------------------------------\n\nexport type PlaceOrderInstallment = Readonly<{\n count: number;\n /** Per-installment amount, as a display decimal. The gateway's unit is minor units. */\n value: PaymentMoney;\n interestRate: number;\n}>;\n\nexport type PlaceOrderMethod = Readonly<{ id: string; groupName: string }>;\n\n/**\n * The `Payment` slice T14's fragment forwards (arity-1, ADR-0015) — the same convention as\n * `PaymentSectionData`. These five values are the part of the descriptor `paymentStartTransaction`\n * does not carry: T18 deliberately did not re-project the read model onto a write payload.\n */\nexport type PlaceOrderData = {\n selectedMethodId?: string | null;\n selectedInstallment?: PlaceOrderInstallment | null;\n methods?: readonly PlaceOrderMethod[];\n} | null;\n\nexport type PlaceOrderResult = Readonly<{\n /** The placed order's id, once the callback resolved one. */\n orderId: string | null;\n /** Empty on success. Carries the server's own codes verbatim when a backend leg reported one. */\n userErrors: readonly PaymentUserError[];\n}>;\n\nexport type UsePlaceOrder = Readonly<{\n /** Runs the three-call sequence. Resolves; never rejects for an expected outcome. */\n placeOrder: () => Promise<PlaceOrderResult>;\n /** True while the sequence runs — what T14's blocking loading state renders. */\n placing: boolean;\n /** The latest attempt's userErrors. Empty before the first attempt and while one is in flight. */\n userErrors: readonly PaymentUserError[];\n /** The latest attempt's order id. */\n orderId: string | null;\n}>;\n\n// ---------------------------------------------------------------------------\n// Payload settling\n// ---------------------------------------------------------------------------\n\n/**\n * The start payload once every field the descriptor needs is known non-null. `userProfileId` is the\n * exception: a guest order legitimately has none.\n */\ntype SettledTransaction = Readonly<{\n transactionId: string;\n merchantName: string;\n gatewayUrl: string;\n orderGroup: string;\n userProfileId: string | null;\n value: PaymentMoney;\n valueInCents: number;\n referenceValueInCents: number;\n}>;\n\n/** Identifiers, a host, and amounts — nothing here is sensitive, so naming them in a log is safe. */\nfunction missingTransactionFields(payload: PaymentStartTransactionResult): string[] {\n return Object.entries({\n transactionId: payload.transactionId,\n merchantName: payload.merchantName,\n orderGroup: payload.orderGroup,\n value: payload.value,\n valueInCents: payload.valueInCents,\n referenceValueInCents: payload.referenceValueInCents,\n })\n .filter(([, field]) => field === null)\n .map(([name]) => name);\n}\n\n/**\n * Narrows a payload that reported no userErrors, or refuses it. The origin is checked first and\n * separately: it is the field that decides where a card is sent, and a refusal here happens before\n * any holder is asked to assemble one.\n */\nfunction settleTransaction(payload: PaymentStartTransactionResult): SettledTransaction | null {\n if (!isTrustedGatewayOrigin(payload.gatewayUrl)) {\n console.error('[payment.placeOrder] the opened transaction carries no trusted https gateway origin');\n return null;\n }\n\n const { transactionId, merchantName, orderGroup, userProfileId, value, valueInCents, referenceValueInCents } =\n payload;\n\n if (\n transactionId === null ||\n merchantName === null ||\n orderGroup === null ||\n value === null ||\n valueInCents === null ||\n referenceValueInCents === null\n ) {\n console.error(\n '[payment.placeOrder] the opened transaction payload is incomplete',\n missingTransactionFields(payload).join(', '),\n );\n return null;\n }\n\n return {\n gatewayUrl: payload.gatewayUrl,\n transactionId,\n merchantName,\n orderGroup,\n userProfileId,\n value,\n valueInCents,\n referenceValueInCents,\n };\n}\n\n/**\n * The one derived amount on the descriptor. VTEX's `payments[0]` carries no per-installment minor-unit\n * value, so T18 could not expose one and the conversion stays here — rounded, because\n * `19.99 * 100 === 1998.9999999999998`. `Number()` is not redundant: the components codegen maps the\n * `Decimal` scalar to `number` while the resolver serializes it as a string (see the spec's Open items).\n */\nfunction toMinorUnits(amount: number): number {\n return Math.round(Number(amount) * 100);\n}\n\n/**\n * The one call into holder-owned code. A holder's continuation is specified to resolve `userErrors` —\n * `sendPayments` already collapses every gateway outcome to one — so a throw means its contract broke.\n * Only a sanitised error *name* is logged (T160): the closure that threw holds the card, and a message\n * or stack could echo it — and so, unsanitised, could `.name` itself, since it is a writable own\n * property. See `loggableErrorName()`.\n */\nasync function invokeSubmission(\n submit: PaymentSubmission,\n transaction: PaymentGatewayTransaction,\n): Promise<readonly PaymentUserError[]> {\n try {\n // T160: totals the awaited resolution inside this `try`, mirroring `invokeStartTransaction`'s and\n // `invokeGatewayCallback`'s own T156 fix shape — a bare `return await submit(transaction)` let a\n // nullish resolution escape untouched, so the caller's own `submissionErrors.length` read threw\n // one call site away, latching the attempt at `opening` past the point `resume()` can reach it.\n const result = await submit(transaction);\n if (result == null) {\n console.error('[payment.placeOrder] the registered submission resolved with no result');\n return [NETWORK_ERROR];\n }\n return result;\n } catch (error) {\n console.error('[payment.placeOrder] the registered submission threw', loggableErrorName(error));\n return [NETWORK_ERROR];\n }\n}\n\n/**\n * The pre-flight counterpart of `invokeSubmission`, and it inherits that function's one rule: the\n * closure that throws holds the card, so only a sanitised error *name* is logged (T160) — never a\n * message, a stack, or the raw writable `.name` property. See `loggableErrorName()`.\n */\nfunction runPreflight(check: PaymentPreflightCheck): readonly PaymentUserError[] {\n try {\n return check();\n } catch (error) {\n console.error('[payment.placeOrder] the registered pre-flight threw', loggableErrorName(error));\n return [NETWORK_ERROR];\n }\n}\n\n/**\n * The `startTransaction` counterpart of `invokeSubmission`/`runPreflight` (fix pass, Medium finding):\n * before this, `run()` awaited `startTransaction()` directly, so a **rejection** — as opposed to a\n * resolved payload carrying `userErrors` — escaped past every one of `run()`'s own outcome branches\n * and `settleSequence`'s rejection handler never touches the attempt record, leaving it stuck at\n * `opening` for the payment root's lifetime. `opening` is the one phase `placeOrder()` treats as\n * \"already running\" with no failure exit, so every later click resolves `{ orderId: null, userErrors:\n * [] }` without running anything — a false \"Order placed successfully.\" with the CTA disabled forever.\n * `usePlaceOrder` is exported public API and `$actions` is compiler-injected and pluggable, so a\n * provider that rejects here is reachable, not hypothetical. Collapsing the rejection to a resolved\n * payload with one `userErrors` entry means `run()`'s own existing `payload.userErrors.length > 0`\n * branch is what catches it — the same `refused` outcome a server-reported refusal already gets,\n * correct here too since nothing can have been paid before the transaction even opened.\n *\n * T156 fold: the result is destructured inside this `try`, not returned as `await startTransaction()`\n * untouched — a provider resolving `undefined` used to throw at the caller's own\n * `payload.userErrors.length` read, past this function's own `catch`, latching the attempt at\n * `opening` exactly like the rejection case above. Destructuring a nullish resolution throws here\n * instead, so it collapses the same way. Mirrors `invokeGatewayCallback`'s own reason for\n * destructuring, below.\n *\n * Fix pass (review F1/S1): `userErrors` is defaulted with `?? [NETWORK_ERROR]`, not `?? []` — a\n * *partial* payload (present, but missing `userErrors`, e.g. a provider whose GraphQL selection omits\n * the field) used to leave it `undefined`, reaching the caller's `payload.userErrors.length` read\n * (`:627`) and throwing past this function's own `catch`, latching the attempt at `opening` exactly\n * like the cases already fixed. `userErrors` is this result's only signal of whether anything actually\n * went wrong, so a payload that omits it defaults to a *failure*, mirroring a rejection — defaulting to\n * `[]` instead would settle the attempt on a payload this function never actually saw. (The in-repo\n * default provider's own `payload?.userErrors ?? []`, `use-payment-actions.ts:202`, defaults to `[]`\n * only because there `payload` itself — not just the field — is what may be missing.)\n */\nasync function invokeStartTransaction(\n startTransaction: PaymentActions['startTransaction'],\n): Promise<PaymentStartTransactionResult> {\n try {\n const {\n transactionId,\n merchantName,\n gatewayUrl,\n orderGroup,\n userProfileId,\n value,\n valueInCents,\n referenceValueInCents,\n userErrors,\n } = await startTransaction();\n if (userErrors == null) {\n console.error('[payment.placeOrder] startTransaction resolved with no userErrors', orderGroup);\n }\n return {\n transactionId,\n merchantName,\n gatewayUrl,\n orderGroup,\n userProfileId,\n value,\n valueInCents,\n referenceValueInCents,\n userErrors: userErrors ?? [NETWORK_ERROR],\n };\n } catch (error) {\n console.error('[payment.placeOrder] startTransaction threw', loggableErrorName(error));\n return {\n transactionId: null,\n merchantName: null,\n gatewayUrl: null,\n orderGroup: null,\n userProfileId: null,\n value: null,\n valueInCents: null,\n referenceValueInCents: null,\n userErrors: [NETWORK_ERROR],\n };\n }\n}\n\n/**\n * The `gatewayCallback` counterpart of `invokeStartTransaction` above (T156, PR #756 review R-004):\n * before this, both call sites below awaited `gatewayCallback` directly, so a **rejection**\n * escaped past every outcome branch and `settleSequence`'s rejection handler never touches the attempt\n * record, leaving it stuck at `finalizing` for the payment root's lifetime — `placeOrder()` treats\n * `finalizing` exactly like `opening`, an already-running phase with no failure exit, so every later\n * click resolved `{ orderId: attempt.orderId, userErrors: [] }` without issuing anything.\n *\n * The destination is `unresolved`, never `refused` like `invokeStartTransaction`'s own collapse: this\n * leg only runs once the card already reached the gateway, so nothing here can pretend no money moved.\n * `unresolved` is exactly the phase `PlaceOrderAttemptPhase` already documents for this case, below,\n * and is already resumable.\n *\n * Three failure shapes collapse to the same `NETWORK_ERROR` result, logged distinctly (fix pass,\n * review F1/S1/F3): a **rejection**, caught below; a **nullish resolution** (`undefined`/`null`),\n * caught by the explicit check that follows rather than by letting a destructure throw, so it logs a\n * line of its own instead of printing identically to a rejection; and a **partial resolution** — an\n * object present but missing `userErrors`, e.g. a provider whose GraphQL selection omits the field —\n * defaulted with `?? [NETWORK_ERROR]`, not `?? []`. That last case used to return `{ orderId,\n * userErrors }` untouched, leaving `userErrors: undefined`, so the *caller's* `callback.userErrors.length`\n * read threw one call site away from where this wrapper's whole job is to stop exactly that. `[]` would\n * default a payload this function never actually saw to a clean success; `userErrors` is the only\n * signal of whether the callback settled anything, so omitting it means treating it as a failure.\n * Both failure branches log `orderGroup` and `attemptId` — neither is card data\n * (`missingTransactionFields`'s comment above), and this is the one point in the flow where money may\n * already have moved — but never the error's message, stack, or any call argument (FR-6).\n *\n * No `gatewayCallback` call may exist in this module outside this function (INV-2, asserted against\n * this file's own source text in use-place-order.test.tsx) — the two call sites below (`resume()` and\n * `run()`) call this and nothing else.\n */\nasync function invokeGatewayCallback(\n gatewayCallback: PaymentActions['gatewayCallback'],\n orderGroup: string,\n attemptId: string,\n): Promise<PaymentGatewayCallbackResult> {\n let result: PaymentGatewayCallbackResult | null | undefined;\n try {\n result = await gatewayCallback(orderGroup);\n } catch (error) {\n console.error('[payment.placeOrder] gatewayCallback threw', orderGroup, attemptId, loggableErrorName(error));\n return { orderId: null, userErrors: [NETWORK_ERROR] };\n }\n\n if (result == null) {\n console.error('[payment.placeOrder] gatewayCallback resolved with no result', orderGroup, attemptId);\n return { orderId: null, userErrors: [NETWORK_ERROR] };\n }\n\n if (result.userErrors == null) {\n console.error('[payment.placeOrder] gatewayCallback resolved with no userErrors', orderGroup, attemptId);\n }\n return { orderId: result.orderId ?? null, userErrors: result.userErrors ?? [NETWORK_ERROR] };\n}\n\n/**\n * T176 (FR-2, FR-3): what the caller reads from a `gatewayCallback` that reported `userErrors`, on both\n * paths that issue it. The gateway already accepted the card POST by then, so a result made only of this\n * package's own collapse — `PAYMENT_NETWORK_ERROR_CODE`, which `usePaymentActions` and\n * `invokeGatewayCallback` resolve for every thrown, lost or shapeless response — says nothing about\n * whether VTEX processed the order. It becomes PLACE_ORDER_UNCONFIRMED rather than a \"try again\" that\n * invites a second payment. A code the server sent is an answer, and comes back unchanged (INV-10).\n *\n * Total, and never throws: a non-array, or an entry with no readable string `code`, carries no server\n * code, so it reads as unconfirmed too. A server code elsewhere in the same list still wins, whatever\n * order the entries come in.\n */\nfunction reconcileCallbackUserErrors(userErrors: readonly PaymentUserError[]): readonly PaymentUserError[] {\n try {\n if (!Array.isArray(userErrors)) return [PLACE_ORDER_UNCONFIRMED_ERROR];\n if (userErrors.length === 0) return userErrors;\n const carriesServerCode = userErrors.some((userError) => {\n try {\n const code = userError.code;\n return typeof code === 'string' && code !== PAYMENT_NETWORK_ERROR_CODE;\n } catch {\n return false;\n }\n });\n return carriesServerCode ? userErrors : [PLACE_ORDER_UNCONFIRMED_ERROR];\n } catch {\n return [PLACE_ORDER_UNCONFIRMED_ERROR];\n }\n}\n\n/**\n * `crypto.randomUUID()` is secure-context-only (fix pass, Low finding) — on a plain-HTTP preview or\n * staging origin the method does not exist on `crypto` at all, so calling it throws, and every click\n * collapses to a generic `NETWORK_ERROR` with no diagnostic. `localhost` is always a secure context,\n * so dev never reproduces it. Falls back to a timestamp+random string, which only needs to be unique\n * for one payment root's lifetime (the staleness guard below is its one reader) — not a real UUID.\n */\nfunction generateAttemptId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;\n}\n\n/**\n * Fix pass (High finding, race #2): guards every write past an attempt's genesis against a stale,\n * out-of-order completion clobbering a newer outcome. `run()`/`resume()` mint one write *before* their\n * own first await (safe by construction — nothing can have superseded an attempt this call itself just\n * created) and one or more writes *after* an await, once `startTransaction`/the continuation/\n * `gatewayCallback` resolve. Because the writing closure keeps running after its own CTA unmounts\n * (NFR-3), that later write cannot assume the shared attempt is still the one it started with — a\n * remounted or sibling instance may have already resumed it to a newer phase in the meantime. Using\n * the setter's own functional-update form (`SetPlaceOrderAttempt`) checks against whatever the shared\n * attempt actually is *when the write is applied*, not the value this closure captured before its\n * await — mirrors `selectionToken`'s own precedent (`matter/payment/use-payment-method-selection.ts`)\n * for the identical shape of problem, keyed on the attempt's own id instead of a second counter.\n * `settled` is additionally excluded because INV-6 forbids any transition away from it.\n */\nfunction writeAttemptIfCurrent(\n setAttempt: SetPlaceOrderAttempt | null | undefined,\n attemptId: string,\n next: PlaceOrderAttempt | null,\n): void {\n setAttempt?.((prev) => (prev !== null && prev.attemptId === attemptId && prev.phase !== 'settled' ? next : prev));\n}\n\n/**\n * T29 (Decision 3, FR-3): the only path back into a `sent`/`unresolved` attempt — `gatewayCallback` is\n * the sole call it ever issues. Module scope, not a `useCallback` inside the hook (fix pass, Low\n * finding): the previous version of this doc comment claimed `startTransaction`, the submission\n * channel, and `PaymentGatewayTransaction` were \"not in scope,\" so a careless future edit could not\n * make a resume re-pay — true at runtime today (verified by identifier sweep) but false as a\n * structural guarantee, since all three were merely unreferenced identifiers a nested closure could\n * still have read. Hoisting here makes the claim actually true: none of those three is even\n * *importable* from a bare module-level function that takes only `gatewayCallback`/`setAttempt`/\n * `setPlacing`/`current` as arguments — a resume is safe by construction, not by convention.\n */\nasync function resume(\n gatewayCallback: PaymentActions['gatewayCallback'] | undefined,\n setAttempt: SetPlaceOrderAttempt | null | undefined,\n setPlacing: (placing: boolean) => void,\n current: PlaceOrderAttempt,\n): Promise<PlaceOrderResult> {\n if (!gatewayCallback || current.orderGroup === null) {\n // Not reachable through this hook's own writes — §3's own invariant keeps `orderGroup` non-null\n // past `opening` — only through a caller-constructed attempt or a missing action namespace. Fails\n // the same way `run()`'s own first guard does, above.\n console.error('[payment.placeOrder] cannot resume an attempt with no order group');\n return { orderId: null, userErrors: [NETWORK_ERROR] };\n }\n\n // Mirrors `run()`'s own `setPlacing(true)` placement, immediately before its one `await`, so the\n // shared placing state stays accurate across a remount too. A plain write, guarded by\n // `writeAttemptIfCurrent` rather than an unconditional one, since `current` is a caller-supplied\n // snapshot that may already be stale by the time this async function actually runs.\n writeAttemptIfCurrent(setAttempt, current.attemptId, { ...current, phase: 'finalizing' });\n setPlacing(true);\n\n const callback = await invokeGatewayCallback(gatewayCallback, current.orderGroup, current.attemptId);\n const failed = callback.userErrors.length > 0;\n // T176 (FR-4, FR-7): the result is fixed by the callback before the write, so a caller-supplied\n // writer that throws can no longer turn a confirmed order into a rejection — `settleSequence` would\n // resolve that as NETWORK_ERROR. The record may stay at `finalizing` then; nothing here can repair a\n // broken writer. Only a sanitised error name is logged, as on every other catch in this module.\n const result: PlaceOrderResult = failed\n ? { orderId: null, userErrors: reconcileCallbackUserErrors(callback.userErrors) }\n : { orderId: callback.orderId, userErrors: [] };\n\n try {\n writeAttemptIfCurrent(\n setAttempt,\n current.attemptId,\n failed ? { ...current, phase: 'unresolved' } : { ...current, phase: 'settled', orderId: callback.orderId },\n );\n } catch (error) {\n console.error('[payment.placeOrder] the resume record write threw', current.attemptId, loggableErrorName(error));\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// The order-attempt record (T29, specs/make-an-order-attempt-survive-a.md) — an identity and a phase\n// that outlive the CTA's own mount, so a remount or a second click resumes a sequence instead of\n// re-running its dangerous half. Defined here, not in matter/payment/use-payment-method-selection.ts:\n// this is the only module that knows the sequence is three calls, and the layering stays\n// matter → sdk (submission-registry.tsx's own rule) — usePaymentMethodSelection imports this type,\n// mirroring `PaymentPath`'s own alias of `PaymentSubmissionPath`.\n// ---------------------------------------------------------------------------\n\n/**\n * How far an order attempt got. The phase, not an error code, is what says whether money may already\n * have moved — an aborted card POST and a refused transaction both surface as the same userError, and\n * only this distinguishes them.\n */\nexport type PlaceOrderAttemptPhase =\n /** `startTransaction` issued or resolved. Nothing has been paid. */\n | 'opening'\n /** A leg refused before the card left the browser. A fresh full attempt is legal. */\n | 'refused'\n /** The continuation resolved with no userErrors — the card reached the gateway. */\n | 'sent'\n /** The card's fate is unknown: the continuation threw, or the POST was aborted, or a finalization\n * did not resolve. Treated exactly like `sent`. */\n | 'unresolved'\n /** `gatewayCallback` is in flight for this attempt's order group. */\n | 'finalizing'\n /** `gatewayCallback` resolved with no userErrors. Terminal. */\n | 'settled';\n\nexport type PlaceOrderAttempt = Readonly<{\n /** Identity that outlives any one mount. Not `useId()` — that is per-instance and resets. */\n attemptId: string;\n phase: PlaceOrderAttemptPhase;\n /** Resolved by `startTransaction`; the only key a resume needs. Null before `opening` settles. */\n orderGroup: string | null;\n /**\n * Resolved by `gatewayCallback`. Not an independent identifier: the resolver returns\n * `orderId: input.orderGroup` (`checkout/index.ts:1965`), echoing the argument back, because the\n * upstream success response body is discarded. So `orderId === orderGroup` on every success today.\n */\n orderId: string | null;\n}>;\n\n/**\n * Fix pass (High finding, race #2): widened past a plain value so a write can be made conditional on\n * whatever the shared attempt actually is *at write time* — the primitive `useState` already exposes\n * this (its setter has always accepted an updater function; only this package's own declared type\n * stopped a caller from using that form). A plain value remains valid; every existing caller that\n * never needed the guard is unaffected.\n */\nexport type SetPlaceOrderAttempt = (\n update: PlaceOrderAttempt | null | ((prev: PlaceOrderAttempt | null) => PlaceOrderAttempt | null),\n) => void;\n\n// ---------------------------------------------------------------------------\n// The hook\n// ---------------------------------------------------------------------------\n\nexport type UsePlaceOrderArgs = {\n /** `Payment` slice from the caller's fragment. */\n $data?: PlaceOrderData;\n /** Compiler-injected action surface; carries `payment.startTransaction`, `payment.gatewayCallback`, and `analytics.track`. */\n $actions?: ComponentActions;\n /**\n * T29's order-attempt record and its writer — `usePayment().attempt` / `.setAttempt`, forwarded by\n * the caller exactly like `billingAddressDifferent` below. **Optional by necessity, not by safety**\n * (mirrors `submitGate`'s own doc note): a caller that omits either gets today's behaviour, silently\n * — `placeOrder()` always takes the full `run()` path, with no resume and no phase written anywhere.\n * `PlaceOrder` always sources both from `usePayment()`.\n */\n attempt?: PlaceOrderAttempt | null;\n /** See `attempt` above. Identity must be stable across renders (NFR-3) — `usePayment().setAttempt` already is. */\n setAttempt?: SetPlaceOrderAttempt | null;\n /**\n * `usePayment().draft.billingAddressDifferent` (T11's widening of `PaymentDraft`) — an explicit\n * argument, not read from `usePayment()`'s context in this hook, so the hook keeps its existing\n * shape of taking every fact it needs as a parameter rather than adding a second hard context\n * dependency alongside the submission channel below. `null`/omitted folds to the same \"not set\"\n * descriptor shape `gateway.ts`'s own `transaction.billingAddressDifferent ?? false` already expects\n * (fix pass, PR #544 review F1). The caller is responsible for sourcing this from `usePayment()` and\n * forwarding it here.\n */\n billingAddressDifferent?: boolean | null;\n /**\n * A client-side refusal the caller already resolved, or `null` to proceed. Deliberately an opaque\n * `PaymentUserError` rather than the facts behind it: this module may name no identifier matching\n * `CARD_FIELD_PATTERN`, which includes `document`. The caller owns resolving it —\n * `matter/payment/usePaymentSubmitGate()` is the one in this package.\n *\n * **Optional by necessity, not by safety.** This hook is exported public API; `PlaceOrder` is the\n * only in-repo caller, and it always sources this from `usePaymentSubmitGate()`, but nothing stops a\n * storefront from composing its own place-order button directly on this hook. Omitting this argument\n * is silent and behaves exactly as if this whole pre-flight branch did not exist — no cardholder or\n * billing-address refusal ever reaches a CTA built that way, even though the same client-side gate\n * would have caught it. There is no compile-time signal for that omission (making the type mandatory\n * would be a breaking change to a hook other code may already call, and a caller can still opt out\n * explicitly with `null` regardless of the type), so a store composing its own CTA on this hook must\n * source this argument from `usePaymentSubmitGate()` deliberately, not by copying an example that\n * happened to include it.\n */\n submitGate?: PaymentUserError | null;\n};\n\nexport function usePlaceOrder({\n $data,\n $actions,\n attempt,\n setAttempt,\n billingAddressDifferent,\n submitGate,\n}: UsePlaceOrderArgs): UsePlaceOrder {\n const { path, resolve, resolvePreflight } = usePaymentSubmission();\n\n const [placing, setPlacing] = useState(false);\n const [userErrors, setUserErrors] = useState<readonly PaymentUserError[]>([]);\n const [orderId, setOrderId] = useState<string | null>(null);\n const inFlight = useRef<Promise<PlaceOrderResult> | null>(null);\n\n const startTransaction = $actions?.payment?.startTransaction;\n const gatewayCallback = $actions?.payment?.gatewayCallback;\n const track = $actions?.analytics?.track;\n\n const selectedMethodId = $data?.selectedMethodId ?? null;\n const methods = $data?.methods;\n const selectedInstallment = $data?.selectedInstallment ?? null;\n\n const selectedMethod = useMemo(\n () => methods?.find((method) => method.id === selectedMethodId) ?? null,\n [methods, selectedMethodId],\n );\n\n const run = useCallback(async (): Promise<PlaceOrderResult> => {\n if (!startTransaction || !gatewayCallback) {\n console.error('[payment.placeOrder] the payment action namespace is not injected');\n return { orderId: null, userErrors: [NETWORK_ERROR] };\n }\n\n // Resolved before anything is opened (epic, T13): a transaction nothing can complete is dangling\n // state on the order form. The two causes are one state to a shopper and differ only in the log.\n const submit = path === null ? null : resolve(path);\n if (submit === null) {\n console.error('[payment.placeOrder] no submission is registered for the entry path', path ?? 'none');\n return { orderId: null, userErrors: [MISSING_SUBMISSION_ERROR] };\n }\n\n // `paymentSystem` and `groupName` come from the read model, not from the start payload, so a\n // missing selection would otherwise surface only after the transaction was already open.\n if (selectedMethod === null) {\n console.error('[payment.placeOrder] no payment method is selected');\n return { orderId: null, userErrors: [MISSING_METHOD_ERROR] };\n }\n\n // T24's submit gate: an already-resolved client-side refusal (`matter/payment/\n // usePaymentSubmitGate()`), joining the pre-flight so no transaction opens for a submission this\n // client already knows would be refused. Only the opaque verdict is logged — never a fact behind\n // it — keeping this module's own PCI boundary intact (Decision 2).\n if (submitGate) {\n console.error('[payment.placeOrder] a client-side gate refused the submission', submitGate.code);\n return { orderId: null, userErrors: [submitGate] };\n }\n\n // The holder's own pre-flight on the draft it holds (T27). It resolves the same verdict its\n // submission would have resolved at :348, one network leg earlier, so an avoidable input mistake\n // never opens a transaction that is then left dangling. Only the opaque refusal crosses back —\n // this module learns nothing about what was wrong, exactly as with the gate above.\n //\n // Absence is a silent no-op, not a refusal (FR-6) — the same choice `submitGate` two branches\n // above makes on its own absence, staying quiet when it is simply falsy too. The parallel stops\n // there: `submitGate`'s truthiness *is* the refusal, while `preflight`'s only means a check was\n // registered. Deliberate, not overlooked (fix pass, review S1): a holder that registered no check\n // (the two-argument overload) contributes nothing here, exactly as if this branch did not exist.\n // Logging on this branch would fire on the overwhelming majority of calls — it is every pre-T27\n // holder's default, and most of this hook's own test suite exercises it as the baseline — diluting\n // the signal this file's other console.error calls carry for genuine anomalies. Failing closed\n // instead would refuse every one of those callers, breaking `register`'s own optional third\n // argument (NFR-6, Decision 2). A caller that wants this protection must register a check.\n const preflight = path === null ? null : resolvePreflight(path);\n if (preflight) {\n const refusals = runPreflight(preflight);\n if (refusals.length > 0) {\n console.error('[payment.placeOrder] the registered pre-flight refused the submission', refusals[0]?.code);\n return { orderId: null, userErrors: refusals };\n }\n }\n\n // T170 (FR-2/FR-3): the three facts this function's own `catch` needs and cannot read from inside\n // its `try`. `handedOver` records whether the card had already been handed to the registered\n // submission when a throw happened — the record's own phase cannot answer that, since it reads\n // `opening` on both sides of the order-group write below. `settledOrderGroup` carries the one\n // field an `unresolved` record must keep for `resume()` to be able to act on it at all, since the\n // settled payload itself is scoped inside the `try`.\n //\n // `placedAttempt` is the terminal record the success tail below is about to write, captured\n // *before* that write (fix round, Critical finding). `handedOver` cannot stand in for it: it is\n // equally true for the whole span from the card POST onwards, including the window *after*\n // `gatewayCallback` has already resolved a real order — and in that window neither `refused` nor\n // `unresolved` is the truth, because the order is placed. The sequence is the only thing that\n // knows it got there, so it carries the fact rather than letting the `catch` infer a phase the\n // record cannot distinguish.\n //\n // T176 (FR-5, Decision 4): `outcome` is the result each branch past the card hand-over fixes\n // *before* its own record write, so the `catch` can return it. From that point a throw — by a\n // caller-supplied writer or by the tracker — may still need the record recovered, but it no longer\n // changes what the caller reads: a confirmed order stays confirmed, and a refusal stays that refusal.\n // It is still null for a throw raised before any branch fixed a result, which keeps T170's\n // NETWORK_ERROR.\n //\n // Locals, not fields on `PlaceOrderAttempt` (NFR-1): only this closure ever reads them, and\n // putting any of them on the exported record would re-open the question T167 closed — readers\n // second-guessing the phase (INV-4).\n let handedOver = false;\n let settledOrderGroup: string | null = null;\n let placedAttempt: PlaceOrderAttempt | null = null;\n let outcome: PlaceOrderResult | null = null;\n\n // T29 (FR-1/FR-2): a fresh attempt record, minted only once every synchronous refusal guard above\n // has passed — none of those guards ever creates one, so a pre-card refusal (Scenario 6) leaves\n // `attempt` untouched and a fresh full run stays legal next time. Not `useId()` (T28's own\n // `instanceKey`): that resets on every remount, and this identity must not. A plain write, not\n // `writeAttemptIfCurrent`: this is the genesis of the attempt, so there is nothing yet for a\n // concurrent completion to have superseded.\n const attemptId = generateAttemptId();\n setAttempt?.({ attemptId, phase: 'opening', orderGroup: null, orderId: null });\n try {\n // FR-1 (T56): raised here — after every synchronous refusal guard, immediately before the first\n // `await` — so a refusal that returns before this line never commits a `placing: true` frame.\n // `setUserErrors([])` and `setOrderId(null)` remain in `placeOrder` (the click handler), and\n // `settle`'s `setPlacing(false)` is untouched.\n setPlacing(true);\n\n const payload = await invokeStartTransaction(startTransaction);\n\n // Before the completeness check, so a server diagnosis is never replaced by a synthesized one —\n // T18 returns INVALID_TRANSACTION_DATA with every other field null for a transaction VTEX opened\n // with no merchant to pay, which a null-field check would otherwise swallow.\n if (payload.userErrors.length > 0) {\n // T29 (Decision 4, Scenario 6): nothing was paid, so the attempt records `refused` — a fresh\n // full sequence stays legal next time (`placeOrder()`'s own branch falls through to `run()` for\n // any phase other than opening/finalizing/settled/sent/unresolved, `refused` included). Fix\n // pass (Critical finding): this used to reset straight to `null`, which is indistinguishable\n // from \"no attempt ever started\" to a *different*, still-mounted or freshly-remounted instance —\n // `usePaymentMethodSelection`'s shared `placing` derivation reads this phase specifically to\n // release a lock a CTA that unmounted mid-flight can never release itself (FR-5, INV-5).\n // `writeAttemptIfCurrent`, not a plain write: this closure may be running after its own CTA\n // unmounted (NFR-3), so a newer completion for this same attempt must win instead.\n writeAttemptIfCurrent(setAttempt, attemptId, { attemptId, phase: 'refused', orderGroup: null, orderId: null });\n return { orderId: null, userErrors: payload.userErrors };\n }\n\n const settled = settleTransaction(payload);\n if (settled === null) {\n writeAttemptIfCurrent(setAttempt, attemptId, { attemptId, phase: 'refused', orderGroup: null, orderId: null });\n return { orderId: null, userErrors: [INVALID_TRANSACTION_ERROR] };\n }\n\n // T167: orderGroup is known and validated as soon as settleTransaction() returns non-null — well\n // before invokeSubmission() (the card POST, below) resolves. Writing it now closes that gap for the\n // whole 'opening' window instead of leaving it null until a terminal write. Data-completeness only:\n // placeOrder()'s phase branch and resumable (place-order.tsx) still derive only from `phase`, which\n // stays 'opening' here, so neither becomes actionable while this record sits in this phase.\n //\n // T170: kept for the recovery path too, which cannot reach `settled` from the `catch` below.\n settledOrderGroup = settled.orderGroup;\n writeAttemptIfCurrent(setAttempt, attemptId, {\n attemptId,\n phase: 'opening',\n orderGroup: settled.orderGroup,\n orderId: null,\n });\n\n const transaction: PaymentGatewayTransaction = {\n gatewayUrl: settled.gatewayUrl,\n transactionId: settled.transactionId,\n merchantName: settled.merchantName,\n orderGroup: settled.orderGroup,\n userProfileId: settled.userProfileId,\n paymentSystem: selectedMethod.id,\n groupName: selectedMethod.groupName,\n currencyCode: settled.value.currencyCode,\n valueInCents: settled.valueInCents,\n referenceValueInCents: settled.referenceValueInCents,\n installments: selectedInstallment?.count ?? 1,\n // A count: 1 installment is the full transaction value by definition. The read model's\n // selectedInstallment.value for a synthesized 1x tier is option.value (mappers.ts:597),\n // which can differ from the transaction's own value (Decision 8, spec:232) — e.g. an option\n // priced at 10000 on an order form whose value is 8997. Using settled.valueInCents for the\n // 1x case avoids charging option.value instead of the transaction value. For count > 1 the\n // tier value is upstream data, never synthesized, so the per-installment amount is correct.\n installmentsValueInCents:\n selectedInstallment && selectedInstallment.count !== 1\n ? toMinorUnits(selectedInstallment.value.amount)\n : settled.valueInCents,\n interestRate: selectedInstallment?.interestRate ?? 0,\n // Omitted entirely (never an explicit `undefined` key) when the caller passed none, matching\n // `callbackUrl`'s own absent-when-not-provided convention above (fix pass, review F1).\n ...(billingAddressDifferent != null ? { billingAddressDifferent } : {}),\n };\n\n // T170 (FR-3): raised on this side of the await, never the other. From the next line on, the\n // card has left the browser and nothing may recover this attempt as `refused` — which is the\n // only fact the `catch` below cannot re-derive, since the record reads `opening` either way.\n handedOver = true;\n const submissionErrors = await invokeSubmission(submit, transaction);\n // An unpaid transaction is never finalized.\n if (submissionErrors.length > 0) {\n // T29 (FR-2, Decision 5): the card's fate is unknown past this point. `gateway.ts` resolves one\n // opaque userError whether the gateway declined the card, the network failed, or the POST was\n // aborted (FR-6) — this hook cannot and must not try to tell those apart, so every one of them is\n // `unresolved`, never `refused`. Sticky for the payment root's lifetime (FR-4): the only call\n // `resume()` ever issues again is `gatewayCallback(orderGroup)`.\n outcome = { orderId: null, userErrors: submissionErrors };\n writeAttemptIfCurrent(setAttempt, attemptId, {\n attemptId,\n phase: 'unresolved',\n orderGroup: settled.orderGroup,\n orderId: null,\n });\n return outcome;\n }\n\n // T29: the card reached the gateway clean. Past this line the card may be charged — `resume()`\n // (below) is the only legal path back in (Decision 3). Fix pass (High finding): written as\n // `finalizing`, not `sent` — `sent` and `finalizing` are two distinct edges in the spec's own state\n // diagram (`sent --> finalizing: gatewayCallback issued`), but this call issues `gatewayCallback`\n // with no `await` in between, so nothing can ever legally observe the record at rest in `sent`\n // here: React batches same-tick writes, so whichever value is written *last*, before the next\n // `await`, is the only one any render (or a remount reading mid-flight) ever sees. The previous\n // version of this comment argued that fact meant the choice between the two values did not matter;\n // it does — `resumable` (place-order.tsx) treats `sent` as safe to resume, so writing it here left\n // `resumable` true for the entire span of this in-flight `gatewayCallback`, and a remounted or\n // sibling CTA could issue a second, concurrent one on the same order group (this file's own \"is\n // between the card POST and gatewayCallback\" test used to pin exactly that window). `finalizing` is\n // the value that keeps `resumable` false until this call actually settles — exactly what `resume()`\n // (below) already writes for its own, separately-triggered invocation of this same call.\n writeAttemptIfCurrent(setAttempt, attemptId, {\n attemptId,\n phase: 'finalizing',\n orderGroup: settled.orderGroup,\n orderId: null,\n });\n\n const callback = await invokeGatewayCallback(gatewayCallback, settled.orderGroup, attemptId);\n if (callback.userErrors.length > 0) {\n outcome = { orderId: null, userErrors: reconcileCallbackUserErrors(callback.userErrors) };\n writeAttemptIfCurrent(setAttempt, attemptId, {\n attemptId,\n phase: 'unresolved',\n orderGroup: settled.orderGroup,\n orderId: null,\n });\n return outcome;\n }\n\n // Built into a local before it is written, not inline (fix round, Critical finding): the write\n // below is itself a throw site — `setAttempt` is caller-supplied — and from the line above onwards\n // the order is placed. Handing the same value to the `catch` lets it re-assert the terminal truth\n // instead of downgrading a paid order to a resumable one.\n placedAttempt = {\n attemptId,\n phase: 'settled',\n orderGroup: settled.orderGroup,\n orderId: callback.orderId,\n };\n outcome = { orderId: callback.orderId, userErrors: [] };\n\n // GA4's transaction_id is the order a merchant reconciles against. A callback can succeed without\n // resolving one, and that is still a placed order, so the payment transaction id is the fallback.\n // `settled.value.amount` is typed `number` but arrives over the wire as a decimal string (the same\n // Decimal-scalar mismatch `toMinorUnits` coerces above), so GA4's numeric `value` needs the same\n // `Number()` coercion the rest of this package applies at the point of numeric use (e.g.\n // `sdk/cart/optimistic.ts`'s `moneyAdd`).\n //\n // T176 (Decision 5): dispatched before the terminal write, not after it. The event reports VTEX's\n // confirmation, which has already happened; the record is local bookkeeping about that fact, so a\n // writer that throws on it no longer loses the event. A tracker that throws lands in the `catch`\n // before that write, and the recovery write lands `placedAttempt` instead — never a second call.\n track?.({\n name: 'purchase',\n transactionId: callback.orderId ?? settled.transactionId,\n value: { amount: Number(settled.value.amount), currencyCode: settled.value.currencyCode },\n });\n\n writeAttemptIfCurrent(setAttempt, attemptId, placedAttempt);\n\n return outcome;\n } catch (error) {\n // T170 (FR-1): before this, `run()` had no `try`/`catch` of its own, so a throw anywhere past\n // the genesis write above — the synchronous descriptor build, or the caller-supplied `setAttempt`\n // that `writeAttemptIfCurrent` invokes — escaped every one of the outcome branches inside it.\n // `settleSequence`'s rejection handler (`placeOrder`, below) restores this hook's *local* state\n // only and never touches the shared record, so the record stayed latched at `opening` with no\n // sequence alive to move it: every later click hit `placeOrder()`'s own in-flight branch and\n // issued nothing, for the payment root's lifetime.\n //\n // The phase this settles on is not a judgement call, but it is a three-way one, and the first\n // version of this catch read it from `handedOver` alone (fix round, Critical finding). A throw\n // before the card was handed over provably never started the card POST, so nothing was paid and\n // `refused` is correct — a fresh full attempt stays legal (T29 Decision 4). A throw after it\n // with no order resolved means the card's fate is unknown, which is what `unresolved` means\n // (Decision 5) and what `resume()` exists for. But the `settled` write above is a throw site\n // too, and at that one site `gatewayCallback` has already returned a real order while `settled`\n // has not yet landed — so `writeAttemptIfCurrent`'s own `settled` guard does not refuse the\n // write either, and `unresolved` there made `resumable` (`place-order.tsx`) true for an order\n // that was already paid: one click on the re-enabled CTA routed through `resume()` into a second\n // `gatewayCallback` on the same order group, undeduplicated at every layer (no ownership check,\n // no idempotency key in `PaymentGatewayCallbackInput`, and VTEX's behaviour on a repeat carried\n // as unmeasured residual risk under PEXP-2049), doubling pressure on the account-wide\n // `vtex.checkout` breaker as well. Hence `placedAttempt` first: re-asserting the terminal record\n // the failed write was carrying is the only phase that is true there, and it can never be a\n // downgrade, because `writeAttemptIfCurrent` refuses it outright once `settled` has landed\n // (INV-6). Since T176 the `track?.()` call sits before that write, so for a tracker that throws\n // this recovery write is the one that lands `settled`.\n //\n // `writeAttemptIfCurrent`, never a plain write (INV-1): this closure may be running after its\n // own CTA unmounted (NFR-3), so a newer completion for this same attempt must win instead. That\n // write is itself wrapped, because it goes through the same caller-supplied `setAttempt` that may\n // be what threw in the first place (fix round, High finding): `writeAttemptIfCurrent` passes the\n // setter a *function* updater, so a writer that rejects that form — one that serializes,\n // schema-validates or persists what it is given — lands the plain genesis write and throws on\n // every write after it, this one included. Unguarded, that second throw escaped `run()` and left\n // this hook's contract (FR-6: resolve, never reject) resting on `settleSequence` catching it a\n // frame away. Nothing further can be tried once the writer itself is broken — a plain\n // `setAttempt` fallback is exactly the stale-closure clobber INV-1 forbids — so it is logged and\n // the result is returned either way: the `outcome` a branch already fixed (T176, FR-5), or\n // NETWORK_ERROR when the throw came before any branch fixed one. Only a sanitised error *name* is\n // logged, on both lines (FR-7): the closure that threw may hold the card, and a message, a stack,\n // or the raw writable `.name` could echo it. See `loggableErrorName()`.\n console.error('[payment.placeOrder] the place-order sequence threw', attemptId, loggableErrorName(error));\n try {\n writeAttemptIfCurrent(\n setAttempt,\n attemptId,\n placedAttempt ??\n (handedOver\n ? { attemptId, phase: 'unresolved', orderGroup: settledOrderGroup, orderId: null }\n : { attemptId, phase: 'refused', orderGroup: null, orderId: null }),\n );\n } catch (writeError) {\n console.error(\n '[payment.placeOrder] the place-order recovery write threw',\n attemptId,\n loggableErrorName(writeError),\n );\n }\n return outcome ?? { orderId: null, userErrors: [NETWORK_ERROR] };\n }\n }, [\n startTransaction,\n gatewayCallback,\n track,\n path,\n resolve,\n resolvePreflight,\n selectedMethod,\n selectedInstallment,\n billingAddressDifferent,\n submitGate,\n setAttempt,\n ]);\n\n const placeOrder = useCallback<UsePlaceOrder['placeOrder']>(() => {\n // A second click lands before T14's disabled state commits a render, and duplicating a\n // transaction is the worst outcome this surface has — so the guard is the in-flight promise\n // itself, not the `placing` flag. Both callers observe the one attempt that ran.\n if (inFlight.current !== null) return inFlight.current;\n\n // FR-1 (T56): `setPlacing(true)` moved into `run()`/`resume()`, immediately before their own first\n // `await`, so a synchronous refusal never commits a `placing: true` frame. `run()`'s and `resume()`'s\n // outcomes share this one settle wrapper (T29).\n const settleSequence = (sequence: Promise<PlaceOrderResult>): Promise<PlaceOrderResult> => {\n setUserErrors([]);\n setOrderId(null);\n\n const settle = (result: PlaceOrderResult): PlaceOrderResult => {\n inFlight.current = null;\n setPlacing(false);\n setUserErrors(result.userErrors);\n setOrderId(result.orderId);\n return result;\n };\n\n // `run()`/`resume()` resolve for every path this hook drives itself, but a collaborator outside\n // its control (a different $actions provider, or a mock resolving `undefined`) can still reject\n // one. Settling on both paths keeps `placing` from wedging true forever and surfaces an error, so\n // the CTA is clickable again either way. Spec correction (review F2): whether the *next* click\n // actually retries anything is decided by the attempt's phase, not by this settle —\n // `invokeStartTransaction`/`invokeGatewayCallback` each write a resumable phase before their own\n // await and normalize their result, so a throw on either leg lands on `refused`/`unresolved`\n // (T156). T170 closes the rest of that class at the source: `run()` now catches its own throws\n // and settles the record itself, so what is left is a collaborator-owned promise that never\n // settles at all — nothing has thrown, so there is nothing for either that catch or this settle\n // to act on, and bounding it needs a deadline nobody has set yet (T170's own OQ-1).\n const outcome = sequence.then(settle, () => settle({ orderId: null, userErrors: [NETWORK_ERROR] }));\n\n inFlight.current = outcome;\n return outcome;\n };\n\n // T29 (FR-2, FR-3): the CTA's next legal action is derived from the surviving attempt's own phase,\n // not always `run()`. `sent`/`unresolved` resume through a module-level function with no access at\n // all to `startTransaction` or the submission channel (Decision 3); `opening`/`finalizing` are\n // already running — possibly from a mount this closure has outlived (NFR-3) — and `settled` is\n // terminal (INV-6), so none of the three issues anything, independent of whatever a composing\n // CTA's own disabled state happens to do. Only the terminal one reads as a success, though: see\n // the in-flight arm's own comment below. `refused` falls through to a full run below like `null` —\n // nothing was paid, so a fresh attempt is legal (Decision 4). The `'sent'` check just below is\n // defensive-only: `run()` never leaves the record resting there today (it writes `finalizing`\n // directly, same tick — see that write site's own comment); kept so a future write path resting at\n // `sent` would still resume correctly instead of silently falling through to a full run.\n if (attempt) {\n if (attempt.phase === 'settled') {\n return Promise.resolve({ orderId: attempt.orderId, userErrors: [] });\n }\n // T170 (FR-5, Decision 4): split off the two in-flight phases, which used to share this arm and\n // so resolved the empty `userErrors` this hook's own contract defines as success\n // (`PlaceOrderResult`) — a reader that did not start the sequence was handed a success shape for\n // an order still in flight, and a CTA derived from it (`place-order.tsx`'s `succeeded`) rendered\n // \"placed\" for an order nobody had paid for. Settled through `settleSequence` like every other\n // outcome, so the refusal reaches this hook's own `userErrors` rather than only the resolved\n // value a composing CTA may never read. Still issues nothing, and still reads the phase alone\n // (INV-4): waiting is the correct behaviour while the card POST is genuinely open.\n if (attempt.phase === 'opening' || attempt.phase === 'finalizing') {\n return settleSequence(Promise.resolve({ orderId: attempt.orderId, userErrors: [PLACE_ORDER_IN_FLIGHT_ERROR] }));\n }\n if (attempt.phase === 'sent' || attempt.phase === 'unresolved') {\n return settleSequence(resume(gatewayCallback, setAttempt, setPlacing, attempt));\n }\n }\n\n return settleSequence(run());\n }, [run, attempt, gatewayCallback, setAttempt]);\n\n return useMemo(() => ({ placeOrder, placing, userErrors, orderId }), [placeOrder, placing, userErrors, orderId]);\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { defineActions } from "../../core/dist/actions/registry.js";
|
|
3
3
|
import "../../core/dist/index.js";
|
|
4
|
+
import { loggableErrorName } from "../../utils/loggable-error-name.js";
|
|
4
5
|
import { REGION_UPDATE } from "./region-operations.js";
|
|
5
6
|
import { useMutation } from "@apollo/client/react";
|
|
6
7
|
import { useCallback, useMemo } from "react";
|
|
@@ -38,7 +39,7 @@ function useRegionActions() {
|
|
|
38
39
|
try {
|
|
39
40
|
return (await updateMutation({ variables: { postalCode } })).data?.regionUpdate.errors ?? [];
|
|
40
41
|
} catch (error) {
|
|
41
|
-
console.error("[region.setPostalCode] regionUpdate failed", error);
|
|
42
|
+
console.error("[region.setPostalCode] regionUpdate failed", loggableErrorName(error));
|
|
42
43
|
return [NETWORK_USER_ERROR];
|
|
43
44
|
}
|
|
44
45
|
}, [updateMutation]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-region-actions.js","names":[],"sources":["../../../src/sdk/region/use-region-actions.ts"],"sourcesContent":["'use client';\n\nimport { useMutation } from '@apollo/client/react';\nimport { defineActions } from '@vtex/fs-core';\nimport { useCallback, useMemo } from 'react';\nimport { REGION_UPDATE, type RegionUserError } from './region-operations.js';\n\nexport type { RegionUserError } from './region-operations.js';\n\n/**\n * The `region` action namespace — the one sanctioned client write surface for the shopper's active\n * region (docs/storefront-core/adr-0010-actions-own-the-write-side.md).\n *\n * The single action is async/pessimistic: the handler awaits the mutation and only the\n * server-confirmed payload changes the cache. A region cannot be guessed client-side, because the\n * server owns both the postal-code format and the city it resolves to. The handler is stateless — no\n * reactive vars, no module state — since the active value lives in the normalized cache and its\n * persistence is the server's business.\n *\n * `setPostalCode` resolves the payload `errors` (empty on success) and never throws for an expected\n * outcome; infrastructure failures resolve as the synthetic `NETWORK_ERROR`, matching `session` and\n * `identity`.\n */\nexport type RegionActions = Readonly<{\n /** Submits a postal code as the shopper typed it (masked). Resolves to [] on success. Never throws. */\n setPostalCode: (postalCode: string) => Promise<readonly RegionUserError[]>;\n}>;\n\nconst NETWORK_USER_ERROR: RegionUserError = {\n code: 'NETWORK_ERROR',\n message: 'Unable to set your region. Please try again.',\n};\n\n/**\n * The `region` namespace hook. Registered with the first-party action registry via `regionActions`\n * below; the compiler-emitted `RegionActionProvider` calls it and threads the narrowed method into\n * each consumer's `$actions.region`.\n *\n * A successful write merges the payload into the normalized cache — it selects the full `Region`\n * field set including `id`, so Apollo identifies the entity and every `RegionFragment` subscriber\n * re-renders with no refetch. The merge alone is not enough on the *first* write of a session,\n * though: `Query.region` is nullable, so a shopper who arrives with no region has `null` stored at\n * that root field, and merging an entity never repoints a root field that holds null. The reactive\n * read resolves the entity *through* `Query.region`, so it would keep reading null until a reload.\n * Hence the `update` below, which points the root field at the entity. It is idempotent, so writes\n * after the first cost nothing.\n *\n * The masked value is passed through untouched: the server strips the mask and echoes back a\n * formatted `postalCode`, which keeps one authority on format instead of two that can diverge.\n */\nexport function useRegionActions(): RegionActions {\n const [updateMutation] = useMutation(REGION_UPDATE, {\n update(cache, { data }) {\n const region = data?.regionUpdate.region;\n if (!region) return;\n const id = cache.identify(region);\n if (!id) return;\n cache.modify({\n fields: {\n region: (_existing, { toReference }) => toReference(id),\n },\n });\n },\n });\n\n const setPostalCode = useCallback<RegionActions['setPostalCode']>(\n async (postalCode) => {\n try {\n const result = await updateMutation({ variables: { postalCode } });\n return result.data?.regionUpdate.errors ?? [];\n } catch (error) {\n console.error('[region.setPostalCode] regionUpdate failed', error);\n return [NETWORK_USER_ERROR];\n }\n },\n [updateMutation],\n );\n\n return useMemo(() => ({ setPostalCode }), [setPostalCode]);\n}\n\n/**\n * Declares the `region` action namespace — the single source of truth for its method/payload\n * metadata, runtime provider, and `use` hook (mirrors `sessionActions`). The native catalog builder\n * (`scripts/build-catalog.mjs`) statically reads this call to emit `actions.json`; at runtime\n * `RegionActionProvider` calls `useRegionActions` directly.\n */\nexport const regionActions = defineActions({\n namespace: 'region',\n description: \"Mutation-backed writes against the shopper's active region (postal code).\",\n provider: { name: 'RegionActionProvider', importPath: '@vtex/faststore/internal/client' },\n methods: {\n setPostalCode: {\n description:\n 'Set the active region from a postal code, masked or bare (async/pessimistic). Resolves the payload errors.',\n payload: [{ name: 'postalCode', type: 'string' }],\n },\n },\n use: useRegionActions,\n});\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"use-region-actions.js","names":[],"sources":["../../../src/sdk/region/use-region-actions.ts"],"sourcesContent":["'use client';\n\nimport { useMutation } from '@apollo/client/react';\nimport { defineActions } from '@vtex/fs-core';\nimport { useCallback, useMemo } from 'react';\nimport { loggableErrorName } from '../../utils/loggable-error-name.js';\nimport { REGION_UPDATE, type RegionUserError } from './region-operations.js';\n\nexport type { RegionUserError } from './region-operations.js';\n\n/**\n * The `region` action namespace — the one sanctioned client write surface for the shopper's active\n * region (docs/storefront-core/adr-0010-actions-own-the-write-side.md).\n *\n * The single action is async/pessimistic: the handler awaits the mutation and only the\n * server-confirmed payload changes the cache. A region cannot be guessed client-side, because the\n * server owns both the postal-code format and the city it resolves to. The handler is stateless — no\n * reactive vars, no module state — since the active value lives in the normalized cache and its\n * persistence is the server's business.\n *\n * `setPostalCode` resolves the payload `errors` (empty on success) and never throws for an expected\n * outcome; infrastructure failures resolve as the synthetic `NETWORK_ERROR`, matching `session` and\n * `identity`.\n */\nexport type RegionActions = Readonly<{\n /** Submits a postal code as the shopper typed it (masked). Resolves to [] on success. Never throws. */\n setPostalCode: (postalCode: string) => Promise<readonly RegionUserError[]>;\n}>;\n\nconst NETWORK_USER_ERROR: RegionUserError = {\n code: 'NETWORK_ERROR',\n message: 'Unable to set your region. Please try again.',\n};\n\n/**\n * The `region` namespace hook. Registered with the first-party action registry via `regionActions`\n * below; the compiler-emitted `RegionActionProvider` calls it and threads the narrowed method into\n * each consumer's `$actions.region`.\n *\n * A successful write merges the payload into the normalized cache — it selects the full `Region`\n * field set including `id`, so Apollo identifies the entity and every `RegionFragment` subscriber\n * re-renders with no refetch. The merge alone is not enough on the *first* write of a session,\n * though: `Query.region` is nullable, so a shopper who arrives with no region has `null` stored at\n * that root field, and merging an entity never repoints a root field that holds null. The reactive\n * read resolves the entity *through* `Query.region`, so it would keep reading null until a reload.\n * Hence the `update` below, which points the root field at the entity. It is idempotent, so writes\n * after the first cost nothing.\n *\n * The masked value is passed through untouched: the server strips the mask and echoes back a\n * formatted `postalCode`, which keeps one authority on format instead of two that can diverge.\n */\nexport function useRegionActions(): RegionActions {\n const [updateMutation] = useMutation(REGION_UPDATE, {\n update(cache, { data }) {\n const region = data?.regionUpdate.region;\n if (!region) return;\n const id = cache.identify(region);\n if (!id) return;\n cache.modify({\n fields: {\n region: (_existing, { toReference }) => toReference(id),\n },\n });\n },\n });\n\n const setPostalCode = useCallback<RegionActions['setPostalCode']>(\n async (postalCode) => {\n try {\n const result = await updateMutation({ variables: { postalCode } });\n return result.data?.regionUpdate.errors ?? [];\n } catch (error) {\n console.error('[region.setPostalCode] regionUpdate failed', loggableErrorName(error));\n return [NETWORK_USER_ERROR];\n }\n },\n [updateMutation],\n );\n\n return useMemo(() => ({ setPostalCode }), [setPostalCode]);\n}\n\n/**\n * Declares the `region` action namespace — the single source of truth for its method/payload\n * metadata, runtime provider, and `use` hook (mirrors `sessionActions`). The native catalog builder\n * (`scripts/build-catalog.mjs`) statically reads this call to emit `actions.json`; at runtime\n * `RegionActionProvider` calls `useRegionActions` directly.\n */\nexport const regionActions = defineActions({\n namespace: 'region',\n description: \"Mutation-backed writes against the shopper's active region (postal code).\",\n provider: { name: 'RegionActionProvider', importPath: '@vtex/faststore/internal/client' },\n methods: {\n setPostalCode: {\n description:\n 'Set the active region from a postal code, masked or bare (async/pessimistic). Resolves the payload errors.',\n payload: [{ name: 'postalCode', type: 'string' }],\n },\n },\n use: useRegionActions,\n});\n"],"mappings":";;;;;;;;AA6BA,MAAM,qBAAsC;CAC1C,MAAM;CACN,SAAS;AACX;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,mBAAkC;CAChD,MAAM,CAAC,kBAAkB,YAAY,eAAe,EAClD,OAAO,OAAO,EAAE,QAAQ;EACtB,MAAM,SAAS,MAAM,aAAa;EAClC,IAAI,CAAC,QAAQ;EACb,MAAM,KAAK,MAAM,SAAS,MAAM;EAChC,IAAI,CAAC,IAAI;EACT,MAAM,OAAO,EACX,QAAQ,EACN,SAAS,WAAW,EAAE,kBAAkB,YAAY,EAAE,EACxD,EACF,CAAC;CACH,EACF,CAAC;CAED,MAAM,gBAAgB,YACpB,OAAO,eAAe;EACpB,IAAI;GAEF,QAAO,MADc,eAAe,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,EAAA,CACnD,MAAM,aAAa,UAAU,CAAC;EAC9C,SAAS,OAAO;GACd,QAAQ,MAAM,8CAA8C,kBAAkB,KAAK,CAAC;GACpF,OAAO,CAAC,kBAAkB;EAC5B;CACF,GACA,CAAC,cAAc,CACjB;CAEA,OAAO,eAAe,EAAE,cAAc,IAAI,CAAC,aAAa,CAAC;AAC3D;AAQ6B,cAAc;CACzC,WAAW;CACX,aAAa;CACb,UAAU;EAAE,MAAM;EAAwB,YAAY;CAAkC;CACxF,SAAS,EACP,eAAe;EACb,aACE;EACF,SAAS,CAAC;GAAE,MAAM;GAAc,MAAM;EAAS,CAAC;CAClD,EACF;CACA,KAAK;AACP,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { CreateClientOptions } from "../client/create-client.js";
|
|
2
|
+
//#region src/sdk/session/read-server-session.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Classifies `Query.session` for a server caller.
|
|
5
|
+
*
|
|
6
|
+
* Always resolves. `signed-in` is `loggedIn === true`. An identified guest is `anonymous`. A
|
|
7
|
+
* throw, a GraphQL error, a missing `session`, or a timeout is `failed`, including a partial
|
|
8
|
+
* payload that says `loggedIn: true`. The result carries no session payload. No `Cookie` header
|
|
9
|
+
* is set here, so `createClient` keeps forwarding `cookies()`.
|
|
10
|
+
*/
|
|
11
|
+
export type ServerSessionRead = {
|
|
12
|
+
status: 'signed-in';
|
|
13
|
+
} | {
|
|
14
|
+
status: 'anonymous';
|
|
15
|
+
} | {
|
|
16
|
+
status: 'failed';
|
|
17
|
+
reason: string;
|
|
18
|
+
};
|
|
19
|
+
export declare function readServerSession(options?: CreateClientOptions): Promise<ServerSessionRead>;
|
|
20
|
+
//#endregion
|
|
21
|
+
//# sourceMappingURL=read-server-session.d.ts.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ServerSessionReadDocument } from "../generated/graphql.js";
|
|
2
|
+
import { createClient } from "../client/create-client.js";
|
|
3
|
+
import { CombinedGraphQLErrors } from "@apollo/client/errors";
|
|
4
|
+
//#region src/sdk/session/read-server-session.ts
|
|
5
|
+
/**
|
|
6
|
+
* Local bound for the session read. Not the content runtime's request timeout: that module must
|
|
7
|
+
* stay off the server barrel.
|
|
8
|
+
*/
|
|
9
|
+
const SERVER_SESSION_READ_TIMEOUT_MS = 5e3;
|
|
10
|
+
async function readServerSession(options) {
|
|
11
|
+
try {
|
|
12
|
+
const result = await createClient(options).query({
|
|
13
|
+
query: ServerSessionReadDocument,
|
|
14
|
+
errorPolicy: "all",
|
|
15
|
+
fetchPolicy: "no-cache",
|
|
16
|
+
context: { fetchOptions: { signal: AbortSignal.timeout(SERVER_SESSION_READ_TIMEOUT_MS) } }
|
|
17
|
+
});
|
|
18
|
+
if (isTimeoutError(result.error)) return {
|
|
19
|
+
status: "failed",
|
|
20
|
+
reason: "timeout"
|
|
21
|
+
};
|
|
22
|
+
if (result.error || result.data?.session == null) return {
|
|
23
|
+
status: "failed",
|
|
24
|
+
reason: graphqlFailureReason(result.error)
|
|
25
|
+
};
|
|
26
|
+
if (result.data.session.loggedIn === true) return { status: "signed-in" };
|
|
27
|
+
return { status: "anonymous" };
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (isTimeoutError(error)) return {
|
|
30
|
+
status: "failed",
|
|
31
|
+
reason: "timeout"
|
|
32
|
+
};
|
|
33
|
+
if (error instanceof Error && error.message.length > 0) return {
|
|
34
|
+
status: "failed",
|
|
35
|
+
reason: error.message
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
status: "failed",
|
|
39
|
+
reason: "network error"
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function graphqlFailureReason(error) {
|
|
44
|
+
if (CombinedGraphQLErrors.is(error)) {
|
|
45
|
+
const message = error.errors[0]?.message;
|
|
46
|
+
if (typeof message === "string" && message.length > 0) return message;
|
|
47
|
+
}
|
|
48
|
+
return "graphql error";
|
|
49
|
+
}
|
|
50
|
+
function isTimeoutError(error) {
|
|
51
|
+
if (hasAbortName(error)) return true;
|
|
52
|
+
if (typeof error === "object" && error !== null && "cause" in error) return hasAbortName(error.cause);
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
function hasAbortName(error) {
|
|
56
|
+
return typeof error === "object" && error !== null && "name" in error && (error.name === "TimeoutError" || error.name === "AbortError");
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { readServerSession };
|
|
60
|
+
|
|
61
|
+
//# sourceMappingURL=read-server-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-server-session.js","names":[],"sources":["../../../src/sdk/session/read-server-session.ts"],"sourcesContent":["import { CombinedGraphQLErrors } from '@apollo/client/errors';\nimport type { CreateClientOptions } from '../client/create-client.js';\nimport { createClient } from '../client/create-client.js';\nimport { ServerSessionReadDocument } from '../generated/graphql.js';\n\n/**\n * Local bound for the session read. Not the content runtime's request timeout: that module must\n * stay off the server barrel.\n */\nconst SERVER_SESSION_READ_TIMEOUT_MS = 5_000;\n\n/**\n * Classifies `Query.session` for a server caller.\n *\n * Always resolves. `signed-in` is `loggedIn === true`. An identified guest is `anonymous`. A\n * throw, a GraphQL error, a missing `session`, or a timeout is `failed`, including a partial\n * payload that says `loggedIn: true`. The result carries no session payload. No `Cookie` header\n * is set here, so `createClient` keeps forwarding `cookies()`.\n */\nexport type ServerSessionRead =\n | { status: 'signed-in' }\n | { status: 'anonymous' }\n | { status: 'failed'; reason: string };\n\nexport async function readServerSession(options?: CreateClientOptions): Promise<ServerSessionRead> {\n try {\n const client = createClient(options);\n const result = await client.query({\n query: ServerSessionReadDocument,\n errorPolicy: 'all',\n fetchPolicy: 'no-cache',\n context: { fetchOptions: { signal: AbortSignal.timeout(SERVER_SESSION_READ_TIMEOUT_MS) } },\n });\n\n // A timeout lands on `result.error` under `errorPolicy: 'all'` rather than rejecting.\n if (isTimeoutError(result.error)) {\n return { status: 'failed', reason: 'timeout' };\n }\n\n if (result.error || result.data?.session == null) {\n return { status: 'failed', reason: graphqlFailureReason(result.error) };\n }\n\n if (result.data.session.loggedIn === true) {\n return { status: 'signed-in' };\n }\n\n return { status: 'anonymous' };\n } catch (error) {\n if (isTimeoutError(error)) {\n return { status: 'failed', reason: 'timeout' };\n }\n if (error instanceof Error && error.message.length > 0) {\n return { status: 'failed', reason: error.message };\n }\n return { status: 'failed', reason: 'network error' };\n }\n}\n\nfunction graphqlFailureReason(error: unknown): string {\n if (CombinedGraphQLErrors.is(error)) {\n const message = error.errors[0]?.message;\n if (typeof message === 'string' && message.length > 0) return message;\n }\n return 'graphql error';\n}\n\nfunction isTimeoutError(error: unknown): boolean {\n if (hasAbortName(error)) return true;\n if (typeof error === 'object' && error !== null && 'cause' in error) {\n return hasAbortName(error.cause);\n }\n return false;\n}\n\nfunction hasAbortName(error: unknown): boolean {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'name' in error &&\n (error.name === 'TimeoutError' || error.name === 'AbortError')\n );\n}\n"],"mappings":";;;;;;;;AASA,MAAM,iCAAiC;AAevC,eAAsB,kBAAkB,SAA2D;CACjG,IAAI;EAEF,MAAM,SAAS,MADA,aAAa,OACF,CAAC,CAAC,MAAM;GAChC,OAAO;GACP,aAAa;GACb,aAAa;GACb,SAAS,EAAE,cAAc,EAAE,QAAQ,YAAY,QAAQ,8BAA8B,EAAE,EAAE;EAC3F,CAAC;EAGD,IAAI,eAAe,OAAO,KAAK,GAC7B,OAAO;GAAE,QAAQ;GAAU,QAAQ;EAAU;EAG/C,IAAI,OAAO,SAAS,OAAO,MAAM,WAAW,MAC1C,OAAO;GAAE,QAAQ;GAAU,QAAQ,qBAAqB,OAAO,KAAK;EAAE;EAGxE,IAAI,OAAO,KAAK,QAAQ,aAAa,MACnC,OAAO,EAAE,QAAQ,YAAY;EAG/B,OAAO,EAAE,QAAQ,YAAY;CAC/B,SAAS,OAAO;EACd,IAAI,eAAe,KAAK,GACtB,OAAO;GAAE,QAAQ;GAAU,QAAQ;EAAU;EAE/C,IAAI,iBAAiB,SAAS,MAAM,QAAQ,SAAS,GACnD,OAAO;GAAE,QAAQ;GAAU,QAAQ,MAAM;EAAQ;EAEnD,OAAO;GAAE,QAAQ;GAAU,QAAQ;EAAgB;CACrD;AACF;AAEA,SAAS,qBAAqB,OAAwB;CACpD,IAAI,sBAAsB,GAAG,KAAK,GAAG;EACnC,MAAM,UAAU,MAAM,OAAO,EAAE,EAAE;EACjC,IAAI,OAAO,YAAY,YAAY,QAAQ,SAAS,GAAG,OAAO;CAChE;CACA,OAAO;AACT;AAEA,SAAS,eAAe,OAAyB;CAC/C,IAAI,aAAa,KAAK,GAAG,OAAO;CAChC,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,WAAW,OAC5D,OAAO,aAAa,MAAM,KAAK;CAEjC,OAAO;AACT;AAEA,SAAS,aAAa,OAAyB;CAC7C,OACE,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,UACT,MAAM,SAAS,kBAAkB,MAAM,SAAS;AAErD"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { defineActions } from "../../core/dist/actions/registry.js";
|
|
3
3
|
import "../../core/dist/index.js";
|
|
4
|
+
import { loggableErrorName } from "../../utils/loggable-error-name.js";
|
|
4
5
|
import { SESSION_LOGIN_WITH_PASSWORD, SESSION_LOGOUT, SESSION_REQUEST_ACCESS_CODE, SESSION_VALIDATE_ACCESS_CODE } from "./auth-operations.js";
|
|
5
6
|
import { useMutation } from "@apollo/client/react";
|
|
6
7
|
import { useCallback, useMemo } from "react";
|
|
@@ -24,7 +25,7 @@ function useSessionActions() {
|
|
|
24
25
|
try {
|
|
25
26
|
return (await loginMutation({ variables: { input: payload } })).data?.sessionLoginWithPassword.userErrors ?? [];
|
|
26
27
|
} catch (error) {
|
|
27
|
-
console.error("[session.loginWithPassword] sessionLoginWithPassword failed", error);
|
|
28
|
+
console.error("[session.loginWithPassword] sessionLoginWithPassword failed", loggableErrorName(error));
|
|
28
29
|
return [NETWORK_USER_ERROR];
|
|
29
30
|
}
|
|
30
31
|
}, [loginMutation]);
|
|
@@ -36,7 +37,7 @@ function useSessionActions() {
|
|
|
36
37
|
userErrors: data?.userErrors ?? []
|
|
37
38
|
};
|
|
38
39
|
} catch (error) {
|
|
39
|
-
console.error("[session.requestAccessCode] sessionRequestAccessCode failed", error);
|
|
40
|
+
console.error("[session.requestAccessCode] sessionRequestAccessCode failed", loggableErrorName(error));
|
|
40
41
|
return {
|
|
41
42
|
authenticationToken: null,
|
|
42
43
|
userErrors: [NETWORK_USER_ERROR]
|
|
@@ -47,7 +48,7 @@ function useSessionActions() {
|
|
|
47
48
|
try {
|
|
48
49
|
return (await validateMutation({ variables: { input: payload } })).data?.sessionValidateAccessCode.userErrors ?? [];
|
|
49
50
|
} catch (error) {
|
|
50
|
-
console.error("[session.validateAccessCode] sessionValidateAccessCode failed", error);
|
|
51
|
+
console.error("[session.validateAccessCode] sessionValidateAccessCode failed", loggableErrorName(error));
|
|
51
52
|
return [NETWORK_USER_ERROR];
|
|
52
53
|
}
|
|
53
54
|
}, [validateMutation]);
|
|
@@ -55,7 +56,7 @@ function useSessionActions() {
|
|
|
55
56
|
try {
|
|
56
57
|
return (await logoutMutation()).data?.sessionLogout.userErrors ?? [];
|
|
57
58
|
} catch (error) {
|
|
58
|
-
console.error("[session.logout] sessionLogout failed", error);
|
|
59
|
+
console.error("[session.logout] sessionLogout failed", loggableErrorName(error));
|
|
59
60
|
return [NETWORK_USER_ERROR];
|
|
60
61
|
}
|
|
61
62
|
}, [logoutMutation]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-session-actions.js","names":[],"sources":["../../../src/sdk/session/use-session-actions.ts"],"sourcesContent":["'use client';\n\nimport { useMutation } from '@apollo/client/react';\nimport { defineActions } from '@vtex/fs-core';\nimport { useCallback, useMemo } from 'react';\nimport {\n SESSION_LOGIN_WITH_PASSWORD,\n SESSION_LOGOUT,\n SESSION_REQUEST_ACCESS_CODE,\n SESSION_VALIDATE_ACCESS_CODE,\n type SessionAccessCodeRequestResult,\n type SessionPasswordLoginPayload,\n type SessionRequestAccessCodePayload,\n type SessionUserError,\n type SessionValidateAccessCodePayload,\n} from './auth-operations.js';\n\n/**\n * The `session` action namespace — the one sanctioned client write surface for authentication\n * (docs/storefront-core/reference-free-particle-model.md § Write side — actions, docs/storefront-core/adr-0010-actions-own-the-write-side.md).\n *\n * Every action is async/pessimistic (Decision 1): the handler awaits the mutation and only the\n * server-confirmed payload changes the cache — authentication cannot be guessed client-side. The\n * handlers are stateless: no reactive vars, no module state; the access-code `authenticationToken`\n * is returned to the caller, which owns it as UI flow state (Decision 4). Each action resolves its\n * payload `userErrors` (empty on success) and never throws for expected outcomes; infrastructure\n * failures resolve as the synthetic `NETWORK_ERROR` (Decision 5). The credentials pass through\n * mutation variables only — never logged, never stored.\n */\nexport type SessionActions = Readonly<{\n /** Password sign-in. Async/pessimistic; resolves the payload userErrors (empty on success). */\n loginWithPassword: (payload: SessionPasswordLoginPayload) => Promise<readonly SessionUserError[]>;\n /** Email an access code; resolves the opaque authenticationToken to round-trip into validate. */\n requestAccessCode: (payload: SessionRequestAccessCodePayload) => Promise<SessionAccessCodeRequestResult>;\n /** Validate an access code + token; logs the shopper in exactly as a password login would. */\n validateAccessCode: (payload: SessionValidateAccessCodePayload) => Promise<readonly SessionUserError[]>;\n /** Sign out. Idempotent for guests. */\n logout: () => Promise<readonly SessionUserError[]>;\n}>;\n\nconst NETWORK_USER_ERROR: SessionUserError = {\n code: 'NETWORK_ERROR',\n message: 'Unable to complete the request. Please try again.',\n};\n\n/**\n * The `session` namespace hook. Registered with the first-party action registry via `sessionActions`\n * below; the compiler-emitted `SessionActionProvider` calls it and threads the narrowed methods into\n * each consumer's `$actions.session`. Login/validate/logout refresh the session purely through the\n * normalized-cache merge of the mutation response (Decision 2) — no refetch, no imperative write.\n */\nexport function useSessionActions(): SessionActions {\n const [loginMutation] = useMutation(SESSION_LOGIN_WITH_PASSWORD);\n const [requestMutation] = useMutation(SESSION_REQUEST_ACCESS_CODE);\n const [validateMutation] = useMutation(SESSION_VALIDATE_ACCESS_CODE);\n const [logoutMutation] = useMutation(SESSION_LOGOUT);\n\n const loginWithPassword = useCallback<SessionActions['loginWithPassword']>(\n async (payload) => {\n try {\n const result = await loginMutation({ variables: { input: payload } });\n return result.data?.sessionLoginWithPassword.userErrors ?? [];\n } catch (error) {\n console.error('[session.loginWithPassword] sessionLoginWithPassword failed', error);\n return [NETWORK_USER_ERROR];\n }\n },\n [loginMutation],\n );\n\n const requestAccessCode = useCallback<SessionActions['requestAccessCode']>(\n async (payload) => {\n try {\n const result = await requestMutation({ variables: { input: payload } });\n const data = result.data?.sessionRequestAccessCode;\n return { authenticationToken: data?.authenticationToken ?? null, userErrors: data?.userErrors ?? [] };\n } catch (error) {\n console.error('[session.requestAccessCode] sessionRequestAccessCode failed', error);\n return { authenticationToken: null, userErrors: [NETWORK_USER_ERROR] };\n }\n },\n [requestMutation],\n );\n\n const validateAccessCode = useCallback<SessionActions['validateAccessCode']>(\n async (payload) => {\n try {\n const result = await validateMutation({ variables: { input: payload } });\n return result.data?.sessionValidateAccessCode.userErrors ?? [];\n } catch (error) {\n console.error('[session.validateAccessCode] sessionValidateAccessCode failed', error);\n return [NETWORK_USER_ERROR];\n }\n },\n [validateMutation],\n );\n\n const logout = useCallback<SessionActions['logout']>(async () => {\n try {\n const result = await logoutMutation();\n return result.data?.sessionLogout.userErrors ?? [];\n } catch (error) {\n console.error('[session.logout] sessionLogout failed', error);\n return [NETWORK_USER_ERROR];\n }\n }, [logoutMutation]);\n\n return useMemo(\n () => ({ loginWithPassword, requestAccessCode, validateAccessCode, logout }),\n [loginWithPassword, requestAccessCode, validateAccessCode, logout],\n );\n}\n\n/**\n * Declares the `session` action namespace — the single source of truth for its method/payload\n * metadata, runtime provider, and `use` hook (mirrors `cartActions`). The native catalog builder\n * (`scripts/build-catalog.mjs`) statically reads this call to emit `actions.json`; at runtime\n * `SessionActionProvider` calls `useSessionActions` directly.\n */\nexport const sessionActions = defineActions({\n namespace: 'session',\n description: 'Mutation-backed authentication writes against the VTEX ID session (login, access code, logout).',\n provider: { name: 'SessionActionProvider', importPath: '@vtex/faststore/internal/client' },\n methods: {\n loginWithPassword: {\n description: 'Sign in with email and password (async/pessimistic). Resolves the payload userErrors.',\n payload: [\n { name: 'email', type: 'string' },\n { name: 'password', type: 'string' },\n ],\n },\n requestAccessCode: {\n description: 'Email the shopper an access code; resolves the opaque authenticationToken to round-trip.',\n payload: [{ name: 'email', type: 'string' }],\n },\n validateAccessCode: {\n description: 'Validate an access code with its authenticationToken (async/pessimistic); logs the shopper in.',\n payload: [\n { name: 'email', type: 'string' },\n { name: 'accessCode', type: 'string' },\n { name: 'authenticationToken', type: 'string' },\n ],\n },\n logout: {\n description: 'Sign out, clearing the auth cookies (async/pessimistic). Idempotent for guests.',\n payload: [],\n },\n },\n use: useSessionActions,\n});\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"use-session-actions.js","names":[],"sources":["../../../src/sdk/session/use-session-actions.ts"],"sourcesContent":["'use client';\n\nimport { useMutation } from '@apollo/client/react';\nimport { defineActions } from '@vtex/fs-core';\nimport { useCallback, useMemo } from 'react';\nimport { loggableErrorName } from '../../utils/loggable-error-name.js';\nimport {\n SESSION_LOGIN_WITH_PASSWORD,\n SESSION_LOGOUT,\n SESSION_REQUEST_ACCESS_CODE,\n SESSION_VALIDATE_ACCESS_CODE,\n type SessionAccessCodeRequestResult,\n type SessionPasswordLoginPayload,\n type SessionRequestAccessCodePayload,\n type SessionUserError,\n type SessionValidateAccessCodePayload,\n} from './auth-operations.js';\n\n/**\n * The `session` action namespace — the one sanctioned client write surface for authentication\n * (docs/storefront-core/reference-free-particle-model.md § Write side — actions, docs/storefront-core/adr-0010-actions-own-the-write-side.md).\n *\n * Every action is async/pessimistic (Decision 1): the handler awaits the mutation and only the\n * server-confirmed payload changes the cache — authentication cannot be guessed client-side. The\n * handlers are stateless: no reactive vars, no module state; the access-code `authenticationToken`\n * is returned to the caller, which owns it as UI flow state (Decision 4). Each action resolves its\n * payload `userErrors` (empty on success) and never throws for expected outcomes; infrastructure\n * failures resolve as the synthetic `NETWORK_ERROR` (Decision 5). The credentials pass through\n * mutation variables only — never logged, never stored.\n */\nexport type SessionActions = Readonly<{\n /** Password sign-in. Async/pessimistic; resolves the payload userErrors (empty on success). */\n loginWithPassword: (payload: SessionPasswordLoginPayload) => Promise<readonly SessionUserError[]>;\n /** Email an access code; resolves the opaque authenticationToken to round-trip into validate. */\n requestAccessCode: (payload: SessionRequestAccessCodePayload) => Promise<SessionAccessCodeRequestResult>;\n /** Validate an access code + token; logs the shopper in exactly as a password login would. */\n validateAccessCode: (payload: SessionValidateAccessCodePayload) => Promise<readonly SessionUserError[]>;\n /** Sign out. Idempotent for guests. */\n logout: () => Promise<readonly SessionUserError[]>;\n}>;\n\nconst NETWORK_USER_ERROR: SessionUserError = {\n code: 'NETWORK_ERROR',\n message: 'Unable to complete the request. Please try again.',\n};\n\n/**\n * The `session` namespace hook. Registered with the first-party action registry via `sessionActions`\n * below; the compiler-emitted `SessionActionProvider` calls it and threads the narrowed methods into\n * each consumer's `$actions.session`. Login/validate/logout refresh the session purely through the\n * normalized-cache merge of the mutation response (Decision 2) — no refetch, no imperative write.\n */\nexport function useSessionActions(): SessionActions {\n const [loginMutation] = useMutation(SESSION_LOGIN_WITH_PASSWORD);\n const [requestMutation] = useMutation(SESSION_REQUEST_ACCESS_CODE);\n const [validateMutation] = useMutation(SESSION_VALIDATE_ACCESS_CODE);\n const [logoutMutation] = useMutation(SESSION_LOGOUT);\n\n const loginWithPassword = useCallback<SessionActions['loginWithPassword']>(\n async (payload) => {\n try {\n const result = await loginMutation({ variables: { input: payload } });\n return result.data?.sessionLoginWithPassword.userErrors ?? [];\n } catch (error) {\n console.error('[session.loginWithPassword] sessionLoginWithPassword failed', loggableErrorName(error));\n return [NETWORK_USER_ERROR];\n }\n },\n [loginMutation],\n );\n\n const requestAccessCode = useCallback<SessionActions['requestAccessCode']>(\n async (payload) => {\n try {\n const result = await requestMutation({ variables: { input: payload } });\n const data = result.data?.sessionRequestAccessCode;\n return { authenticationToken: data?.authenticationToken ?? null, userErrors: data?.userErrors ?? [] };\n } catch (error) {\n console.error('[session.requestAccessCode] sessionRequestAccessCode failed', loggableErrorName(error));\n return { authenticationToken: null, userErrors: [NETWORK_USER_ERROR] };\n }\n },\n [requestMutation],\n );\n\n const validateAccessCode = useCallback<SessionActions['validateAccessCode']>(\n async (payload) => {\n try {\n const result = await validateMutation({ variables: { input: payload } });\n return result.data?.sessionValidateAccessCode.userErrors ?? [];\n } catch (error) {\n console.error('[session.validateAccessCode] sessionValidateAccessCode failed', loggableErrorName(error));\n return [NETWORK_USER_ERROR];\n }\n },\n [validateMutation],\n );\n\n const logout = useCallback<SessionActions['logout']>(async () => {\n try {\n const result = await logoutMutation();\n return result.data?.sessionLogout.userErrors ?? [];\n } catch (error) {\n console.error('[session.logout] sessionLogout failed', loggableErrorName(error));\n return [NETWORK_USER_ERROR];\n }\n }, [logoutMutation]);\n\n return useMemo(\n () => ({ loginWithPassword, requestAccessCode, validateAccessCode, logout }),\n [loginWithPassword, requestAccessCode, validateAccessCode, logout],\n );\n}\n\n/**\n * Declares the `session` action namespace — the single source of truth for its method/payload\n * metadata, runtime provider, and `use` hook (mirrors `cartActions`). The native catalog builder\n * (`scripts/build-catalog.mjs`) statically reads this call to emit `actions.json`; at runtime\n * `SessionActionProvider` calls `useSessionActions` directly.\n */\nexport const sessionActions = defineActions({\n namespace: 'session',\n description: 'Mutation-backed authentication writes against the VTEX ID session (login, access code, logout).',\n provider: { name: 'SessionActionProvider', importPath: '@vtex/faststore/internal/client' },\n methods: {\n loginWithPassword: {\n description: 'Sign in with email and password (async/pessimistic). Resolves the payload userErrors.',\n payload: [\n { name: 'email', type: 'string' },\n { name: 'password', type: 'string' },\n ],\n },\n requestAccessCode: {\n description: 'Email the shopper an access code; resolves the opaque authenticationToken to round-trip.',\n payload: [{ name: 'email', type: 'string' }],\n },\n validateAccessCode: {\n description: 'Validate an access code with its authenticationToken (async/pessimistic); logs the shopper in.',\n payload: [\n { name: 'email', type: 'string' },\n { name: 'accessCode', type: 'string' },\n { name: 'authenticationToken', type: 'string' },\n ],\n },\n logout: {\n description: 'Sign out, clearing the auth cookies (async/pessimistic). Idempotent for guests.',\n payload: [],\n },\n },\n use: useSessionActions,\n});\n"],"mappings":";;;;;;;;AAyCA,MAAM,qBAAuC;CAC3C,MAAM;CACN,SAAS;AACX;;;;;;;AAQA,SAAgB,oBAAoC;CAClD,MAAM,CAAC,iBAAiB,YAAY,2BAA2B;CAC/D,MAAM,CAAC,mBAAmB,YAAY,2BAA2B;CACjE,MAAM,CAAC,oBAAoB,YAAY,4BAA4B;CACnE,MAAM,CAAC,kBAAkB,YAAY,cAAc;CAEnD,MAAM,oBAAoB,YACxB,OAAO,YAAY;EACjB,IAAI;GAEF,QAAO,MADc,cAAc,EAAE,WAAW,EAAE,OAAO,QAAQ,EAAE,CAAC,EAAA,CACtD,MAAM,yBAAyB,cAAc,CAAC;EAC9D,SAAS,OAAO;GACd,QAAQ,MAAM,+DAA+D,kBAAkB,KAAK,CAAC;GACrG,OAAO,CAAC,kBAAkB;EAC5B;CACF,GACA,CAAC,aAAa,CAChB;CAEA,MAAM,oBAAoB,YACxB,OAAO,YAAY;EACjB,IAAI;GAEF,MAAM,QAAO,MADQ,gBAAgB,EAAE,WAAW,EAAE,OAAO,QAAQ,EAAE,CAAC,EAAA,CAClD,MAAM;GAC1B,OAAO;IAAE,qBAAqB,MAAM,uBAAuB;IAAM,YAAY,MAAM,cAAc,CAAC;GAAE;EACtG,SAAS,OAAO;GACd,QAAQ,MAAM,+DAA+D,kBAAkB,KAAK,CAAC;GACrG,OAAO;IAAE,qBAAqB;IAAM,YAAY,CAAC,kBAAkB;GAAE;EACvE;CACF,GACA,CAAC,eAAe,CAClB;CAEA,MAAM,qBAAqB,YACzB,OAAO,YAAY;EACjB,IAAI;GAEF,QAAO,MADc,iBAAiB,EAAE,WAAW,EAAE,OAAO,QAAQ,EAAE,CAAC,EAAA,CACzD,MAAM,0BAA0B,cAAc,CAAC;EAC/D,SAAS,OAAO;GACd,QAAQ,MAAM,iEAAiE,kBAAkB,KAAK,CAAC;GACvG,OAAO,CAAC,kBAAkB;EAC5B;CACF,GACA,CAAC,gBAAgB,CACnB;CAEA,MAAM,SAAS,YAAsC,YAAY;EAC/D,IAAI;GAEF,QAAO,MADc,eAAe,EAAA,CACtB,MAAM,cAAc,cAAc,CAAC;EACnD,SAAS,OAAO;GACd,QAAQ,MAAM,yCAAyC,kBAAkB,KAAK,CAAC;GAC/E,OAAO,CAAC,kBAAkB;EAC5B;CACF,GAAG,CAAC,cAAc,CAAC;CAEnB,OAAO,eACE;EAAE;EAAmB;EAAmB;EAAoB;CAAO,IAC1E;EAAC;EAAmB;EAAmB;EAAoB;CAAM,CACnE;AACF;AAQ8B,cAAc;CAC1C,WAAW;CACX,aAAa;CACb,UAAU;EAAE,MAAM;EAAyB,YAAY;CAAkC;CACzF,SAAS;EACP,mBAAmB;GACjB,aAAa;GACb,SAAS,CACP;IAAE,MAAM;IAAS,MAAM;GAAS,GAChC;IAAE,MAAM;IAAY,MAAM;GAAS,CACrC;EACF;EACA,mBAAmB;GACjB,aAAa;GACb,SAAS,CAAC;IAAE,MAAM;IAAS,MAAM;GAAS,CAAC;EAC7C;EACA,oBAAoB;GAClB,aAAa;GACb,SAAS;IACP;KAAE,MAAM;KAAS,MAAM;IAAS;IAChC;KAAE,MAAM;KAAc,MAAM;IAAS;IACrC;KAAE,MAAM;KAAuB,MAAM;IAAS;GAChD;EACF;EACA,QAAQ;GACN,aAAa;GACb,SAAS,CAAC;EACZ;CACF;CACA,KAAK;AACP,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { graphql } from "../generated/gql.js";
|
|
3
|
+
import { loggableErrorName } from "../../utils/loggable-error-name.js";
|
|
3
4
|
import { getApolloContext } from "@apollo/client/react";
|
|
4
5
|
import { useCallback, useContext } from "react";
|
|
5
6
|
//#region src/sdk/session/use-session-revalidation.ts
|
|
@@ -54,7 +55,7 @@ function useSessionRevalidation() {
|
|
|
54
55
|
});
|
|
55
56
|
return "revalidated";
|
|
56
57
|
} catch (error) {
|
|
57
|
-
console.error("[useSessionRevalidation] session re-read failed", error);
|
|
58
|
+
console.error("[useSessionRevalidation] session re-read failed", loggableErrorName(error));
|
|
58
59
|
return "failed";
|
|
59
60
|
}
|
|
60
61
|
}, [client]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-session-revalidation.js","names":[],"sources":["../../../src/sdk/session/use-session-revalidation.ts"],"sourcesContent":["'use client';\n\nimport { getApolloContext } from '@apollo/client/react';\nimport { useCallback, useContext } from 'react';\nimport { graphql } from '../generated/gql.js';\n\n/**\n * The imperative re-read of the session, for the one case the reactive path cannot serve: a document\n * the browser restored from its back-forward cache. A restored document resumes its heap without\n * remounting, so `PageReactiveDataRoot` never re-runs its `cache-first` query and\n * `useReactiveEntity` never issues a request — the session comes back exactly as stale as the frame\n * around it.\n *\n * Selects the same field set as `SessionFragment`, so the response writes over the entity the layout\n * query already references and every `SessionRoot` below it reacts.\n */\nexport const ACCOUNT_SESSION_REVALIDATE = graphql(`\n query AccountSessionRevalidate {\n session {\n ...SessionFragment\n }\n }\n`);\n\n/**\n * What a revalidation is able to say about the session afterwards.\n *\n * `unavailable` is not a failure. With no Apollo client there is no cache to hold a stale session and\n * nothing to re-read: the session in the tree came from a `$data` prop, and a prop cannot go stale\n * behind the caller's back. A caller that treats it as a failure is distrusting an answer that was\n * never checkable — which is every story and test composed without a provider.\n */\nexport type SessionRevalidation = 'revalidated' | 'failed' | 'unavailable';\n\n/**\n * Discards the cached session and re-reads it from the network, resolving what it was able to find\n * out. Only `failed` means the caller is holding an answer it has reason to distrust: the session was\n * discarded and the query that should have replaced it rejected.\n *\n * **The discard comes first, and that ordering is the contract.** For the whole of the re-read there\n * must be no session in the cache for anything to read: a re-read that merely overwrote would leave\n * the restored `loggedIn` answering every consumer in the tree until the network replied. What has to\n * go is the normalized entity, not the root field that points at it — the reactive read subscribes to\n * `Session:<id>` through `useFragment`, so a root field cleared from under a live entity leaves that\n * read complete and the consumer resolved.\n *\n * The consumers follow asynchronously, once the watched query has re-read: a component that must\n * withhold something in the same frame the discard happens has to hold that state itself, and\n * `AccountAccessGate` does.\n *\n * The client is resolved through Apollo's context rather than `useApolloClient()`, which throws when\n * there is none: every session-aware story and test in this package renders `SessionRoot` bare, and a\n * revalidation that cannot run is not a reason for a component to fail to render.\n */\nexport function useSessionRevalidation(): () => Promise<SessionRevalidation> {\n const { client } = useContext(getApolloContext());\n\n return useCallback(async (): Promise<SessionRevalidation> => {\n if (!client) {\n return 'unavailable';\n }\n\n try {\n // The document about to be re-read is also what names the entity to discard: it selects the\n // same field, so it resolves whichever Session the cache is currently holding. An incomplete\n // read means there is nothing cached to discard, and the re-read below is all that is needed.\n const cached = client.cache.readQuery({ query: ACCOUNT_SESSION_REVALIDATE });\n const entity = cached?.session ? client.cache.identify(cached.session) : undefined;\n if (entity) {\n client.cache.evict({ id: entity });\n }\n\n await client.query({ query: ACCOUNT_SESSION_REVALIDATE, fetchPolicy: 'network-only' });\n return 'revalidated';\n } catch (error) {\n // The discard is inside this guard along with the re-read, so a cache that refused either one\n // resolves rather than rejecting. A caller left awaiting a promise that never settles would\n // hold its shopper in the wait forever, which is the one outcome worse than saying no.\n console.error('[useSessionRevalidation] session re-read failed', error);\n return 'failed';\n }\n }, [client]);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"use-session-revalidation.js","names":[],"sources":["../../../src/sdk/session/use-session-revalidation.ts"],"sourcesContent":["'use client';\n\nimport { getApolloContext } from '@apollo/client/react';\nimport { useCallback, useContext } from 'react';\nimport { loggableErrorName } from '../../utils/loggable-error-name.js';\nimport { graphql } from '../generated/gql.js';\n\n/**\n * The imperative re-read of the session, for the one case the reactive path cannot serve: a document\n * the browser restored from its back-forward cache. A restored document resumes its heap without\n * remounting, so `PageReactiveDataRoot` never re-runs its `cache-first` query and\n * `useReactiveEntity` never issues a request — the session comes back exactly as stale as the frame\n * around it.\n *\n * Selects the same field set as `SessionFragment`, so the response writes over the entity the layout\n * query already references and every `SessionRoot` below it reacts.\n */\nexport const ACCOUNT_SESSION_REVALIDATE = graphql(`\n query AccountSessionRevalidate {\n session {\n ...SessionFragment\n }\n }\n`);\n\n/**\n * What a revalidation is able to say about the session afterwards.\n *\n * `unavailable` is not a failure. With no Apollo client there is no cache to hold a stale session and\n * nothing to re-read: the session in the tree came from a `$data` prop, and a prop cannot go stale\n * behind the caller's back. A caller that treats it as a failure is distrusting an answer that was\n * never checkable — which is every story and test composed without a provider.\n */\nexport type SessionRevalidation = 'revalidated' | 'failed' | 'unavailable';\n\n/**\n * Discards the cached session and re-reads it from the network, resolving what it was able to find\n * out. Only `failed` means the caller is holding an answer it has reason to distrust: the session was\n * discarded and the query that should have replaced it rejected.\n *\n * **The discard comes first, and that ordering is the contract.** For the whole of the re-read there\n * must be no session in the cache for anything to read: a re-read that merely overwrote would leave\n * the restored `loggedIn` answering every consumer in the tree until the network replied. What has to\n * go is the normalized entity, not the root field that points at it — the reactive read subscribes to\n * `Session:<id>` through `useFragment`, so a root field cleared from under a live entity leaves that\n * read complete and the consumer resolved.\n *\n * The consumers follow asynchronously, once the watched query has re-read: a component that must\n * withhold something in the same frame the discard happens has to hold that state itself, and\n * `AccountAccessGate` does.\n *\n * The client is resolved through Apollo's context rather than `useApolloClient()`, which throws when\n * there is none: every session-aware story and test in this package renders `SessionRoot` bare, and a\n * revalidation that cannot run is not a reason for a component to fail to render.\n */\nexport function useSessionRevalidation(): () => Promise<SessionRevalidation> {\n const { client } = useContext(getApolloContext());\n\n return useCallback(async (): Promise<SessionRevalidation> => {\n if (!client) {\n return 'unavailable';\n }\n\n try {\n // The document about to be re-read is also what names the entity to discard: it selects the\n // same field, so it resolves whichever Session the cache is currently holding. An incomplete\n // read means there is nothing cached to discard, and the re-read below is all that is needed.\n const cached = client.cache.readQuery({ query: ACCOUNT_SESSION_REVALIDATE });\n const entity = cached?.session ? client.cache.identify(cached.session) : undefined;\n if (entity) {\n client.cache.evict({ id: entity });\n }\n\n await client.query({ query: ACCOUNT_SESSION_REVALIDATE, fetchPolicy: 'network-only' });\n return 'revalidated';\n } catch (error) {\n // The discard is inside this guard along with the re-read, so a cache that refused either one\n // resolves rather than rejecting. A caller left awaiting a promise that never settles would\n // hold its shopper in the wait forever, which is the one outcome worse than saying no.\n console.error('[useSessionRevalidation] session re-read failed', loggableErrorName(error));\n return 'failed';\n }\n }, [client]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiBA,MAAa,6BAA6B,QAAQ;;;;;;CAMjD;;;;;;;;;;;;;;;;;;;;;AAgCD,SAAgB,yBAA6D;CAC3E,MAAM,EAAE,WAAW,WAAW,iBAAiB,CAAC;CAEhD,OAAO,YAAY,YAA0C;EAC3D,IAAI,CAAC,QACH,OAAO;EAGT,IAAI;GAIF,MAAM,SAAS,OAAO,MAAM,UAAU,EAAE,OAAO,2BAA2B,CAAC;GAC3E,MAAM,SAAS,QAAQ,UAAU,OAAO,MAAM,SAAS,OAAO,OAAO,IAAI,KAAA;GACzE,IAAI,QACF,OAAO,MAAM,MAAM,EAAE,IAAI,OAAO,CAAC;GAGnC,MAAM,OAAO,MAAM;IAAE,OAAO;IAA4B,aAAa;GAAe,CAAC;GACrF,OAAO;EACT,SAAS,OAAO;GAId,QAAQ,MAAM,mDAAmD,kBAAkB,KAAK,CAAC;GACzF,OAAO;EACT;CACF,GAAG,CAAC,MAAM,CAAC;AACb"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { defineActions } from "../../core/dist/actions/registry.js";
|
|
3
3
|
import "../../core/dist/index.js";
|
|
4
|
-
import {
|
|
4
|
+
import { loggableErrorName } from "../../utils/loggable-error-name.js";
|
|
5
5
|
import { SHIPPING_OPTION_SELECT } from "./option-select-operations.js";
|
|
6
|
+
import { SHIPPING_ADDRESS_UPDATE, SHIPPING_NETWORK_ERROR_CODE } from "./shipping-operations.js";
|
|
6
7
|
import { useMutation } from "@apollo/client/react";
|
|
7
8
|
import { useCallback, useMemo } from "react";
|
|
8
9
|
//#region src/sdk/shipping/use-shipping-actions.ts
|
|
@@ -27,7 +28,7 @@ function useShippingActions() {
|
|
|
27
28
|
try {
|
|
28
29
|
return (await addressMutation({ variables: { input: address } })).data?.shippingAddressUpdate.userErrors ?? [];
|
|
29
30
|
} catch (error) {
|
|
30
|
-
console.error("[shipping.setAddress] shippingAddressUpdate failed", error);
|
|
31
|
+
console.error("[shipping.setAddress] shippingAddressUpdate failed", loggableErrorName(error));
|
|
31
32
|
return [SET_ADDRESS_NETWORK_ERROR];
|
|
32
33
|
}
|
|
33
34
|
}, [addressMutation]);
|
|
@@ -35,7 +36,7 @@ function useShippingActions() {
|
|
|
35
36
|
try {
|
|
36
37
|
return (await selectOptionMutation({ variables: { input: { optionId } } })).data?.shippingOptionSelect.userErrors ?? [];
|
|
37
38
|
} catch (error) {
|
|
38
|
-
console.error("[shipping.selectOption] shippingOptionSelect failed", error);
|
|
39
|
+
console.error("[shipping.selectOption] shippingOptionSelect failed", loggableErrorName(error));
|
|
39
40
|
return [SELECT_OPTION_NETWORK_ERROR];
|
|
40
41
|
}
|
|
41
42
|
}, [selectOptionMutation]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-shipping-actions.js","names":[],"sources":["../../../src/sdk/shipping/use-shipping-actions.ts"],"sourcesContent":["'use client';\n\nimport { useMutation } from '@apollo/client/react';\nimport { defineActions } from '@vtex/fs-core';\nimport { useCallback, useMemo } from 'react';\nimport { SHIPPING_OPTION_SELECT, type ShippingOptionSelectUserError } from './option-select-operations.js';\nimport {\n SHIPPING_ADDRESS_UPDATE,\n SHIPPING_NETWORK_ERROR_CODE,\n type ShippingAddressInput,\n type ShippingAddressUpdateErrorEntity,\n} from './shipping-operations.js';\n\n/**\n * The `shipping` action namespace — the sanctioned client write surface against the VTEX orderForm\n * shippingData: delivery-address updates (docs/storefront-core/reference-free-particle-model.md § The cart write contract;\n * docs/storefront-core/adr-0010-actions-own-the-write-side.md) and cart-scoped delivery-option\n * selection (docs/storefront-core/reference-free-particle-model.md § The cart write contract). Per Decision 4 the two tasks share this one\n * namespace — T2 (`setAddress`) merged first and owns the registration, T3 extends it with\n * `selectOption` here.\n *\n * Both actions are async/pessimistic (Decision 1 of the address spec): the handler awaits the\n * mutation and only the server-confirmed payload changes the cache. Each is stateless (no reactive\n * vars, no module state), resolves its payload `userErrors` (empty on success), and never throws for\n * expected outcomes; an infrastructure failure (network or an upstream GraphQLError) is caught and\n * collapsed to one synthetic network `userError` — no per-code branching (US-4, FR-5). Inputs pass\n * through mutation variables only — never logged, never stored beyond the cache.\n */\nexport type ShippingActions = Readonly<{\n /** Persist the single delivery address (async/pessimistic). Resolves the payload userErrors. */\n setAddress: (address: ShippingAddressInput) => Promise<readonly ShippingAddressUpdateErrorEntity[]>;\n /** Select a delivery option for the whole cart. Resolves the payload userErrors (empty on success). */\n selectOption: (optionId: string) => Promise<readonly ShippingOptionSelectUserError[]>;\n}>;\n\nconst SET_ADDRESS_NETWORK_ERROR: ShippingAddressUpdateErrorEntity = {\n code: SHIPPING_NETWORK_ERROR_CODE,\n message: 'Unable to complete the request. Please try again.',\n};\n\nconst SELECT_OPTION_NETWORK_ERROR: ShippingOptionSelectUserError = {\n code: 'NETWORK_ERROR',\n message: 'Unable to update the delivery option. Please try again.',\n};\n\n/**\n * The `shipping` namespace hook. Registered with the first-party action registry via `shippingActions`\n * below; the compiler-emitted `ShippingActionProvider` calls it and threads the narrowed methods into\n * each consumer's `$actions.shipping`. Writes persist purely through the normalized-cache merge of\n * each mutation response (Decision 2 of the address spec) — no refetch, no imperative write.\n */\nexport function useShippingActions(): ShippingActions {\n const [addressMutation] = useMutation(SHIPPING_ADDRESS_UPDATE);\n const [selectOptionMutation] = useMutation(SHIPPING_OPTION_SELECT);\n\n const setAddress = useCallback<ShippingActions['setAddress']>(\n async (address) => {\n try {\n const result = await addressMutation({ variables: { input: address } });\n return result.data?.shippingAddressUpdate.userErrors ?? [];\n } catch (error) {\n console.error('[shipping.setAddress] shippingAddressUpdate failed', error);\n return [SET_ADDRESS_NETWORK_ERROR];\n }\n },\n [addressMutation],\n );\n\n const selectOption = useCallback<ShippingActions['selectOption']>(\n async (optionId) => {\n try {\n const result = await selectOptionMutation({ variables: { input: { optionId } } });\n return result.data?.shippingOptionSelect.userErrors ?? [];\n } catch (error) {\n console.error('[shipping.selectOption] shippingOptionSelect failed', error);\n return [SELECT_OPTION_NETWORK_ERROR];\n }\n },\n [selectOptionMutation],\n );\n\n return useMemo(() => ({ setAddress, selectOption }), [setAddress, selectOption]);\n}\n\n/**\n * Declares the `shipping` action namespace — the single source of truth for its method/payload\n * metadata, runtime provider, and `use` hook (mirrors `cartActions`, `sessionActions`). The native\n * catalog builder (`scripts/build-catalog.mjs`) statically reads this call to emit `actions.json`;\n * at runtime `ShippingActionProvider` calls `useShippingActions` directly.\n */\nexport const shippingActions = defineActions({\n namespace: 'shipping',\n description: 'Mutation-backed delivery writes against the VTEX orderForm shippingData (address in T2; option in T3).',\n provider: { name: 'ShippingActionProvider', importPath: '@vtex/faststore/internal/client' },\n methods: {\n setAddress: {\n description:\n 'Persist the single delivery address in one submit (async/pessimistic). Resolves the payload userErrors.',\n payload: [\n { name: 'savedAddressId', type: 'string' },\n { name: 'recipientName', type: 'string' },\n { name: 'postalCode', type: 'string' },\n { name: 'country', type: 'string' },\n { name: 'street', type: 'string' },\n { name: 'number', type: 'string' },\n { name: 'complement', type: 'string' },\n { name: 'neighborhood', type: 'string' },\n { name: 'city', type: 'string' },\n { name: 'state', type: 'string' },\n ],\n },\n selectOption: {\n description: 'Select a delivery option for the whole cart (async/pessimistic). Resolves the payload userErrors.',\n payload: [{ name: 'optionId', type: 'string' }],\n },\n },\n use: useShippingActions,\n});\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"use-shipping-actions.js","names":[],"sources":["../../../src/sdk/shipping/use-shipping-actions.ts"],"sourcesContent":["'use client';\n\nimport { useMutation } from '@apollo/client/react';\nimport { defineActions } from '@vtex/fs-core';\nimport { useCallback, useMemo } from 'react';\nimport { loggableErrorName } from '../../utils/loggable-error-name.js';\nimport { SHIPPING_OPTION_SELECT, type ShippingOptionSelectUserError } from './option-select-operations.js';\nimport {\n SHIPPING_ADDRESS_UPDATE,\n SHIPPING_NETWORK_ERROR_CODE,\n type ShippingAddressInput,\n type ShippingAddressUpdateErrorEntity,\n} from './shipping-operations.js';\n\n/**\n * The `shipping` action namespace — the sanctioned client write surface against the VTEX orderForm\n * shippingData: delivery-address updates (docs/storefront-core/reference-free-particle-model.md § The cart write contract;\n * docs/storefront-core/adr-0010-actions-own-the-write-side.md) and cart-scoped delivery-option\n * selection (docs/storefront-core/reference-free-particle-model.md § The cart write contract). Per Decision 4 the two tasks share this one\n * namespace — T2 (`setAddress`) merged first and owns the registration, T3 extends it with\n * `selectOption` here.\n *\n * Both actions are async/pessimistic (Decision 1 of the address spec): the handler awaits the\n * mutation and only the server-confirmed payload changes the cache. Each is stateless (no reactive\n * vars, no module state), resolves its payload `userErrors` (empty on success), and never throws for\n * expected outcomes; an infrastructure failure (network or an upstream GraphQLError) is caught and\n * collapsed to one synthetic network `userError` — no per-code branching (US-4, FR-5). Inputs pass\n * through mutation variables only — never logged, never stored beyond the cache.\n */\nexport type ShippingActions = Readonly<{\n /** Persist the single delivery address (async/pessimistic). Resolves the payload userErrors. */\n setAddress: (address: ShippingAddressInput) => Promise<readonly ShippingAddressUpdateErrorEntity[]>;\n /** Select a delivery option for the whole cart. Resolves the payload userErrors (empty on success). */\n selectOption: (optionId: string) => Promise<readonly ShippingOptionSelectUserError[]>;\n}>;\n\nconst SET_ADDRESS_NETWORK_ERROR: ShippingAddressUpdateErrorEntity = {\n code: SHIPPING_NETWORK_ERROR_CODE,\n message: 'Unable to complete the request. Please try again.',\n};\n\nconst SELECT_OPTION_NETWORK_ERROR: ShippingOptionSelectUserError = {\n code: 'NETWORK_ERROR',\n message: 'Unable to update the delivery option. Please try again.',\n};\n\n/**\n * The `shipping` namespace hook. Registered with the first-party action registry via `shippingActions`\n * below; the compiler-emitted `ShippingActionProvider` calls it and threads the narrowed methods into\n * each consumer's `$actions.shipping`. Writes persist purely through the normalized-cache merge of\n * each mutation response (Decision 2 of the address spec) — no refetch, no imperative write.\n */\nexport function useShippingActions(): ShippingActions {\n const [addressMutation] = useMutation(SHIPPING_ADDRESS_UPDATE);\n const [selectOptionMutation] = useMutation(SHIPPING_OPTION_SELECT);\n\n const setAddress = useCallback<ShippingActions['setAddress']>(\n async (address) => {\n try {\n const result = await addressMutation({ variables: { input: address } });\n return result.data?.shippingAddressUpdate.userErrors ?? [];\n } catch (error) {\n console.error('[shipping.setAddress] shippingAddressUpdate failed', loggableErrorName(error));\n return [SET_ADDRESS_NETWORK_ERROR];\n }\n },\n [addressMutation],\n );\n\n const selectOption = useCallback<ShippingActions['selectOption']>(\n async (optionId) => {\n try {\n const result = await selectOptionMutation({ variables: { input: { optionId } } });\n return result.data?.shippingOptionSelect.userErrors ?? [];\n } catch (error) {\n console.error('[shipping.selectOption] shippingOptionSelect failed', loggableErrorName(error));\n return [SELECT_OPTION_NETWORK_ERROR];\n }\n },\n [selectOptionMutation],\n );\n\n return useMemo(() => ({ setAddress, selectOption }), [setAddress, selectOption]);\n}\n\n/**\n * Declares the `shipping` action namespace — the single source of truth for its method/payload\n * metadata, runtime provider, and `use` hook (mirrors `cartActions`, `sessionActions`). The native\n * catalog builder (`scripts/build-catalog.mjs`) statically reads this call to emit `actions.json`;\n * at runtime `ShippingActionProvider` calls `useShippingActions` directly.\n */\nexport const shippingActions = defineActions({\n namespace: 'shipping',\n description: 'Mutation-backed delivery writes against the VTEX orderForm shippingData (address in T2; option in T3).',\n provider: { name: 'ShippingActionProvider', importPath: '@vtex/faststore/internal/client' },\n methods: {\n setAddress: {\n description:\n 'Persist the single delivery address in one submit (async/pessimistic). Resolves the payload userErrors.',\n payload: [\n { name: 'savedAddressId', type: 'string' },\n { name: 'recipientName', type: 'string' },\n { name: 'postalCode', type: 'string' },\n { name: 'country', type: 'string' },\n { name: 'street', type: 'string' },\n { name: 'number', type: 'string' },\n { name: 'complement', type: 'string' },\n { name: 'neighborhood', type: 'string' },\n { name: 'city', type: 'string' },\n { name: 'state', type: 'string' },\n ],\n },\n selectOption: {\n description: 'Select a delivery option for the whole cart (async/pessimistic). Resolves the payload userErrors.',\n payload: [{ name: 'optionId', type: 'string' }],\n },\n },\n use: useShippingActions,\n});\n"],"mappings":";;;;;;;;;AAoCA,MAAM,4BAA8D;CAClE,MAAM;CACN,SAAS;AACX;AAEA,MAAM,8BAA6D;CACjE,MAAM;CACN,SAAS;AACX;;;;;;;AAQA,SAAgB,qBAAsC;CACpD,MAAM,CAAC,mBAAmB,YAAY,uBAAuB;CAC7D,MAAM,CAAC,wBAAwB,YAAY,sBAAsB;CAEjE,MAAM,aAAa,YACjB,OAAO,YAAY;EACjB,IAAI;GAEF,QAAO,MADc,gBAAgB,EAAE,WAAW,EAAE,OAAO,QAAQ,EAAE,CAAC,EAAA,CACxD,MAAM,sBAAsB,cAAc,CAAC;EAC3D,SAAS,OAAO;GACd,QAAQ,MAAM,sDAAsD,kBAAkB,KAAK,CAAC;GAC5F,OAAO,CAAC,yBAAyB;EACnC;CACF,GACA,CAAC,eAAe,CAClB;CAEA,MAAM,eAAe,YACnB,OAAO,aAAa;EAClB,IAAI;GAEF,QAAO,MADc,qBAAqB,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,CAAC,EAAA,CAClE,MAAM,qBAAqB,cAAc,CAAC;EAC1D,SAAS,OAAO;GACd,QAAQ,MAAM,uDAAuD,kBAAkB,KAAK,CAAC;GAC7F,OAAO,CAAC,2BAA2B;EACrC;CACF,GACA,CAAC,oBAAoB,CACvB;CAEA,OAAO,eAAe;EAAE;EAAY;CAAa,IAAI,CAAC,YAAY,YAAY,CAAC;AACjF;AAQ+B,cAAc;CAC3C,WAAW;CACX,aAAa;CACb,UAAU;EAAE,MAAM;EAA0B,YAAY;CAAkC;CAC1F,SAAS;EACP,YAAY;GACV,aACE;GACF,SAAS;IACP;KAAE,MAAM;KAAkB,MAAM;IAAS;IACzC;KAAE,MAAM;KAAiB,MAAM;IAAS;IACxC;KAAE,MAAM;KAAc,MAAM;IAAS;IACrC;KAAE,MAAM;KAAW,MAAM;IAAS;IAClC;KAAE,MAAM;KAAU,MAAM;IAAS;IACjC;KAAE,MAAM;KAAU,MAAM;IAAS;IACjC;KAAE,MAAM;KAAc,MAAM;IAAS;IACrC;KAAE,MAAM;KAAgB,MAAM;IAAS;IACvC;KAAE,MAAM;KAAQ,MAAM;IAAS;IAC/B;KAAE,MAAM;KAAS,MAAM;IAAS;GAClC;EACF;EACA,cAAc;GACZ,aAAa;GACb,SAAS,CAAC;IAAE,MAAM;IAAY,MAAM;GAAS,CAAC;EAChD;CACF;CACA,KAAK;AACP,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//#region src/utils/loggable-error-name.ts
|
|
2
|
+
/**
|
|
3
|
+
* `Error.prototype.name` is a writable own property (security pass, T156 review; fixed here, T160):
|
|
4
|
+
* any collaborator a caller wraps in a `try`/`catch` — a holder's continuation, a pre-flight check,
|
|
5
|
+
* an action provider, a GraphQL mutation rejection — can throw an object whose `.name` is arbitrary
|
|
6
|
+
* text, including text built from card data or PII the caller exists to keep out of a log. An
|
|
7
|
+
* allow-list, not a pattern or a deny-list, mirrors this package's own rule for exactly this shape of
|
|
8
|
+
* problem (`matter/sensitive-field-protection.test.ts`'s own "an allow-list fails loudly ... a
|
|
9
|
+
* deny-list fails silently"). Lives in `utils/`, not in any one domain's `sdk/` subdirectory, because
|
|
10
|
+
* its callers already span two peer domains — `payment` and `identity` — neither of which should
|
|
11
|
+
* import a logging helper from the other (T160, Decision 6).
|
|
12
|
+
*/
|
|
13
|
+
const LOGGABLE_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
14
|
+
"Error",
|
|
15
|
+
"EvalError",
|
|
16
|
+
"RangeError",
|
|
17
|
+
"ReferenceError",
|
|
18
|
+
"SyntaxError",
|
|
19
|
+
"TypeError",
|
|
20
|
+
"URIError",
|
|
21
|
+
"AggregateError"
|
|
22
|
+
]);
|
|
23
|
+
/**
|
|
24
|
+
* The one place every caller below decides what to log for a caught error. Never the raw `.name`.
|
|
25
|
+
*
|
|
26
|
+
* `error` is an untrusted value: a holder's continuation, a pre-flight check, or any other
|
|
27
|
+
* pluggable collaborator can throw whatever object it wants, engineered so that merely inspecting
|
|
28
|
+
* it — not only reading `.name` — throws (fix rounds 1-3, all Medium findings). A `Proxy` can trap
|
|
29
|
+
* `getPrototypeOf` (triggered by the `instanceof` check below), `get` (triggered by the `.name`
|
|
30
|
+
* read), or make either one answer differently across repeated calls. Rather than guard each
|
|
31
|
+
* mechanism one at a time, every operation this function performs on `error` — the `instanceof`
|
|
32
|
+
* check, the `.name` read, and the allow-list membership check — runs inside one `try`/`catch`:
|
|
33
|
+
* whatever throws, and for whatever reason, collapses to `'UnknownError'` exactly like any other
|
|
34
|
+
* unrecognised name. `error.name` is still read exactly once, into `name` below, so a getter that
|
|
35
|
+
* answers differently across separate calls has no second read left to answer differently on.
|
|
36
|
+
*/
|
|
37
|
+
function loggableErrorName(error) {
|
|
38
|
+
try {
|
|
39
|
+
if (!(error instanceof Error)) return "UnknownError";
|
|
40
|
+
const name = error.name;
|
|
41
|
+
return LOGGABLE_ERROR_NAMES.has(name) ? name : "UnknownError";
|
|
42
|
+
} catch {
|
|
43
|
+
return "UnknownError";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { loggableErrorName };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=loggable-error-name.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loggable-error-name.js","names":[],"sources":["../../src/utils/loggable-error-name.ts"],"sourcesContent":["/**\n * `Error.prototype.name` is a writable own property (security pass, T156 review; fixed here, T160):\n * any collaborator a caller wraps in a `try`/`catch` — a holder's continuation, a pre-flight check,\n * an action provider, a GraphQL mutation rejection — can throw an object whose `.name` is arbitrary\n * text, including text built from card data or PII the caller exists to keep out of a log. An\n * allow-list, not a pattern or a deny-list, mirrors this package's own rule for exactly this shape of\n * problem (`matter/sensitive-field-protection.test.ts`'s own \"an allow-list fails loudly ... a\n * deny-list fails silently\"). Lives in `utils/`, not in any one domain's `sdk/` subdirectory, because\n * its callers already span two peer domains — `payment` and `identity` — neither of which should\n * import a logging helper from the other (T160, Decision 6).\n */\nconst LOGGABLE_ERROR_NAMES: ReadonlySet<string> = new Set([\n 'Error',\n 'EvalError',\n 'RangeError',\n 'ReferenceError',\n 'SyntaxError',\n 'TypeError',\n 'URIError',\n 'AggregateError',\n]);\n\n/**\n * The one place every caller below decides what to log for a caught error. Never the raw `.name`.\n *\n * `error` is an untrusted value: a holder's continuation, a pre-flight check, or any other\n * pluggable collaborator can throw whatever object it wants, engineered so that merely inspecting\n * it — not only reading `.name` — throws (fix rounds 1-3, all Medium findings). A `Proxy` can trap\n * `getPrototypeOf` (triggered by the `instanceof` check below), `get` (triggered by the `.name`\n * read), or make either one answer differently across repeated calls. Rather than guard each\n * mechanism one at a time, every operation this function performs on `error` — the `instanceof`\n * check, the `.name` read, and the allow-list membership check — runs inside one `try`/`catch`:\n * whatever throws, and for whatever reason, collapses to `'UnknownError'` exactly like any other\n * unrecognised name. `error.name` is still read exactly once, into `name` below, so a getter that\n * answers differently across separate calls has no second read left to answer differently on.\n */\nexport function loggableErrorName(error: unknown): string {\n try {\n if (!(error instanceof Error)) {\n return 'UnknownError';\n }\n\n const name = error.name;\n return LOGGABLE_ERROR_NAMES.has(name) ? name : 'UnknownError';\n } catch {\n return 'UnknownError';\n }\n}\n"],"mappings":";;;;;;;;;;;;AAWA,MAAM,uCAA4C,IAAI,IAAI;CACxD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;;;;;;;;;;;AAgBD,SAAgB,kBAAkB,OAAwB;CACxD,IAAI;EACF,IAAI,EAAE,iBAAiB,QACrB,OAAO;EAGT,MAAM,OAAO,MAAM;EACnB,OAAO,qBAAqB,IAAI,IAAI,IAAI,OAAO;CACjD,QAAQ;EACN,OAAO;CACT;AACF"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/utils/resolve-authored-copy.ts
|
|
2
|
+
/**
|
|
3
|
+
* Resolve an authored copy value against its default. A store author can write "" or " " into any
|
|
4
|
+
* `type: 'string'` schema prop — both are valid JSON strings — and neither `??` nor a default
|
|
5
|
+
* parameter falls through on them, so the surface renders a blank label, legend or alert. Logical OR
|
|
6
|
+
* is not a fix either: " " is truthy.
|
|
7
|
+
*
|
|
8
|
+
* The authored value is returned VERBATIM when it has content — trimmed only to decide, never to
|
|
9
|
+
* render, matching `cart.tsx`'s `configuredLabel` and `region.tsx`'s `$content` idiom.
|
|
10
|
+
*/
|
|
11
|
+
function resolveAuthoredCopy(authored, fallback) {
|
|
12
|
+
if (authored == null) return fallback;
|
|
13
|
+
return authored.trim().length > 0 ? authored : fallback;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { resolveAuthoredCopy };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=resolve-authored-copy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-authored-copy.js","names":[],"sources":["../../src/utils/resolve-authored-copy.ts"],"sourcesContent":["/**\n * Resolve an authored copy value against its default. A store author can write \"\" or \" \" into any\n * `type: 'string'` schema prop — both are valid JSON strings — and neither `??` nor a default\n * parameter falls through on them, so the surface renders a blank label, legend or alert. Logical OR\n * is not a fix either: \" \" is truthy.\n *\n * The authored value is returned VERBATIM when it has content — trimmed only to decide, never to\n * render, matching `cart.tsx`'s `configuredLabel` and `region.tsx`'s `$content` idiom.\n */\nexport function resolveAuthoredCopy(authored: string | undefined | null, fallback: string): string {\n if (authored == null) return fallback;\n return authored.trim().length > 0 ? authored : fallback;\n}\n"],"mappings":";;;;;;;;;;AASA,SAAgB,oBAAoB,UAAqC,UAA0B;CACjG,IAAI,YAAY,MAAM,OAAO;CAC7B,OAAO,SAAS,KAAK,CAAC,CAAC,SAAS,IAAI,WAAW;AACjD"}
|