diff --git a/.changeset/pos-validation-target-grammar.md b/.changeset/pos-validation-target-grammar.md new file mode 100644 index 0000000000..6c1343a976 --- /dev/null +++ b/.changeset/pos-validation-target-grammar.md @@ -0,0 +1,5 @@ +--- +'@shopify/ui-extensions': minor +--- + +Add types for POS intercept `ValidationAdd.target` values (`$.cart`, `$.cart.lineItems['']`, `$.payment`), narrowed per intercepted event. diff --git a/packages/ui-extensions/src/surfaces/point-of-sale/events.ts b/packages/ui-extensions/src/surfaces/point-of-sale/events.ts index 1bd42d4af4..6662608fad 100644 --- a/packages/ui-extensions/src/surfaces/point-of-sale/events.ts +++ b/packages/ui-extensions/src/surfaces/point-of-sale/events.ts @@ -113,10 +113,66 @@ export interface PaymentValidationsEvent extends Event { readonly amount: MoneyV2; } +/** + * Targets the whole cart rather than a specific line item. + * + * @private + */ +export type CartTarget = '$.cart'; + +/** + * Targets one cart line item by its `uuid` from this event's `cart` snapshot, + * for example `$.cart.lineItems['adfd6b06-4a24-4f5f-9f4b-ea21f4432dd4']`. + * + * @private + */ +export type CartLineItemTarget = `$.cart.lineItems['${string}']`; + +/** @private */ +export type CartValidationTarget = CartTarget | CartLineItemTarget; + +/** + * Targets the payment attempt being intercepted. + * + * @private + */ +export type PaymentTarget = '$.payment'; + +/** @private */ +export type PaymentValidationTarget = PaymentTarget; + +/** + * Where a validation applies, as an enumerated token. Targets are matched + * as exact strings, never evaluated as JSON paths. + * + * @private + */ +export type ValidationTarget = CartValidationTarget | PaymentValidationTarget; + +/** + * Maps POS interceptable workflow names to their valid validation targets. + * + * @private + */ +interface ValidationTargetMap { + [POS_INTERCEPT_NAMES.CART_VALIDATIONS]: CartValidationTarget; + [POS_INTERCEPT_NAMES.PAYMENT_VALIDATIONS]: PaymentValidationTarget; +} + +/** + * The validation targets valid for a given intercepted event. + * + * @private + */ +export type ValidationTargetFor = + TEvent['type'] extends keyof ValidationTargetMap + ? ValidationTargetMap[TEvent['type']] + : ValidationTarget; + /** @private */ export type ShopifyInterceptor = ( event: TEvent, -) => InterceptResult; +) => InterceptResult>; /** * The result an interceptor returns. An empty `operations` list allows the @@ -124,8 +180,10 @@ export type ShopifyInterceptor = ( * * @private */ -export interface InterceptResult { - operations: Operation[]; +export interface InterceptResult< + TTarget extends ValidationTarget = ValidationTarget, +> { + operations: Operation[]; } /** @@ -133,8 +191,10 @@ export interface InterceptResult { * * @private */ -export interface Operation { - validationAdd?: ValidationAdd; +export interface Operation< + TTarget extends ValidationTarget = ValidationTarget, +> { + validationAdd?: ValidationAdd; } /** @private */ @@ -145,15 +205,31 @@ export type ValidationLevel = 'WARNING' | 'ERROR'; * * @private */ -export interface ValidationAdd { +export interface ValidationAdd< + TTarget extends ValidationTarget = ValidationTarget, +> { /** `ERROR` blocks the workflow. `WARNING` does not. */ level: ValidationLevel; - /** Stable identifier for this validation. */ + /** + * Stable identifier for this validation. Handles are namespaced per + * extension and may repeat across targets: the same handle on two line + * items is two validations. + */ handle: string; - /** JSON-path locator for where the validation applies. */ - target?: string; + /** + * Locates the data the validation applies to; the host decides where it + * renders. Omitted or unrecognized targets fall back to the event's root + * scope (`$.cart` / `$.payment`) — the validation still applies, rendered + * less specifically. + * + * Line item uuids are only valid within the event that delivered them: + * use `lineItems[n].uuid` from this event's cart snapshot, don't cache + * uuids across events. Bundle components are not addressable; target + * their parent line. + */ + target?: TTarget; } export type {