@vtex/faststore 0.0.3 → 0.0.5
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 +41 -0
- package/README.md +7 -3
- package/dist/catalog.json +337 -1
- package/dist/components.d.ts +5 -1
- package/dist/components.js +8 -4
- package/dist/content-types.json +73 -0
- package/dist/docs/account-access-gate.md +153 -0
- package/dist/docs/account-entry-point.md +1 -2
- package/dist/docs/account-menu.md +164 -0
- package/dist/docs/buy-button.md +1 -1
- package/dist/docs/cart.md +1 -1
- package/dist/docs/my-account-profile.md +208 -0
- package/dist/docs/session.md +1 -1
- package/dist/docs/sign-in-redirect.md +1 -1
- package/dist/matter/account-access-gate/account-access-gate.d.ts +86 -0
- package/dist/matter/account-access-gate/account-access-gate.js +283 -0
- package/dist/matter/account-access-gate/account-access-gate.js.map +1 -0
- package/dist/matter/account-access-gate/sign-out-intent.d.ts +17 -0
- package/dist/matter/account-access-gate/sign-out-intent.js +62 -0
- package/dist/matter/account-access-gate/sign-out-intent.js.map +1 -0
- package/dist/matter/account-entry-point/account-entry-point.js +2 -2
- package/dist/matter/account-entry-point/account-entry-point.js.map +1 -1
- package/dist/matter/account-menu/account-menu.d.ts +88 -0
- package/dist/matter/account-menu/account-menu.js +246 -0
- package/dist/matter/account-menu/account-menu.js.map +1 -0
- package/dist/matter/cart/cart.d.ts +1 -1
- package/dist/matter/cart/cart.js +2 -2
- package/dist/matter/cart/cart.js.map +1 -1
- package/dist/matter/delivery-options/delivery-options.js.map +1 -1
- package/dist/matter/my-account-profile/my-account-profile.d.ts +291 -0
- package/dist/matter/my-account-profile/my-account-profile.js +856 -0
- package/dist/matter/my-account-profile/my-account-profile.js.map +1 -0
- package/dist/matter/shipping-address/shipping-address.js +2 -2
- package/dist/matter/shipping-address/shipping-address.js.map +1 -1
- package/dist/matter/shipping-simulator/shipping-simulator.js.map +1 -1
- package/dist/particles/footer-social/footer-social.js +1 -1
- package/dist/particles/incentives/incentives.js +1 -1
- package/dist/sdk/actions/cart-action-provider.d.ts +1 -1
- package/dist/sdk/actions/cart-action-provider.js +1 -1
- package/dist/sdk/actions/cart-action-provider.js.map +1 -1
- package/dist/sdk/actions/session-action-provider.d.ts +1 -1
- package/dist/sdk/actions/session-action-provider.js +1 -1
- package/dist/sdk/actions/session-action-provider.js.map +1 -1
- package/dist/sdk/actions/shipping-action-provider.d.ts +1 -1
- package/dist/sdk/actions/shipping-action-provider.js +1 -1
- package/dist/sdk/actions/shipping-action-provider.js.map +1 -1
- package/dist/sdk/cache/storefront-cache.js +1 -1
- package/dist/sdk/cache/storefront-cache.js.map +1 -1
- package/dist/sdk/cart/operations.d.ts +4 -4
- package/dist/sdk/cart/operations.js +1 -1
- package/dist/sdk/cart/operations.js.map +1 -1
- package/dist/sdk/cart/optimistic.js +1 -1
- package/dist/sdk/cart/optimistic.js.map +1 -1
- package/dist/sdk/cart/use-cart-actions.d.ts +1 -1
- package/dist/sdk/cart/use-cart-actions.js +3 -3
- package/dist/sdk/cart/use-cart-actions.js.map +1 -1
- package/dist/sdk/cart/use-cart-validate-on-open.js +1 -1
- package/dist/sdk/cart/use-cart-validate-on-open.js.map +1 -1
- package/dist/sdk/cart/write-gate.js +1 -1
- package/dist/sdk/cart/write-gate.js.map +1 -1
- package/dist/sdk/data/page-reactive-data-root.d.ts +2 -2
- package/dist/sdk/data/page-reactive-data-root.js.map +1 -1
- package/dist/sdk/data/reactive-data-provider.d.ts +1 -1
- package/dist/sdk/data/reactive-data-provider.js +1 -1
- package/dist/sdk/data/reactive-data-provider.js.map +1 -1
- package/dist/sdk/data/reactive-list-item-data-provider.d.ts +1 -1
- package/dist/sdk/data/reactive-list-item-data-provider.js +1 -1
- package/dist/sdk/data/reactive-list-item-data-provider.js.map +1 -1
- package/dist/sdk/data/use-reactive-entity.js +1 -1
- package/dist/sdk/data/use-reactive-entity.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/identity/operations.js.map +1 -1
- package/dist/sdk/identity/use-identity-actions.d.ts +1 -1
- package/dist/sdk/identity/use-identity-actions.js.map +1 -1
- package/dist/sdk/session/auth-operations.d.ts +1 -1
- package/dist/sdk/session/auth-operations.js.map +1 -1
- package/dist/sdk/session/use-session-actions.d.ts +1 -1
- package/dist/sdk/session/use-session-actions.js.map +1 -1
- package/dist/sdk/session/use-session-revalidation.js +65 -0
- package/dist/sdk/session/use-session-revalidation.js.map +1 -0
- package/dist/sdk/shipping/option-select-operations.d.ts +1 -1
- package/dist/sdk/shipping/option-select-operations.js.map +1 -1
- package/dist/sdk/shipping/request-timeout.js +1 -1
- package/dist/sdk/shipping/request-timeout.js.map +1 -1
- package/dist/sdk/shipping/shipping-operations.d.ts +1 -1
- package/dist/sdk/shipping/shipping-operations.js.map +1 -1
- package/dist/sdk/shipping/use-shipping-actions.d.ts +2 -2
- package/dist/sdk/shipping/use-shipping-actions.js.map +1 -1
- package/dist/sdk/state/cart.d.ts +3 -4
- package/dist/sdk/state/cart.js +6 -7
- package/dist/sdk/state/cart.js.map +1 -1
- package/dist/utils/safe-href.js +3 -3
- package/dist/utils/safe-href.js.map +1 -1
- package/package.json +1 -18
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operations.js","names":[],"sources":["../../../src/sdk/identity/operations.ts"],"sourcesContent":["import type { TypedDocumentNode } from '@apollo/client';\nimport { gql } from '../graphql-tag.js';\n\n/**\n * Identity (client-profile / client-preferences / customer-profile) GraphQL operations\n * (specs/fspg-859-email-field.md, specs/consent-opt-in-component.md,\n *
|
|
1
|
+
{"version":3,"file":"operations.js","names":[],"sources":["../../../src/sdk/identity/operations.ts"],"sourcesContent":["import type { TypedDocumentNode } from '@apollo/client';\nimport { gql } from '../graphql-tag.js';\n\n/**\n * Identity (client-profile / client-preferences / customer-profile) GraphQL operations\n * (specs/fspg-859-email-field.md, specs/consent-opt-in-component.md,\n * docs/storefront-core/reference-free-particle-model.md § Write side — actions).\n *\n * The first two writes reuse the EXISTING `clientProfileUpdate` / `clientPreferencesUpdate` mutations — partial\n * writes that touch only the fields they are given. `EmailIdentificationControl` sends `{ email }`\n * through `clientProfileUpdate` (flipping `Session.isIdentified`); the consent opt-in Controls send one\n * flag through `clientPreferencesUpdate`. Both live on the User Identity sibling entities of `Cart`,\n * not nested under it.\n *\n * `customerProfileUpdate` is the account-scoped counterpart: it persists to the shopper's saved\n * profile with no cart in play, keyed server-side off the session.\n *\n * Authored with the serializable `gql` tag (not the codegen `graphql()` factory) and explicitly\n * excluded from the codegen `documents` glob (see `codegen.ts`) — the `gql` tag alone is still\n * plucked by `graphql-codegen`, which would duplicate these hand-written types in `sdk/generated/`.\n * The identity writes do not need generated types and stay hand-typed `TypedDocumentNode`s,\n * mirroring how the ShippingSimulator matter authors its raw fragment.\n */\n\n// ---------------------------------------------------------------------------\n// Domain types — mirror the existing checkout schema\n// ---------------------------------------------------------------------------\n\n/** The ClientProfile slice returned by the mutation: the identity-step fields plus the Node identity keys. */\nexport type ClientProfileEntity = Readonly<{\n __typename?: 'ClientProfile';\n id: string;\n email: string | null;\n firstName: string | null;\n lastName: string | null;\n document: string | null;\n phone: string | null;\n}>;\n\n/** A single domain error returned by `clientProfileUpdate` (`ClientProfileUpdateError`). */\nexport type ClientProfileUserError = Readonly<{ code: string; message: string }>;\n\n/** Partial profile input — mirrors the schema's `ClientProfileUpdateInput`; omitted fields are left untouched. */\nexport type ClientProfileUpdateInput = Readonly<{\n email?: string;\n firstName?: string;\n lastName?: string;\n phone?: string;\n document?: string;\n}>;\n\n/** The Session slice returned alongside the profile — lets Apollo's normalized cache flip\n * `useSession()`'s isIdentified reactively, with no separate refetch. */\nexport type ClientProfileUpdateSessionEntity = Readonly<{\n __typename?: 'Session';\n id: string;\n loggedIn: boolean;\n canEditData: boolean;\n isIdentified: boolean;\n}>;\n\nexport type ClientProfileUpdateMutation = {\n clientProfileUpdate: {\n __typename?: 'ClientProfileUpdatePayload';\n clientProfile: ClientProfileEntity | null;\n session: ClientProfileUpdateSessionEntity | null;\n userErrors: ClientProfileUserError[];\n };\n};\n\nexport type ClientProfileUpdateMutationVariables = {\n input: ClientProfileUpdateInput;\n};\n\n// ---------------------------------------------------------------------------\n// Mutation operation\n// ---------------------------------------------------------------------------\n\nexport const CLIENT_PROFILE_UPDATE: TypedDocumentNode<\n ClientProfileUpdateMutation,\n ClientProfileUpdateMutationVariables\n> = gql`\n mutation ClientProfileUpdate($input: ClientProfileUpdateInput!) {\n clientProfileUpdate(input: $input) {\n __typename\n clientProfile {\n __typename\n id\n email\n firstName\n lastName\n document\n phone\n }\n session {\n __typename\n id\n loggedIn\n canEditData\n isIdentified\n }\n userErrors {\n code\n message\n }\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Client preferences (consent opt-ins) — mirror the profile write\n// ---------------------------------------------------------------------------\n\n/** The ClientPreferences slice this feature reads/writes: the two opt-in flags plus the Node key. */\nexport type ClientPreferencesEntity = Readonly<{\n __typename?: 'ClientPreferences';\n id: string;\n optinNewsLetter: boolean | null;\n savePersonalData: boolean | null;\n}>;\n\n/** The two opt-in fields, keyed exactly as the GraphQL `ClientPreferences` type / input. */\nexport type ClientPreferenceKey = 'optinNewsLetter' | 'savePersonalData';\n\n/** Partial preferences input — a Control sends ONE flag; omitted flags are left untouched. */\nexport type ClientPreferencesUpdateInput = Readonly<Partial<Record<ClientPreferenceKey, boolean>>>;\n\nexport type ClientPreferencesUpdateMutation = {\n clientPreferencesUpdate: {\n __typename?: 'ClientPreferencesUpdatePayload';\n clientPreferences: ClientPreferencesEntity | null;\n userErrors: ClientProfileUserError[];\n };\n};\n\nexport type ClientPreferencesUpdateMutationVariables = {\n input: ClientPreferencesUpdateInput;\n};\n\nexport const CLIENT_PREFERENCES_UPDATE: TypedDocumentNode<\n ClientPreferencesUpdateMutation,\n ClientPreferencesUpdateMutationVariables\n> = gql`\n mutation ClientPreferencesUpdate($input: ClientPreferencesUpdateInput!) {\n clientPreferencesUpdate(input: $input) {\n __typename\n clientPreferences {\n __typename\n id\n optinNewsLetter\n savePersonalData\n }\n userErrors {\n code\n message\n }\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Persistent shopper profile — account-scoped, not the cart projection above\n// ---------------------------------------------------------------------------\n\n/** The CustomerProfile slice the mutation returns: every readable field plus the cache key. */\nexport type CustomerProfileEntity = Readonly<{\n __typename?: 'CustomerProfile';\n id: string;\n firstName: string | null;\n lastName: string | null;\n email: string | null;\n homePhone: string | null;\n mobilePhone: string | null;\n document: string | null;\n documentType: string | null;\n birthDate: string | null;\n gender: string | null;\n}>;\n\n/**\n * A single write failure. `code` is a string rather than the server enum because the action adds\n * two client-originated codes — `NETWORK_ERROR` and `UNAUTHORIZED` — to the server's set.\n */\nexport type CustomerProfileUserError = Readonly<{ code: string; message: string }>;\n\n/**\n * Partial profile input — mirrors the schema's `CustomerProfileUpdateInput` verbatim. An omitted\n * field is left untouched upstream; an empty string is an explicit erase. `email` is absent by\n * construction: it is the login identifier and is not writable.\n */\nexport type CustomerProfileUpdateInput = Readonly<{\n firstName?: string;\n lastName?: string;\n homePhone?: string;\n mobilePhone?: string;\n document?: string;\n documentType?: string;\n birthDate?: string;\n gender?: string;\n}>;\n\nexport type CustomerProfileUpdateMutation = {\n customerProfileUpdate: {\n __typename?: 'CustomerProfileUpdatePayload';\n customerProfile: CustomerProfileEntity;\n userErrors: CustomerProfileUserError[];\n };\n};\n\nexport type CustomerProfileUpdateMutationVariables = {\n input: CustomerProfileUpdateInput;\n};\n\n/**\n * Selects the whole entity, not just the written fields: the normalized cache is what re-delivers\n * the new values to the profile Root, so a partial selection would leave it holding stale ones.\n */\nexport const CUSTOMER_PROFILE_UPDATE: TypedDocumentNode<\n CustomerProfileUpdateMutation,\n CustomerProfileUpdateMutationVariables\n> = gql`\n mutation CustomerProfileUpdate($input: CustomerProfileUpdateInput!) {\n customerProfileUpdate(input: $input) {\n __typename\n customerProfile {\n __typename\n id\n firstName\n lastName\n email\n homePhone\n mobilePhone\n document\n documentType\n birthDate\n gender\n }\n userErrors {\n code\n message\n }\n }\n }\n`;\n"],"mappings":";;AA8EA,MAAa,wBAGT,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0DP,MAAa,4BAGT,GAAG;;;;;;;;;;;;;;;;;;;;;AA2EP,MAAa,0BAGT,GAAG"}
|
|
@@ -5,7 +5,7 @@ import { ClientPreferenceKey, ClientProfileUserError, CustomerProfileUpdateInput
|
|
|
5
5
|
* The `identity` action namespace — the context-free owner of the shopper-profile,
|
|
6
6
|
* consent-preference, and persistent-profile writes
|
|
7
7
|
* (specs/fspg-859-email-field.md, specs/consent-opt-in-component.md,
|
|
8
|
-
*
|
|
8
|
+
* docs/storefront-core/reference-free-particle-model.md § Write side — actions,
|
|
9
9
|
* docs/storefront-core/adr-0010-actions-own-the-write-side.md).
|
|
10
10
|
*
|
|
11
11
|
* It mirrors `useCartActions`: a component anywhere in the tree dispatches the write through the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-identity-actions.js","names":[],"sources":["../../../src/sdk/identity/use-identity-actions.ts"],"sourcesContent":["'use client';\n\nimport { CombinedGraphQLErrors } from '@apollo/client/errors';\nimport { useMutation } from '@apollo/client/react';\nimport { defineActions } from '@vtex/fs-core';\nimport { useCallback, useMemo } from 'react';\nimport {\n CLIENT_PREFERENCES_UPDATE,\n CLIENT_PROFILE_UPDATE,\n type ClientPreferenceKey,\n type ClientProfileUserError,\n CUSTOMER_PROFILE_UPDATE,\n type CustomerProfileUpdateInput,\n type CustomerProfileUserError,\n} from './operations.js';\n\n/**\n * The `identity` action namespace — the context-free owner of the shopper-profile,\n * consent-preference, and persistent-profile writes\n * (specs/fspg-859-email-field.md, specs/consent-opt-in-component.md,\n * specs/sfs-3286-customer-profile-update-action.md,\n * docs/storefront-core/adr-0010-actions-own-the-write-side.md).\n *\n * It mirrors `useCartActions`: a component anywhere in the tree dispatches the write through the\n * compiler-injected `$actions.identity.*` prop, and the handler calls the corresponding mutation.\n * Every write is **async / pessimistic** (Decision 1): the caller awaits the persisted result. Each\n * call returns its `userErrors` (empty on success) so the caller can surface server-side failures\n * inline; the returned entity self-updates the normalized cache and every reactive subscriber.\n *\n * The first two writes target the cart's sibling identity entities; `customerProfileUpdate` targets\n * the shopper's account profile, which needs no cart at all.\n */\n\n/**\n * Payload for the profile write. Callers persist only the fields they own; other fields are\n * untouched. The payload is passed through to `ClientProfileUpdateInput` unmapped, so this type\n * mirrors that input verbatim — a stacked field only needs a field to already exist here to reach\n * the wire.\n */\nexport type ClientProfileUpdatePayload = Readonly<{\n email?: string;\n firstName?: string;\n lastName?: string;\n phone?: string;\n document?: string;\n}>;\n\n/** Payload for the preferences write — one opt-in flag; the other flag is left untouched. */\nexport type ClientPreferencesUpdatePayload = Readonly<Partial<Record<ClientPreferenceKey, boolean>>>;\n\n/**\n * Payload for the persistent-profile write. Mirrors `CustomerProfileUpdateInput` verbatim and is\n * forwarded unmapped, so an omitted field stays untouched upstream and an empty string erases.\n */\nexport type CustomerProfileUpdatePayload = CustomerProfileUpdateInput;\n\nexport type IdentityActions = Readonly<{\n /**\n * Persist the provided profile fields (partial) to the active cart's `clientProfileData`.\n * Async — awaits the mutation before resolving. Returns the resulting `userErrors` (empty on\n * success).\n */\n clientProfileUpdate: (payload: ClientProfileUpdatePayload) => Promise<readonly ClientProfileUserError[]>;\n /**\n * Persist the provided consent opt-in flag(s) (partial) to the shopper's `ClientPreferences`\n * entity (order-form-backed). Async — awaits the mutation before resolving. Writes ONLY the\n * requested field. Returns the resulting `userErrors` (empty on success).\n */\n clientPreferencesUpdate: (payload: ClientPreferencesUpdatePayload) => Promise<readonly ClientProfileUserError[]>;\n /**\n * Persist the provided profile fields (partial) to the shopper's persistent, cart-independent\n * account profile. Async — awaits the mutation before resolving. Sends exactly the fields it is\n * given: an omitted field is left untouched, an empty string erases. Returns the resulting\n * `userErrors` (empty on success).\n */\n customerProfileUpdate: (payload: CustomerProfileUpdatePayload) => Promise<readonly CustomerProfileUserError[]>;\n}>;\n\nconst NETWORK_PROFILE_ERROR: ClientProfileUserError = {\n code: 'NETWORK_ERROR',\n message: 'Unable to save your details. Please try again.',\n};\n\nconst NETWORK_PREFERENCE_ERROR: ClientProfileUserError = {\n code: 'NETWORK_ERROR',\n message: 'Unable to save your preference. Please try again.',\n};\n\nconst NETWORK_CUSTOMER_PROFILE_ERROR: CustomerProfileUserError = {\n code: 'NETWORK_ERROR',\n message: 'Unable to save your profile. Please try again.',\n};\n\nconst UNAUTHORIZED_CUSTOMER_PROFILE_ERROR: CustomerProfileUserError = {\n code: 'UNAUTHORIZED',\n message: 'Your session has expired. Please sign in again.',\n};\n\n/**\n * The profile module answers an anonymous or expired session with a top-level `UNAUTHORIZED`, never\n * a `userError`. Collapsing it into `NETWORK_ERROR` would offer a retry that cannot succeed, so it\n * gets its own code and the caller can send the shopper back to sign-in.\n */\nfunction toCustomerProfileError(error: unknown): CustomerProfileUserError {\n if (CombinedGraphQLErrors.is(error)) {\n const unauthorized = error.errors.some(({ extensions }) => extensions?.code === 'UNAUTHORIZED');\n if (unauthorized) return UNAUTHORIZED_CUSTOMER_PROFILE_ERROR;\n }\n return NETWORK_CUSTOMER_PROFILE_ERROR;\n}\n\n/**\n * The `identity` namespace hook. Registered with the first-party action registry via `identityActions`\n * below; the compiler-emitted `IdentityActionProvider` calls it and threads the narrowed methods into\n * each consumer's `$actions.identity`.\n */\nexport function useIdentityActions(): IdentityActions {\n const [profileMutation] = useMutation(CLIENT_PROFILE_UPDATE);\n const [preferencesMutation] = useMutation(CLIENT_PREFERENCES_UPDATE);\n const [customerProfileMutation] = useMutation(CUSTOMER_PROFILE_UPDATE);\n\n const clientProfileUpdate = useCallback<IdentityActions['clientProfileUpdate']>(\n async (payload) => {\n try {\n const result = await profileMutation({ variables: { input: payload } });\n return result.data?.clientProfileUpdate?.userErrors ?? [];\n } catch (error) {\n // Log only the error name — the message can echo the submitted email (PII).\n console.error(\n '[identity.clientProfileUpdate] clientProfileUpdate failed',\n error instanceof Error ? error.name : 'UnknownError',\n );\n return [NETWORK_PROFILE_ERROR];\n }\n },\n [profileMutation],\n );\n\n const clientPreferencesUpdate = useCallback<IdentityActions['clientPreferencesUpdate']>(\n async (payload) => {\n try {\n const result = await preferencesMutation({ variables: { input: payload } });\n return result.data?.clientPreferencesUpdate?.userErrors ?? [];\n } catch (error) {\n // Log only the error name — mirrors the profile write's PII-safe logging.\n console.error(\n '[identity.clientPreferencesUpdate] clientPreferencesUpdate failed',\n error instanceof Error ? error.name : 'UnknownError',\n );\n return [NETWORK_PREFERENCE_ERROR];\n }\n },\n [preferencesMutation],\n );\n\n const customerProfileUpdate = useCallback<IdentityActions['customerProfileUpdate']>(\n async (payload) => {\n try {\n // Forwarded unmapped: an empty string is an explicit erase and must survive to the wire.\n const result = await customerProfileMutation({ variables: { input: payload } });\n return result.data?.customerProfileUpdate?.userErrors ?? [];\n } catch (error) {\n // Log only the error name — the message can echo a submitted profile value (PII).\n console.error(\n '[identity.customerProfileUpdate] customerProfileUpdate failed',\n error instanceof Error ? error.name : 'UnknownError',\n );\n return [toCustomerProfileError(error)];\n }\n },\n [customerProfileMutation],\n );\n\n return useMemo(\n () => ({ clientProfileUpdate, clientPreferencesUpdate, customerProfileUpdate }),\n [clientProfileUpdate, clientPreferencesUpdate, customerProfileUpdate],\n );\n}\n\n/**\n * Declares the `identity` 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 * `IdentityActionProvider` calls `useIdentityActions` directly.\n */\nexport const identityActions = defineActions({\n namespace: 'identity',\n description:\n 'Mutation-backed writes against the shopper identity entities (client profile, consent preferences, account profile).',\n provider: { name: 'IdentityActionProvider', importPath: '@vtex/faststore/internal/client' },\n methods: {\n clientProfileUpdate: {\n description:\n 'Persist profile fields (partial) to the active cart (async strategy). Sending email flips Session.isIdentified.',\n payload: [\n { name: 'email', type: 'string', optional: true },\n { name: 'firstName', type: 'string', optional: true },\n { name: 'lastName', type: 'string', optional: true },\n { name: 'phone', type: 'string', optional: true },\n { name: 'document', type: 'string', optional: true },\n ],\n },\n clientPreferencesUpdate: {\n description:\n 'Persist consent opt-in flags (partial) to the active cart (async strategy). Writes only the provided flag(s).',\n payload: [\n { name: 'optinNewsLetter', type: 'boolean', optional: true },\n { name: 'savePersonalData', type: 'boolean', optional: true },\n ],\n },\n customerProfileUpdate: {\n description:\n 'Persist profile fields (partial) to the persistent account profile, not the cart (async strategy). Omitted fields are untouched; an empty string erases.',\n payload: [\n { name: 'firstName', type: 'string', optional: true },\n { name: 'lastName', type: 'string', optional: true },\n { name: 'homePhone', type: 'string', optional: true },\n { name: 'mobilePhone', type: 'string', optional: true },\n { name: 'document', type: 'string', optional: true },\n { name: 'documentType', type: 'string', optional: true },\n { name: 'birthDate', type: 'string', optional: true },\n { name: 'gender', type: 'string', optional: true },\n ],\n },\n },\n use: useIdentityActions,\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-identity-actions.js","names":[],"sources":["../../../src/sdk/identity/use-identity-actions.ts"],"sourcesContent":["'use client';\n\nimport { CombinedGraphQLErrors } from '@apollo/client/errors';\nimport { useMutation } from '@apollo/client/react';\nimport { defineActions } from '@vtex/fs-core';\nimport { useCallback, useMemo } from 'react';\nimport {\n CLIENT_PREFERENCES_UPDATE,\n CLIENT_PROFILE_UPDATE,\n type ClientPreferenceKey,\n type ClientProfileUserError,\n CUSTOMER_PROFILE_UPDATE,\n type CustomerProfileUpdateInput,\n type CustomerProfileUserError,\n} from './operations.js';\n\n/**\n * The `identity` action namespace — the context-free owner of the shopper-profile,\n * consent-preference, and persistent-profile writes\n * (specs/fspg-859-email-field.md, specs/consent-opt-in-component.md,\n * docs/storefront-core/reference-free-particle-model.md § Write side — actions,\n * docs/storefront-core/adr-0010-actions-own-the-write-side.md).\n *\n * It mirrors `useCartActions`: a component anywhere in the tree dispatches the write through the\n * compiler-injected `$actions.identity.*` prop, and the handler calls the corresponding mutation.\n * Every write is **async / pessimistic** (Decision 1): the caller awaits the persisted result. Each\n * call returns its `userErrors` (empty on success) so the caller can surface server-side failures\n * inline; the returned entity self-updates the normalized cache and every reactive subscriber.\n *\n * The first two writes target the cart's sibling identity entities; `customerProfileUpdate` targets\n * the shopper's account profile, which needs no cart at all.\n */\n\n/**\n * Payload for the profile write. Callers persist only the fields they own; other fields are\n * untouched. The payload is passed through to `ClientProfileUpdateInput` unmapped, so this type\n * mirrors that input verbatim — a stacked field only needs a field to already exist here to reach\n * the wire.\n */\nexport type ClientProfileUpdatePayload = Readonly<{\n email?: string;\n firstName?: string;\n lastName?: string;\n phone?: string;\n document?: string;\n}>;\n\n/** Payload for the preferences write — one opt-in flag; the other flag is left untouched. */\nexport type ClientPreferencesUpdatePayload = Readonly<Partial<Record<ClientPreferenceKey, boolean>>>;\n\n/**\n * Payload for the persistent-profile write. Mirrors `CustomerProfileUpdateInput` verbatim and is\n * forwarded unmapped, so an omitted field stays untouched upstream and an empty string erases.\n */\nexport type CustomerProfileUpdatePayload = CustomerProfileUpdateInput;\n\nexport type IdentityActions = Readonly<{\n /**\n * Persist the provided profile fields (partial) to the active cart's `clientProfileData`.\n * Async — awaits the mutation before resolving. Returns the resulting `userErrors` (empty on\n * success).\n */\n clientProfileUpdate: (payload: ClientProfileUpdatePayload) => Promise<readonly ClientProfileUserError[]>;\n /**\n * Persist the provided consent opt-in flag(s) (partial) to the shopper's `ClientPreferences`\n * entity (order-form-backed). Async — awaits the mutation before resolving. Writes ONLY the\n * requested field. Returns the resulting `userErrors` (empty on success).\n */\n clientPreferencesUpdate: (payload: ClientPreferencesUpdatePayload) => Promise<readonly ClientProfileUserError[]>;\n /**\n * Persist the provided profile fields (partial) to the shopper's persistent, cart-independent\n * account profile. Async — awaits the mutation before resolving. Sends exactly the fields it is\n * given: an omitted field is left untouched, an empty string erases. Returns the resulting\n * `userErrors` (empty on success).\n */\n customerProfileUpdate: (payload: CustomerProfileUpdatePayload) => Promise<readonly CustomerProfileUserError[]>;\n}>;\n\nconst NETWORK_PROFILE_ERROR: ClientProfileUserError = {\n code: 'NETWORK_ERROR',\n message: 'Unable to save your details. Please try again.',\n};\n\nconst NETWORK_PREFERENCE_ERROR: ClientProfileUserError = {\n code: 'NETWORK_ERROR',\n message: 'Unable to save your preference. Please try again.',\n};\n\nconst NETWORK_CUSTOMER_PROFILE_ERROR: CustomerProfileUserError = {\n code: 'NETWORK_ERROR',\n message: 'Unable to save your profile. Please try again.',\n};\n\nconst UNAUTHORIZED_CUSTOMER_PROFILE_ERROR: CustomerProfileUserError = {\n code: 'UNAUTHORIZED',\n message: 'Your session has expired. Please sign in again.',\n};\n\n/**\n * The profile module answers an anonymous or expired session with a top-level `UNAUTHORIZED`, never\n * a `userError`. Collapsing it into `NETWORK_ERROR` would offer a retry that cannot succeed, so it\n * gets its own code and the caller can send the shopper back to sign-in.\n */\nfunction toCustomerProfileError(error: unknown): CustomerProfileUserError {\n if (CombinedGraphQLErrors.is(error)) {\n const unauthorized = error.errors.some(({ extensions }) => extensions?.code === 'UNAUTHORIZED');\n if (unauthorized) return UNAUTHORIZED_CUSTOMER_PROFILE_ERROR;\n }\n return NETWORK_CUSTOMER_PROFILE_ERROR;\n}\n\n/**\n * The `identity` namespace hook. Registered with the first-party action registry via `identityActions`\n * below; the compiler-emitted `IdentityActionProvider` calls it and threads the narrowed methods into\n * each consumer's `$actions.identity`.\n */\nexport function useIdentityActions(): IdentityActions {\n const [profileMutation] = useMutation(CLIENT_PROFILE_UPDATE);\n const [preferencesMutation] = useMutation(CLIENT_PREFERENCES_UPDATE);\n const [customerProfileMutation] = useMutation(CUSTOMER_PROFILE_UPDATE);\n\n const clientProfileUpdate = useCallback<IdentityActions['clientProfileUpdate']>(\n async (payload) => {\n try {\n const result = await profileMutation({ variables: { input: payload } });\n return result.data?.clientProfileUpdate?.userErrors ?? [];\n } catch (error) {\n // Log only the error name — the message can echo the submitted email (PII).\n console.error(\n '[identity.clientProfileUpdate] clientProfileUpdate failed',\n error instanceof Error ? error.name : 'UnknownError',\n );\n return [NETWORK_PROFILE_ERROR];\n }\n },\n [profileMutation],\n );\n\n const clientPreferencesUpdate = useCallback<IdentityActions['clientPreferencesUpdate']>(\n async (payload) => {\n try {\n const result = await preferencesMutation({ variables: { input: payload } });\n return result.data?.clientPreferencesUpdate?.userErrors ?? [];\n } catch (error) {\n // Log only the error name — mirrors the profile write's PII-safe logging.\n console.error(\n '[identity.clientPreferencesUpdate] clientPreferencesUpdate failed',\n error instanceof Error ? error.name : 'UnknownError',\n );\n return [NETWORK_PREFERENCE_ERROR];\n }\n },\n [preferencesMutation],\n );\n\n const customerProfileUpdate = useCallback<IdentityActions['customerProfileUpdate']>(\n async (payload) => {\n try {\n // Forwarded unmapped: an empty string is an explicit erase and must survive to the wire.\n const result = await customerProfileMutation({ variables: { input: payload } });\n return result.data?.customerProfileUpdate?.userErrors ?? [];\n } catch (error) {\n // Log only the error name — the message can echo a submitted profile value (PII).\n console.error(\n '[identity.customerProfileUpdate] customerProfileUpdate failed',\n error instanceof Error ? error.name : 'UnknownError',\n );\n return [toCustomerProfileError(error)];\n }\n },\n [customerProfileMutation],\n );\n\n return useMemo(\n () => ({ clientProfileUpdate, clientPreferencesUpdate, customerProfileUpdate }),\n [clientProfileUpdate, clientPreferencesUpdate, customerProfileUpdate],\n );\n}\n\n/**\n * Declares the `identity` 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 * `IdentityActionProvider` calls `useIdentityActions` directly.\n */\nexport const identityActions = defineActions({\n namespace: 'identity',\n description:\n 'Mutation-backed writes against the shopper identity entities (client profile, consent preferences, account profile).',\n provider: { name: 'IdentityActionProvider', importPath: '@vtex/faststore/internal/client' },\n methods: {\n clientProfileUpdate: {\n description:\n 'Persist profile fields (partial) to the active cart (async strategy). Sending email flips Session.isIdentified.',\n payload: [\n { name: 'email', type: 'string', optional: true },\n { name: 'firstName', type: 'string', optional: true },\n { name: 'lastName', type: 'string', optional: true },\n { name: 'phone', type: 'string', optional: true },\n { name: 'document', type: 'string', optional: true },\n ],\n },\n clientPreferencesUpdate: {\n description:\n 'Persist consent opt-in flags (partial) to the active cart (async strategy). Writes only the provided flag(s).',\n payload: [\n { name: 'optinNewsLetter', type: 'boolean', optional: true },\n { name: 'savePersonalData', type: 'boolean', optional: true },\n ],\n },\n customerProfileUpdate: {\n description:\n 'Persist profile fields (partial) to the persistent account profile, not the cart (async strategy). Omitted fields are untouched; an empty string erases.',\n payload: [\n { name: 'firstName', type: 'string', optional: true },\n { name: 'lastName', type: 'string', optional: true },\n { name: 'homePhone', type: 'string', optional: true },\n { name: 'mobilePhone', type: 'string', optional: true },\n { name: 'document', type: 'string', optional: true },\n { name: 'documentType', type: 'string', optional: true },\n { name: 'birthDate', type: 'string', optional: true },\n { name: 'gender', type: 'string', optional: true },\n ],\n },\n },\n use: useIdentityActions,\n});\n"],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import "./operations.js";
|
|
|
3
3
|
import { TypedDocumentNode } from "@apollo/client";
|
|
4
4
|
//#region src/sdk/session/auth-operations.d.ts
|
|
5
5
|
/**
|
|
6
|
-
* Session auth mutation documents (
|
|
6
|
+
* Session auth mutation documents (docs/storefront-core/reference-free-particle-model.md § Write side — actions).
|
|
7
7
|
*
|
|
8
8
|
* Authored with the hand `gql` tag — the codegen `graphql()` factory is reserved for the reactive
|
|
9
9
|
* read path (`operations.ts`) — and explicitly excluded from the codegen `documents` glob (see
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-operations.js","names":[],"sources":["../../../src/sdk/session/auth-operations.ts"],"sourcesContent":["import type { TypedDocumentNode } from '@apollo/client';\nimport { type CartEntity, CartFragment } from '../cart/operations.js';\nimport { gql } from '../graphql-tag.js';\nimport type { SessionEntity } from './operations.js';\n\n/**\n * Session auth mutation documents (
|
|
1
|
+
{"version":3,"file":"auth-operations.js","names":[],"sources":["../../../src/sdk/session/auth-operations.ts"],"sourcesContent":["import type { TypedDocumentNode } from '@apollo/client';\nimport { type CartEntity, CartFragment } from '../cart/operations.js';\nimport { gql } from '../graphql-tag.js';\nimport type { SessionEntity } from './operations.js';\n\n/**\n * Session auth mutation documents (docs/storefront-core/reference-free-particle-model.md § Write side — actions).\n *\n * Authored with the hand `gql` tag — the codegen `graphql()` factory is reserved for the reactive\n * read path (`operations.ts`) — and explicitly excluded from the codegen `documents` glob (see\n * `codegen.ts`): the `gql` tag alone is still plucked by `graphql-codegen`, which would duplicate\n * these hand-written types in `sdk/generated/`. The mutations stay hand-typed as\n * `TypedDocumentNode`s (Decision 3). Login/validate/logout share `SessionLoginPayloadFragment`,\n * a hand-written fragment selecting the full refreshed payload:\n * `session` with exactly `SessionFragment`'s field set, `cart` spreading the canonical codegen\n * `CartFragment` (interpolated as a document), and the full `clientProfile` / `clientPreferences`\n * slices. All payload entities are normalized Relay Nodes keyed on the orderFormId, so the response\n * write into the cache *is* the post-login refresh: `SessionRoot`'s reactive read flips with no\n * refetch and no imperative cache write (Decision 2).\n *\n * Failures follow ADR-00007's two-path contract: domain outcomes arrive as payload `userErrors`\n * (`SessionError`); transport and top-level GraphQL failures are infrastructure, mapped by the\n * handlers in `use-session-actions.ts` to the one client-synthesized code, `NETWORK_ERROR`.\n */\n\n/** A typed login-attempt failure; NETWORK_ERROR is the one client-synthesized code (Decision 5). */\nexport type SessionUserError = Readonly<{ code: string; message: string }>;\n\nexport type SessionPasswordLoginPayload = Readonly<{ email: string; password: string }>;\n\nexport type SessionRequestAccessCodePayload = Readonly<{ email: string }>;\n\nexport type SessionValidateAccessCodePayload = Readonly<{\n email: string;\n accessCode: string;\n /** Opaque token from requestAccessCode, handed back unchanged (Decision 4). */\n authenticationToken: string;\n}>;\n\nexport type SessionAccessCodeRequestResult = Readonly<{\n authenticationToken: string | null;\n userErrors: readonly SessionUserError[];\n}>;\n\n/** The shopper's profile slice of the login payload; fields are null when unset. */\nexport type ClientProfileEntity = Readonly<{\n __typename: 'ClientProfile';\n id: string;\n email: string | null;\n firstName: string | null;\n lastName: string | null;\n phone: string | null;\n document: string | null;\n}>;\n\n/** The shopper's preference opt-ins slice of the login payload; fields are null when unset. */\nexport type ClientPreferencesEntity = Readonly<{\n __typename: 'ClientPreferences';\n id: string;\n optinNewsLetter: boolean | null;\n savePersonalData: boolean | null;\n}>;\n\n/** The full login/logout mutation payload, projected from the one refreshed order form. */\nexport type SessionLoginPayloadEntity = Readonly<{\n __typename: 'SessionLoginPayload';\n session: SessionEntity;\n cart: CartEntity;\n clientProfile: ClientProfileEntity;\n clientPreferences: ClientPreferencesEntity;\n userErrors: readonly SessionUserError[];\n}>;\n\nexport type SessionRequestAccessCodePayloadEntity = Readonly<{\n __typename: 'SessionRequestAccessCodePayload';\n authenticationToken: string | null;\n userErrors: readonly SessionUserError[];\n}>;\n\ntype SessionLoginWithPasswordData = Readonly<{ sessionLoginWithPassword: SessionLoginPayloadEntity }>;\ntype SessionRequestAccessCodeData = Readonly<{ sessionRequestAccessCode: SessionRequestAccessCodePayloadEntity }>;\ntype SessionValidateAccessCodeData = Readonly<{ sessionValidateAccessCode: SessionLoginPayloadEntity }>;\ntype SessionLogoutData = Readonly<{ sessionLogout: SessionLoginPayloadEntity }>;\n\n/** The shared full-refreshed-payload selection for login/validate/logout; embeds `CartFragment`. */\nconst SessionLoginPayloadFragment = gql`\n fragment SessionLoginPayloadFragment on SessionLoginPayload {\n __typename\n session {\n __typename\n id\n loggedIn\n canEditData\n isIdentified\n }\n cart {\n ...CartFragment\n }\n clientProfile {\n __typename\n id\n email\n firstName\n lastName\n phone\n document\n }\n clientPreferences {\n __typename\n id\n optinNewsLetter\n savePersonalData\n }\n userErrors {\n code\n message\n }\n }\n ${CartFragment}\n`;\n\nexport const SESSION_LOGIN_WITH_PASSWORD: TypedDocumentNode<\n SessionLoginWithPasswordData,\n { input: SessionPasswordLoginPayload }\n> = gql`\n mutation SessionLoginWithPassword($input: SessionPasswordLoginInput!) {\n sessionLoginWithPassword(input: $input) {\n ...SessionLoginPayloadFragment\n }\n }\n ${SessionLoginPayloadFragment}\n`;\n\nexport const SESSION_REQUEST_ACCESS_CODE: TypedDocumentNode<\n SessionRequestAccessCodeData,\n { input: SessionRequestAccessCodePayload }\n> = gql`\n mutation SessionRequestAccessCode($input: SessionRequestAccessCodeInput!) {\n sessionRequestAccessCode(input: $input) {\n __typename\n authenticationToken\n userErrors {\n code\n message\n }\n }\n }\n`;\n\nexport const SESSION_VALIDATE_ACCESS_CODE: TypedDocumentNode<\n SessionValidateAccessCodeData,\n { input: SessionValidateAccessCodePayload }\n> = gql`\n mutation SessionValidateAccessCode($input: SessionValidateAccessCodeInput!) {\n sessionValidateAccessCode(input: $input) {\n ...SessionLoginPayloadFragment\n }\n }\n ${SessionLoginPayloadFragment}\n`;\n\nexport const SESSION_LOGOUT: TypedDocumentNode<SessionLogoutData, Record<string, never>> = gql`\n mutation SessionLogout {\n sessionLogout {\n ...SessionLoginPayloadFragment\n }\n }\n ${SessionLoginPayloadFragment}\n`;\n"],"mappings":";;;;AAqFA,MAAM,8BAA8B,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAiCnC,aAAa;;AAGjB,MAAa,8BAGT,GAAG;;;;;;IAMH,4BAA4B;;AAGhC,MAAa,8BAGT,GAAG;;;;;;;;;;;;AAaP,MAAa,+BAGT,GAAG;;;;;;IAMH,4BAA4B;;AAGhC,MAAa,iBAA8E,GAAG;;;;;;IAM1F,4BAA4B"}
|
|
@@ -3,7 +3,7 @@ import { SessionAccessCodeRequestResult, SessionPasswordLoginPayload, SessionReq
|
|
|
3
3
|
//#region src/sdk/session/use-session-actions.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* The `session` action namespace — the one sanctioned client write surface for authentication
|
|
6
|
-
* (
|
|
6
|
+
* (docs/storefront-core/reference-free-particle-model.md § Write side — actions, docs/storefront-core/adr-0010-actions-own-the-write-side.md).
|
|
7
7
|
*
|
|
8
8
|
* Every action is async/pessimistic (Decision 1): the handler awaits the mutation and only the
|
|
9
9
|
* server-confirmed payload changes the cache — authentication cannot be guessed client-side. The
|
|
@@ -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 * (
|
|
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":";;;;;;;AAwCA,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,KAAK;GAClF,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,KAAK;GAClF,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,KAAK;GACpF,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,KAAK;GAC5D,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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { graphql } from "../generated/gql.js";
|
|
3
|
+
import { getApolloContext } from "@apollo/client/react";
|
|
4
|
+
import { useCallback, useContext } from "react";
|
|
5
|
+
//#region src/sdk/session/use-session-revalidation.ts
|
|
6
|
+
/**
|
|
7
|
+
* The imperative re-read of the session, for the one case the reactive path cannot serve: a document
|
|
8
|
+
* the browser restored from its back-forward cache. A restored document resumes its heap without
|
|
9
|
+
* remounting, so `PageReactiveDataRoot` never re-runs its `cache-first` query and
|
|
10
|
+
* `useReactiveEntity` never issues a request — the session comes back exactly as stale as the frame
|
|
11
|
+
* around it.
|
|
12
|
+
*
|
|
13
|
+
* Selects the same field set as `SessionFragment`, so the response writes over the entity the layout
|
|
14
|
+
* query already references and every `SessionRoot` below it reacts.
|
|
15
|
+
*/
|
|
16
|
+
const ACCOUNT_SESSION_REVALIDATE = graphql(`
|
|
17
|
+
query AccountSessionRevalidate {
|
|
18
|
+
session {
|
|
19
|
+
...SessionFragment
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
`);
|
|
23
|
+
/**
|
|
24
|
+
* Discards the cached session and re-reads it from the network, resolving what it was able to find
|
|
25
|
+
* out. Only `failed` means the caller is holding an answer it has reason to distrust: the session was
|
|
26
|
+
* discarded and the query that should have replaced it rejected.
|
|
27
|
+
*
|
|
28
|
+
* **The discard comes first, and that ordering is the contract.** For the whole of the re-read there
|
|
29
|
+
* must be no session in the cache for anything to read: a re-read that merely overwrote would leave
|
|
30
|
+
* the restored `loggedIn` answering every consumer in the tree until the network replied. What has to
|
|
31
|
+
* go is the normalized entity, not the root field that points at it — the reactive read subscribes to
|
|
32
|
+
* `Session:<id>` through `useFragment`, so a root field cleared from under a live entity leaves that
|
|
33
|
+
* read complete and the consumer resolved.
|
|
34
|
+
*
|
|
35
|
+
* The consumers follow asynchronously, once the watched query has re-read: a component that must
|
|
36
|
+
* withhold something in the same frame the discard happens has to hold that state itself, and
|
|
37
|
+
* `AccountAccessGate` does.
|
|
38
|
+
*
|
|
39
|
+
* The client is resolved through Apollo's context rather than `useApolloClient()`, which throws when
|
|
40
|
+
* there is none: every session-aware story and test in this package renders `SessionRoot` bare, and a
|
|
41
|
+
* revalidation that cannot run is not a reason for a component to fail to render.
|
|
42
|
+
*/
|
|
43
|
+
function useSessionRevalidation() {
|
|
44
|
+
const { client } = useContext(getApolloContext());
|
|
45
|
+
return useCallback(async () => {
|
|
46
|
+
if (!client) return "unavailable";
|
|
47
|
+
try {
|
|
48
|
+
const cached = client.cache.readQuery({ query: ACCOUNT_SESSION_REVALIDATE });
|
|
49
|
+
const entity = cached?.session ? client.cache.identify(cached.session) : void 0;
|
|
50
|
+
if (entity) client.cache.evict({ id: entity });
|
|
51
|
+
await client.query({
|
|
52
|
+
query: ACCOUNT_SESSION_REVALIDATE,
|
|
53
|
+
fetchPolicy: "network-only"
|
|
54
|
+
});
|
|
55
|
+
return "revalidated";
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error("[useSessionRevalidation] session re-read failed", error);
|
|
58
|
+
return "failed";
|
|
59
|
+
}
|
|
60
|
+
}, [client]);
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
export { ACCOUNT_SESSION_REVALIDATE, useSessionRevalidation };
|
|
64
|
+
|
|
65
|
+
//# sourceMappingURL=use-session-revalidation.js.map
|
|
@@ -0,0 +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":";;;;;;;;;;;;;;;AAgBA,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,KAAK;GACtE,OAAO;EACT;CACF,GAAG,CAAC,MAAM,CAAC;AACb"}
|
|
@@ -2,7 +2,7 @@ import "../cart/operations.js";
|
|
|
2
2
|
import { TypedDocumentNode } from "@apollo/client";
|
|
3
3
|
//#region src/sdk/shipping/option-select-operations.d.ts
|
|
4
4
|
/**
|
|
5
|
-
* `shippingOptionSelect` mutation document (
|
|
5
|
+
* `shippingOptionSelect` mutation document (docs/storefront-core/reference-free-particle-model.md § The cart write contract).
|
|
6
6
|
*
|
|
7
7
|
* Authored with the hand `gql` tag — the codegen `graphql()` factory is reserved for the reactive
|
|
8
8
|
* read path (`shipping/operations.ts`) — and explicitly excluded from the codegen `documents` glob
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"option-select-operations.js","names":[],"sources":["../../../src/sdk/shipping/option-select-operations.ts"],"sourcesContent":["import type { TypedDocumentNode } from '@apollo/client';\nimport { type CartEntity, CartFragment } from '../cart/operations.js';\nimport { gql } from '../graphql-tag.js';\n\n/**\n * `shippingOptionSelect` mutation document (
|
|
1
|
+
{"version":3,"file":"option-select-operations.js","names":[],"sources":["../../../src/sdk/shipping/option-select-operations.ts"],"sourcesContent":["import type { TypedDocumentNode } from '@apollo/client';\nimport { type CartEntity, CartFragment } from '../cart/operations.js';\nimport { gql } from '../graphql-tag.js';\n\n/**\n * `shippingOptionSelect` mutation document (docs/storefront-core/reference-free-particle-model.md § The cart write contract).\n *\n * Authored with the hand `gql` tag — the codegen `graphql()` factory is reserved for the reactive\n * read path (`shipping/operations.ts`) — and explicitly excluded from the codegen `documents` glob\n * (see `codegen.ts`): the `gql` tag alone is still plucked by `graphql-codegen`, which would\n * duplicate these hand-written types in `sdk/generated/`. Mirrors the `session`/`identity`\n * action-mutation convention (hand-typed `TypedDocumentNode`s).\n *\n * Selects `cart { ...CartFragment }` alongside `shipping` so the normalized Apollo cache updates\n * the cart totalizer in the same round trip (T1 epic note). Both `shipping` and its nested\n * `options` select `id`/`__typename` so `InMemoryCache` normalizes them as the same entities the\n * reactive `DeliveryOptionsData` fragment reads — required for the mutation response to flow back\n * into the reactive UI with no refetch (Decision 1/Decision 5).\n */\n\nexport type ShippingOptionSelectUserError = Readonly<{ code: string; message: string }>;\n\nexport type ShippingOptionSelectInput = Readonly<{ optionId: string }>;\n\ntype ShippingOptionEntity = Readonly<{\n __typename?: 'ShippingOption';\n id: string;\n name: string;\n channel: string;\n shippingEstimate: Readonly<{ __typename?: 'ShippingEstimate'; value: number; unit: string }>;\n price: Readonly<{ __typename?: 'Money'; amount: number; currencyCode: string }>;\n listPrice: Readonly<{ __typename?: 'Money'; amount: number; currencyCode: string }>;\n}>;\n\ntype ShippingEntity = Readonly<{\n __typename?: 'Shipping';\n id: string;\n options: readonly ShippingOptionEntity[];\n selectedOptionId: string | null;\n}>;\n\ntype ShippingOptionSelectPayloadEntity = Readonly<{\n __typename?: 'ShippingOptionSelectPayload';\n shipping: ShippingEntity | null;\n cart: CartEntity | null;\n userErrors: readonly ShippingOptionSelectUserError[];\n}>;\n\ntype ShippingOptionSelectMutation = Readonly<{ shippingOptionSelect: ShippingOptionSelectPayloadEntity }>;\n\ntype ShippingOptionSelectMutationVariables = Readonly<{ input: ShippingOptionSelectInput }>;\n\nexport const SHIPPING_OPTION_SELECT: TypedDocumentNode<\n ShippingOptionSelectMutation,\n ShippingOptionSelectMutationVariables\n> = gql`\n mutation ShippingOptionSelect($input: ShippingOptionSelectInput!) {\n shippingOptionSelect(input: $input) {\n __typename\n shipping {\n __typename\n id\n options {\n __typename\n id\n name\n channel\n shippingEstimate {\n value\n unit\n }\n price {\n amount\n currencyCode\n }\n listPrice {\n amount\n currencyCode\n }\n }\n selectedOptionId\n }\n cart {\n ...CartFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n ${CartFragment}\n`;\n"],"mappings":";;;AAoDA,MAAa,yBAGT,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAoCH,aAAa"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region src/sdk/shipping/request-timeout.ts
|
|
2
2
|
/**
|
|
3
|
-
* Bounds the default shipping-simulation SDK request (
|
|
3
|
+
* Bounds the default shipping-simulation SDK request (docs/storefront-core/reference-free-particle-model.md § Read side — data).
|
|
4
4
|
*
|
|
5
5
|
* The default `useShippingSimulation` / `useCartShippingSimulation` callbacks run an imperative
|
|
6
6
|
* `client.query` with no time bound, so a logistics transport that never settles leaves the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-timeout.js","names":[],"sources":["../../../src/sdk/shipping/request-timeout.ts"],"sourcesContent":["/**\n * Bounds the default shipping-simulation SDK request (
|
|
1
|
+
{"version":3,"file":"request-timeout.js","names":[],"sources":["../../../src/sdk/shipping/request-timeout.ts"],"sourcesContent":["/**\n * Bounds the default shipping-simulation SDK request (docs/storefront-core/reference-free-particle-model.md § Read side — data).\n *\n * The default `useShippingSimulation` / `useCartShippingSimulation` callbacks run an imperative\n * `client.query` with no time bound, so a logistics transport that never settles leaves the\n * simulator Root stuck on `loading` forever. `withRequestTimeout` races the request against a finite\n * timer: when the timer wins it aborts the in-flight request (via the `AbortSignal` threaded into\n * `context.fetchOptions`) and rejects, which each hook's existing `catch` collapses to the same\n * `UNKNOWN` userError as any other failure — so the timeout reuses the established error path and the\n * Root leaves `loading` for `error`.\n *\n * The abort is best-effort: it cancels the network request only when the Apollo link chain honors the\n * signal (`HttpLink` does; `MockLink` in tests does not). Settlement never depends on it — the timer\n * race guarantees the returned promise resolves or rejects within `timeoutMs` regardless.\n */\n\n// A generous safety net: well above any healthy round-trip and above the resolver's own upstream\n// budget, so it fires only on a genuine hang, not on a merely slow-but-healthy response.\nexport const SHIPPING_SIMULATION_TIMEOUT_MS = 10_000;\n\nexport class ShippingSimulationTimeoutError extends Error {\n constructor() {\n super('Shipping simulation request timed out.');\n this.name = 'ShippingSimulationTimeoutError';\n }\n}\n\nexport function withRequestTimeout<T>(\n run: (signal: AbortSignal) => Promise<T>,\n // Defaults to the shared constant; no runtime caller overrides it today. Kept as the injection\n // point for a future per-account configurable timeout, once a per-account config source exists.\n timeoutMs: number = SHIPPING_SIMULATION_TIMEOUT_MS,\n): Promise<T> {\n const controller = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n controller.abort();\n reject(new ShippingSimulationTimeoutError());\n }, timeoutMs);\n });\n\n // `run` is invoked inside an async wrapper so a synchronous throw becomes a rejection the race can\n // observe. Called bare, it would escape before `finally` is attached, leaving the timer armed and\n // the timeout's own rejection unhandled.\n const request = (async () => run(controller.signal))();\n\n return Promise.race([request, timeout]).finally(() => {\n clearTimeout(timer);\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,MAAa,iCAAiC;AAE9C,IAAa,iCAAb,cAAoD,MAAM;CACxD,cAAc;EACZ,MAAM,wCAAwC;EAC9C,KAAK,OAAO;CACd;AACF;AAEA,SAAgB,mBACd,KAGA,YAAoB,gCACR;CACZ,MAAM,aAAa,IAAI,gBAAgB;CACvC,IAAI;CAEJ,MAAM,UAAU,IAAI,SAAgB,GAAG,WAAW;EAChD,QAAQ,iBAAiB;GACvB,WAAW,MAAM;GACjB,OAAO,IAAI,+BAA+B,CAAC;EAC7C,GAAG,SAAS;CACd,CAAC;CAKD,MAAM,WAAW,YAAY,IAAI,WAAW,MAAM,EAAA,CAAG;CAErD,OAAO,QAAQ,KAAK,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,cAAc;EACpD,aAAa,KAAK;CACpB,CAAC;AACH"}
|
|
@@ -2,7 +2,7 @@ import "../cart/operations.js";
|
|
|
2
2
|
import { TypedDocumentNode } from "@apollo/client";
|
|
3
3
|
//#region src/sdk/shipping/shipping-operations.d.ts
|
|
4
4
|
/**
|
|
5
|
-
* Shipping address mutation documents (
|
|
5
|
+
* Shipping address mutation documents (docs/storefront-core/reference-free-particle-model.md § The cart write contract).
|
|
6
6
|
*
|
|
7
7
|
* Authored with the hand `gql` tag — the codegen `graphql()` factory is reserved for the reactive
|
|
8
8
|
* read path (`operations.ts`) — and explicitly excluded from the codegen `documents` glob (see
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shipping-operations.js","names":[],"sources":["../../../src/sdk/shipping/shipping-operations.ts"],"sourcesContent":["import type { TypedDocumentNode } from '@apollo/client';\nimport { type CartEntity, CartFragment } from '../cart/operations.js';\nimport { gql } from '../graphql-tag.js';\n\n/**\n * Shipping address mutation documents (
|
|
1
|
+
{"version":3,"file":"shipping-operations.js","names":[],"sources":["../../../src/sdk/shipping/shipping-operations.ts"],"sourcesContent":["import type { TypedDocumentNode } from '@apollo/client';\nimport { type CartEntity, CartFragment } from '../cart/operations.js';\nimport { gql } from '../graphql-tag.js';\n\n/**\n * Shipping address mutation documents (docs/storefront-core/reference-free-particle-model.md § The cart write contract).\n *\n * Authored with the hand `gql` tag — the codegen `graphql()` factory is reserved for the reactive\n * read path (`operations.ts`) — and explicitly excluded from the codegen `documents` glob (see\n * `codegen.ts`): the `gql` tag alone is still plucked by `graphql-codegen`, which would duplicate\n * these hand-written types in `sdk/generated/`. The mutation stays hand-typed as a\n * `TypedDocumentNode` (Decision 3 in the spec). The payload projects the full refreshed `shipping`\n * and `cart` state, so the response write into the cache *is* the post-update refresh: `Shipping`'s\n * reactive read flips with no refetch and no imperative cache write.\n *\n * Failures follow ADR-00007's two-path contract: domain outcomes arrive as payload `userErrors`\n * (`ShippingAddressUpdateError`); transport and top-level GraphQL failures are infrastructure,\n * mapped by the handlers in `use-shipping-actions.ts` to the one client-synthesized code,\n * `NETWORK_USER_ERROR`.\n */\n\n/** A typed address-update failure; NETWORK_USER_ERROR is the one client-synthesized code. */\nexport type ShippingAddressUpdateErrorEntity = Readonly<{ code: string; message: string }>;\n\n/**\n * The one client-synthesized error code, carried by the userError that `setAddress` *resolves*\n * (never throws) when the mutation fails at the transport/top-level-GraphQL layer. Consumers use\n * it to route the resolved synthetic failure to the generic upstream error state (Decision 5),\n * distinct from the domain `userErrors` codes that render inline.\n */\nexport const SHIPPING_NETWORK_ERROR_CODE = 'NETWORK_ERROR';\n\nexport type ShippingAddressInput = Readonly<{\n savedAddressId?: string;\n recipientName: string;\n postalCode: string;\n country: string;\n street?: string;\n number?: string;\n complement?: string;\n neighborhood?: string;\n city?: string;\n state?: string;\n}>;\n\n/** The full shippingAddressUpdate mutation payload. */\nexport type ShippingAddressUpdatePayloadEntity = Readonly<{\n __typename: 'ShippingAddressUpdatePayload';\n shipping: {\n __typename: 'Shipping';\n id: string;\n address: {\n __typename: 'Address';\n id?: string | null;\n recipientName?: string | null;\n postalCode?: string | null;\n country: string;\n street?: string | null;\n number?: string | null;\n complement?: string | null;\n neighborhood?: string | null;\n city?: string | null;\n state?: string | null;\n } | null;\n savedAddresses: ReadonlyArray<{\n __typename: 'Address';\n id?: string | null;\n recipientName?: string | null;\n postalCode?: string | null;\n country: string;\n street?: string | null;\n number?: string | null;\n complement?: string | null;\n neighborhood?: string | null;\n city?: string | null;\n state?: string | null;\n }>;\n options: ReadonlyArray<{\n __typename: 'ShippingOption';\n id: string;\n name: string;\n channel: string;\n shippingEstimate: { __typename: 'ShippingEstimate'; value: number; unit: string };\n price: { __typename: 'Money'; amount: number; currencyCode: string };\n listPrice: { __typename: 'Money'; amount: number; currencyCode: string };\n }>;\n selectedOptionId?: string | null;\n } | null;\n cart: CartEntity;\n userErrors: readonly ShippingAddressUpdateErrorEntity[];\n}>;\n\ntype ShippingAddressUpdateData = Readonly<{ shippingAddressUpdate: ShippingAddressUpdatePayloadEntity }>;\n\nexport const SHIPPING_ADDRESS_UPDATE: TypedDocumentNode<ShippingAddressUpdateData, { input: ShippingAddressInput }> =\n gql`\n mutation ShippingAddressUpdate($input: ShippingAddressUpdateInput!) {\n shippingAddressUpdate(input: $input) {\n shipping {\n __typename\n id\n address {\n __typename\n id\n recipientName\n postalCode\n country\n street\n number\n complement\n neighborhood\n city\n state\n }\n savedAddresses {\n __typename\n id\n recipientName\n postalCode\n country\n street\n number\n complement\n neighborhood\n city\n state\n }\n options {\n __typename\n id\n name\n channel\n shippingEstimate {\n value\n unit\n }\n price {\n amount\n currencyCode\n }\n listPrice {\n amount\n currencyCode\n }\n }\n selectedOptionId\n }\n cart {\n ...CartFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n ${CartFragment}\n`;\n"],"mappings":";;;;;;;;;AA8BA,MAAa,8BAA8B;AAgE3C,MAAa,0BACX,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA6DD,aAAa"}
|
|
@@ -4,9 +4,9 @@ import { ShippingAddressInput, ShippingAddressUpdateErrorEntity } from "./shippi
|
|
|
4
4
|
//#region src/sdk/shipping/use-shipping-actions.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* The `shipping` action namespace — the sanctioned client write surface against the VTEX orderForm
|
|
7
|
-
* shippingData: delivery-address updates (
|
|
7
|
+
* shippingData: delivery-address updates (docs/storefront-core/reference-free-particle-model.md § The cart write contract;
|
|
8
8
|
* docs/storefront-core/adr-0010-actions-own-the-write-side.md) and cart-scoped delivery-option
|
|
9
|
-
* selection (
|
|
9
|
+
* selection (docs/storefront-core/reference-free-particle-model.md § The cart write contract). Per Decision 4 the two tasks share this one
|
|
10
10
|
* namespace — T2 (`setAddress`) merged first and owns the registration, T3 extends it with
|
|
11
11
|
* `selectOption` here.
|
|
12
12
|
*
|
|
@@ -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 (
|
|
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":";;;;;;;;AAmCA,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,KAAK;GACzE,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,KAAK;GAC1E,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"}
|
package/dist/sdk/state/cart.d.ts
CHANGED
|
@@ -3,8 +3,7 @@ export declare const orderFormIdVar: import("@apollo/client").ReactiveVar<string
|
|
|
3
3
|
/**
|
|
4
4
|
* Latest `userErrors` from any cart write, surfaced context-free. The cart action hook publishes
|
|
5
5
|
* here on every call (cleared synchronously at the start of the next call) and `CartError` reads it
|
|
6
|
-
* via `useReactiveVar`, decoupling error display from `CartRoot`'s data context (
|
|
7
|
-
* FR-9). Stays in client state — never a query variable or request header.
|
|
6
|
+
* via `useReactiveVar`, decoupling error display from `CartRoot`'s data context (docs/storefront-core/reference-free-particle-model.md § The cart write contract). Stays in client state — never a query variable or request header.
|
|
8
7
|
*/
|
|
9
8
|
export declare const cartUserErrorsVar: import("@apollo/client").ReactiveVar<readonly Readonly<{
|
|
10
9
|
code: string;
|
|
@@ -15,7 +14,7 @@ export declare const cartUserErrorsVar: import("@apollo/client").ReactiveVar<rea
|
|
|
15
14
|
* error and network-catch branches leave it untouched. `CartOpenOnAdd` and `CartRedirectOnAdd` key
|
|
16
15
|
* a `useEffect` on it via `useReactiveVar`, so every successful add re-fires their behavior even when
|
|
17
16
|
* the drawer is already open or was just closed — a boolean would not re-fire on an unchanged value
|
|
18
|
-
* (
|
|
17
|
+
* (docs/storefront-core/reference-free-particle-model.md § Read side — state). Client state only — never a query variable
|
|
19
18
|
* or request header.
|
|
20
19
|
*/
|
|
21
20
|
export declare const cartLastAddVar: import("@apollo/client").ReactiveVar<number>;
|
|
@@ -30,7 +29,7 @@ export declare const miniCartOpenVar: import("@apollo/client").ReactiveVar<boole
|
|
|
30
29
|
/**
|
|
31
30
|
* Latest `userErrors` from a coupon write (`applyCoupon` / `removeCoupon`), surfaced context-free.
|
|
32
31
|
* A dedicated channel mirroring `cartUserErrorsVar` so coupon feedback sits next to the coupon form
|
|
33
|
-
* and never collides with item-write errors (
|
|
32
|
+
* and never collides with item-write errors (docs/storefront-core/reference-free-particle-model.md § The cart write contract). The coupon
|
|
34
33
|
* actions publish here on every call (cleared at the start of the next call) and `CartCouponError`
|
|
35
34
|
* reads it via `useReactiveVar`. Stays in client state — never a query variable or request header.
|
|
36
35
|
*/
|
package/dist/sdk/state/cart.js
CHANGED
|
@@ -4,8 +4,7 @@ const orderFormIdVar = makeVar(null);
|
|
|
4
4
|
/**
|
|
5
5
|
* Latest `userErrors` from any cart write, surfaced context-free. The cart action hook publishes
|
|
6
6
|
* here on every call (cleared synchronously at the start of the next call) and `CartError` reads it
|
|
7
|
-
* via `useReactiveVar`, decoupling error display from `CartRoot`'s data context (
|
|
8
|
-
* FR-9). Stays in client state — never a query variable or request header.
|
|
7
|
+
* via `useReactiveVar`, decoupling error display from `CartRoot`'s data context (docs/storefront-core/reference-free-particle-model.md § The cart write contract). Stays in client state — never a query variable or request header.
|
|
9
8
|
*/
|
|
10
9
|
const cartUserErrorsVar = makeVar([]);
|
|
11
10
|
/**
|
|
@@ -13,7 +12,7 @@ const cartUserErrorsVar = makeVar([]);
|
|
|
13
12
|
* error and network-catch branches leave it untouched. `CartOpenOnAdd` and `CartRedirectOnAdd` key
|
|
14
13
|
* a `useEffect` on it via `useReactiveVar`, so every successful add re-fires their behavior even when
|
|
15
14
|
* the drawer is already open or was just closed — a boolean would not re-fire on an unchanged value
|
|
16
|
-
* (
|
|
15
|
+
* (docs/storefront-core/reference-free-particle-model.md § Read side — state). Client state only — never a query variable
|
|
17
16
|
* or request header.
|
|
18
17
|
*/
|
|
19
18
|
const cartLastAddVar = makeVar(0);
|
|
@@ -28,13 +27,13 @@ const miniCartOpenVar = makeVar(false);
|
|
|
28
27
|
/**
|
|
29
28
|
* Latest `userErrors` from a coupon write (`applyCoupon` / `removeCoupon`), surfaced context-free.
|
|
30
29
|
* A dedicated channel mirroring `cartUserErrorsVar` so coupon feedback sits next to the coupon form
|
|
31
|
-
* and never collides with item-write errors (
|
|
30
|
+
* and never collides with item-write errors (docs/storefront-core/reference-free-particle-model.md § The cart write contract). The coupon
|
|
32
31
|
* actions publish here on every call (cleared at the start of the next call) and `CartCouponError`
|
|
33
32
|
* reads it via `useReactiveVar`. Stays in client state — never a query variable or request header.
|
|
34
33
|
*/
|
|
35
34
|
const cartCouponErrorsVar = makeVar([]);
|
|
36
35
|
/**
|
|
37
|
-
* Count of cart writes currently in flight (
|
|
36
|
+
* Count of cart writes currently in flight (docs/storefront-core/reference-free-particle-model.md § The cart write contract). Every mutating
|
|
38
37
|
* handler in the cart action namespace registers for the lifetime of its call, so `> 0` means a
|
|
39
38
|
* write is outstanding. The stock-validation open call reads this to skip while a write is running —
|
|
40
39
|
* that write's own response already carries current availability, so re-validating would be
|
|
@@ -45,7 +44,7 @@ const cartWritesInFlightVar = makeVar(0);
|
|
|
45
44
|
/**
|
|
46
45
|
* Epoch-ms timestamp of when the client last received current availability (`0` = never). Stamped on
|
|
47
46
|
* a successful `validate` and on every successful cart write, because a write's response already
|
|
48
|
-
* carries recomputed availability (
|
|
47
|
+
* carries recomputed availability (docs/storefront-core/reference-cart.md § Validation). The
|
|
49
48
|
* stock-validation open call compares `now - cartAvailabilityFreshAtVar()` against
|
|
50
49
|
* `CART_VALIDATE_STALENESS_MS` and skips while inside the window, so reopening the overlay — or
|
|
51
50
|
* opening it right after an add — does not re-validate. Client state only.
|
|
@@ -54,7 +53,7 @@ const cartAvailabilityFreshAtVar = makeVar(0);
|
|
|
54
53
|
/**
|
|
55
54
|
* The staleness window for the stock-validation open call, in milliseconds. The single tuning knob:
|
|
56
55
|
* the platform's rate limits under real traffic are not known yet, so this value is a starting point,
|
|
57
|
-
* not a contract (
|
|
56
|
+
* not a contract (docs/storefront-core/reference-cart.md § Validation). The pre-checkout call ignores it and always
|
|
58
57
|
* runs.
|
|
59
58
|
*/
|
|
60
59
|
const CART_VALIDATE_STALENESS_MS = 3e4;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cart.js","names":[],"sources":["../../../src/sdk/state/cart.ts"],"sourcesContent":["import { makeVar } from '@apollo/client';\nimport type { CartUserError } from '../cart/operations.js';\n\n// Session-scoped cart identifier (`Cart.id` from the cart query response). Client state only —\n// never sent as a query variable or request header; the x-order-form-id cookie carries cart\n// context on the transport. Starts null; hydrated when the cart entity arrives, which opens the\n// cart action ready gate. See
|
|
1
|
+
{"version":3,"file":"cart.js","names":[],"sources":["../../../src/sdk/state/cart.ts"],"sourcesContent":["import { makeVar } from '@apollo/client';\nimport type { CartUserError } from '../cart/operations.js';\n\n// Session-scoped cart identifier (`Cart.id` from the cart query response). Client state only —\n// never sent as a query variable or request header; the x-order-form-id cookie carries cart\n// context on the transport. Starts null; hydrated when the cart entity arrives, which opens the\n// cart action ready gate. See docs/storefront-core/reference-cart.md § Cart context; docs/storefront-core/reference-free-particle-model.md § The cart write contract.\nexport const orderFormIdVar = makeVar<string | null>(null);\n\n/**\n * Latest `userErrors` from any cart write, surfaced context-free. The cart action hook publishes\n * here on every call (cleared synchronously at the start of the next call) and `CartError` reads it\n * via `useReactiveVar`, decoupling error display from `CartRoot`'s data context (docs/storefront-core/reference-free-particle-model.md § The cart write contract). Stays in client state — never a query variable or request header.\n */\nexport const cartUserErrorsVar = makeVar<readonly CartUserError[]>([]);\n\n/**\n * Monotonic add-success pulse. `addItem` increments it on each successful add (no userErrors); the\n * error and network-catch branches leave it untouched. `CartOpenOnAdd` and `CartRedirectOnAdd` key\n * a `useEffect` on it via `useReactiveVar`, so every successful add re-fires their behavior even when\n * the drawer is already open or was just closed — a boolean would not re-fire on an unchanged value\n * (docs/storefront-core/reference-free-particle-model.md § Read side — state). Client state only — never a query variable\n * or request header.\n */\nexport const cartLastAddVar = makeVar<number>(0);\n\n/**\n * The mini-cart's open state, surfaced context-free. `CartOpenOnAdd` reads it via `useReactiveVar`\n * to control the `DrawerRoot` and writes it back through `onOpenChange` so the header trigger and\n * manual close (Escape / backdrop / close button) stay in sync. The single source of truth for the\n * controlled drawer; `addItem` never writes it. Client state only — never a query variable or\n * request header.\n */\nexport const miniCartOpenVar = makeVar<boolean>(false);\n\n/**\n * Latest `userErrors` from a coupon write (`applyCoupon` / `removeCoupon`), surfaced context-free.\n * A dedicated channel mirroring `cartUserErrorsVar` so coupon feedback sits next to the coupon form\n * and never collides with item-write errors (docs/storefront-core/reference-free-particle-model.md § The cart write contract). The coupon\n * actions publish here on every call (cleared at the start of the next call) and `CartCouponError`\n * reads it via `useReactiveVar`. Stays in client state — never a query variable or request header.\n */\nexport const cartCouponErrorsVar = makeVar<readonly CartUserError[]>([]);\n\n/**\n * Count of cart writes currently in flight (docs/storefront-core/reference-free-particle-model.md § The cart write contract). Every mutating\n * handler in the cart action namespace registers for the lifetime of its call, so `> 0` means a\n * write is outstanding. The stock-validation open call reads this to skip while a write is running —\n * that write's own response already carries current availability, so re-validating would be\n * redundant. `validate` itself is read-only upstream and never registers here. Client state only —\n * never a query variable or request header.\n */\nexport const cartWritesInFlightVar = makeVar<number>(0);\n\n/**\n * Epoch-ms timestamp of when the client last received current availability (`0` = never). Stamped on\n * a successful `validate` and on every successful cart write, because a write's response already\n * carries recomputed availability (docs/storefront-core/reference-cart.md § Validation). The\n * stock-validation open call compares `now - cartAvailabilityFreshAtVar()` against\n * `CART_VALIDATE_STALENESS_MS` and skips while inside the window, so reopening the overlay — or\n * opening it right after an add — does not re-validate. Client state only.\n */\nexport const cartAvailabilityFreshAtVar = makeVar<number>(0);\n\n/**\n * The staleness window for the stock-validation open call, in milliseconds. The single tuning knob:\n * the platform's rate limits under real traffic are not known yet, so this value is a starting point,\n * not a contract (docs/storefront-core/reference-cart.md § Validation). The pre-checkout call ignores it and always\n * runs.\n */\nexport const CART_VALIDATE_STALENESS_MS = 30_000;\n"],"mappings":";;AAOA,MAAa,iBAAiB,QAAuB,IAAI;;;;;;AAOzD,MAAa,oBAAoB,QAAkC,CAAC,CAAC;;;;;;;;;AAUrE,MAAa,iBAAiB,QAAgB,CAAC;;;;;;;;AAS/C,MAAa,kBAAkB,QAAiB,KAAK;;;;;;;;AASrD,MAAa,sBAAsB,QAAkC,CAAC,CAAC;;;;;;;;;AAUvE,MAAa,wBAAwB,QAAgB,CAAC;;;;;;;;;AAUtD,MAAa,6BAA6B,QAAgB,CAAC;;;;;;;AAQ3D,MAAa,6BAA6B"}
|
package/dist/utils/safe-href.js
CHANGED
|
@@ -7,9 +7,9 @@ const ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set([
|
|
|
7
7
|
]);
|
|
8
8
|
/**
|
|
9
9
|
* The only gate on a CMS-authored destination: `http:`, `https:`, `mailto:`, `tel:`, and paths
|
|
10
|
-
* starting with a single `/`.
|
|
11
|
-
*
|
|
12
|
-
* between it and an `href`.
|
|
10
|
+
* starting with a single `/`. The runtime slice does not enforce a model's `pattern`, and the sukima
|
|
11
|
+
* preflight that does never sees content published after a deploy, so an authored `javascript:` value
|
|
12
|
+
* can reach the component intact and this is what stands between it and an `href`.
|
|
13
13
|
*
|
|
14
14
|
* Rejects rather than sanitizes: a destination that comes back safe is character-for-character the
|
|
15
15
|
* trimmed authored value, so no value is ever silently rewritten into a different working one.
|