From 3f20b39916e7e88fa8f8e8b7d60b2db5d5561ce7 Mon Sep 17 00:00:00 2001 From: "Christian V. Dahl" <31840711+cvd2412@users.noreply.github.com> Date: Wed, 15 Jul 2026 20:23:33 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=A4=96=20Merge=20PR=20#75262=20Clerk=20io?= =?UTF-8?q?=20fix=20attribute=20inferral=20by=20@cvd2412?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- types/clerk.io/clerk.io-tests.ts | 15 ++++ types/clerk.io/index.d.ts | 75 +++++++++++------ types/clerk.io/types/response.d.ts | 129 +++++++++++++++++++---------- 3 files changed, 149 insertions(+), 70 deletions(-) diff --git a/types/clerk.io/clerk.io-tests.ts b/types/clerk.io/clerk.io-tests.ts index 0a37ec972db500..834341d3b1de81 100644 --- a/types/clerk.io/clerk.io-tests.ts +++ b/types/clerk.io/clerk.io-tests.ts @@ -48,4 +48,19 @@ if (window.Clerk) { facets: null, status: "ok", }; + + // Test that `attributes` narrows the keys of `product_data` in the response + window.Clerk("call", "search/search", { + key: "test-key", + query: "test", + limit: 5, + attributes: ["sku", "title"], + }, (response) => { + if (response.product_data) { + response.product_data[0].sku; // $ExpectType unknown + response.product_data[0].title; // $ExpectType unknown + // @ts-expect-error + response.product_data[0].description; + } + }); } diff --git a/types/clerk.io/index.d.ts b/types/clerk.io/index.d.ts index 7fec3b791ab0d2..918194dea4f63e 100644 --- a/types/clerk.io/index.d.ts +++ b/types/clerk.io/index.d.ts @@ -1,3 +1,4 @@ +// Minimum TypeScript Version: 5.0 import * as Config from "./types/config"; import * as Helpers from "./types/helpers"; import * as Response from "./types/response"; @@ -81,44 +82,64 @@ export interface ConfigTypes { "recommendations/page/related_categories": Config.recommendationsPageRelatedCategoriesConfig; } -export interface ResponseTypes { - "search/search": Response.searchSearchResponse; - "search/predictive": Response.searchPredictiveResponse; +export interface ResponseTypes { + "search/search": Response.searchSearchResponse; + "search/predictive": Response.searchPredictiveResponse; "search/suggestions": Response.searchSuggestionsResponse; "search/categories": Response.searchCategoriesResponse; "search/pages": Response.searchPagesResponse; "search/popular": Response.searchPopularResponse; - "recommendations/popular": Response.recommendationsPopularResponse; - "recommendations/trending": Response.recommendationsTrendingResponse; - "recommendations/new": Response.recommendationsNewResponse; - "recommendations/currently_watched": Response.recommendationsCurrentlyWatchedResponse; - "recommendations/recently_bought": Response.recommendationsRecentlyBoughtResponse; - "recommendations/keywords": Response.recommendationsKeywordsResponse; - "recommendations/complementary": Response.recommendationsComplementaryResponse; - "recommendations/substituting": Response.recommendationsSubstitutingResponse; - "recommendations/most_sold_with": Response.recommendationsMostSoldWithResponse; - "recommendations/category/popular": Response.recommendationsCategoryPopularResponse; - "recommendations/category/trending": Response.recommendationsCategoryTrendingResponse; - "recommendations/category/new": Response.recommendationsCategoryNewResponse; - "recommendations/category/popular_subcategories": Response.recommendationsCategoryPopularSubcategoriesResponse; - "recommendations/visitor/history": Response.recommendationsVisitorHistoryResponse; - "recommendations/visitor/complementary": Response.recommendationsVisitorComplementaryResponse; - "recommendations/visitor/substituting": Response.recommendationsVisitorSubstitutingResponse; - "recommendations/customer/history": Response.recommendationsCustomerHistoryResponse; - "recommendations/customer/complementary": Response.recommendationsCustomerComplementaryResponse; - "recommendations/customer/substituting": Response.recommendationsCustomerSubstitutingResponse; + "recommendations/popular": Response.recommendationsPopularResponse; + "recommendations/trending": Response.recommendationsTrendingResponse; + "recommendations/new": Response.recommendationsNewResponse; + "recommendations/currently_watched": Response.recommendationsCurrentlyWatchedResponse; + "recommendations/recently_bought": Response.recommendationsRecentlyBoughtResponse; + "recommendations/keywords": Response.recommendationsKeywordsResponse; + "recommendations/complementary": Response.recommendationsComplementaryResponse; + "recommendations/substituting": Response.recommendationsSubstitutingResponse; + "recommendations/most_sold_with": Response.recommendationsMostSoldWithResponse; + "recommendations/category/popular": Response.recommendationsCategoryPopularResponse; + "recommendations/category/trending": Response.recommendationsCategoryTrendingResponse; + "recommendations/category/new": Response.recommendationsCategoryNewResponse; + "recommendations/category/popular_subcategories": Response.recommendationsCategoryPopularSubcategoriesResponse< + Attributes + >; + "recommendations/visitor/history": Response.recommendationsVisitorHistoryResponse; + "recommendations/visitor/complementary": Response.recommendationsVisitorComplementaryResponse; + "recommendations/visitor/substituting": Response.recommendationsVisitorSubstitutingResponse; + "recommendations/customer/history": Response.recommendationsCustomerHistoryResponse; + "recommendations/customer/complementary": Response.recommendationsCustomerComplementaryResponse; + "recommendations/customer/substituting": Response.recommendationsCustomerSubstitutingResponse; "recommendations/page/substituting": Response.recommendationsPageSubstitutingResponse; - "recommendations/page/product": Response.recommendationsPageProductResponse; - "recommendations/page/category": Response.recommendationsPageCategoryResponse; - "recommendations/page/related_products": Response.recommendationsPageRelatedProductsResponse; - "recommendations/page/related_categories": Response.recommendationsPageRelatedCategoriesResponse; + "recommendations/page/product": Response.recommendationsPageProductResponse; + "recommendations/page/category": Response.recommendationsPageCategoryResponse; + "recommendations/page/related_products": Response.recommendationsPageRelatedProductsResponse; + "recommendations/page/related_categories": Response.recommendationsPageRelatedCategoriesResponse; } +/** + * @description The subset of endpoints whose config accepts an `attributes` field. + */ +export type EndpointsWithAttributes = { + [K in ClerkEndpoints]: ConfigTypes[K] extends { attributes?: string[] } ? K : never; +}[ClerkEndpoints]; + +/** + * @see https://docs.clerk.io/docs/clerkjs-custom-api-calls + * @description Calls a Clerk.js endpoint, narrowing `product_data` in the response to the literal tuple `Attributes` + */ +export function Clerk( + method: "call", + endpoint: T, + config: Omit & { attributes?: Attributes }, + callback?: (response: ResponseTypes[T]) => void, + error?: (error: ClerkErrorResponse) => void, +): void; /** * @see https://docs.clerk.io/docs/clerkjs-custom-api-calls * @description Calls a Clerk.js endpoint */ -export function Clerk( +export function Clerk>( method: "call", endpoint: T, config: ConfigTypes[T], diff --git a/types/clerk.io/types/response.d.ts b/types/clerk.io/types/response.d.ts index cc1c2d38963ec8..f944df780def32 100644 --- a/types/clerk.io/types/response.d.ts +++ b/types/clerk.io/types/response.d.ts @@ -7,11 +7,11 @@ export interface BaseResponse { debug?: Record; } -export type BaseProductResponse = BaseResponse & { - product_data?: PickAttributes[]; +export type BaseProductResponse = BaseResponse & { + product_data?: PickAttributes[]; }; -export type BaseCountedResponse = BaseProductResponse & { +export type BaseCountedResponse = BaseProductResponse & { count: number; facets: unknown | null; }; @@ -44,13 +44,15 @@ export interface Page { } // Search responses -export type searchSearchResponse = BaseCountedResponse; +export type searchSearchResponse = BaseCountedResponse; -export type searchPredictiveResponse = BaseProductResponse & { - facets: unknown | null; - hits: number; - query: string; -}; +export type searchPredictiveResponse = + & BaseProductResponse + & { + facets: unknown | null; + hits: number; + query: string; + }; export type searchSuggestionsResponse = BaseResponse; @@ -66,34 +68,67 @@ export type searchPagesResponse = BaseResponse & { export type searchPopularResponse = BaseResponse; // Recommendations responses -export type recommendationsPopularResponse = BaseCountedResponse; -export type recommendationsTrendingResponse = BaseCountedResponse; -export type recommendationsNewResponse = BaseCountedResponse; -export type recommendationsCurrentlyWatchedResponse = BaseProductResponse; -export type recommendationsRecentlyBoughtResponse = BaseProductResponse; -export type recommendationsKeywordsResponse = BaseProductResponse; -export type recommendationsComplementaryResponse = BaseProductResponse; -export type recommendationsSubstitutingResponse = BaseProductResponse; -export type recommendationsMostSoldWithResponse = BaseCountedResponse; +export type recommendationsPopularResponse = BaseCountedResponse; +export type recommendationsTrendingResponse = BaseCountedResponse< + Attributes +>; +export type recommendationsNewResponse = BaseCountedResponse; +export type recommendationsCurrentlyWatchedResponse = BaseProductResponse< + Attributes +>; +export type recommendationsRecentlyBoughtResponse = BaseProductResponse< + Attributes +>; +export type recommendationsKeywordsResponse = BaseProductResponse< + Attributes +>; +export type recommendationsComplementaryResponse = BaseProductResponse< + Attributes +>; +export type recommendationsSubstitutingResponse = BaseProductResponse< + Attributes +>; +export type recommendationsMostSoldWithResponse = BaseCountedResponse< + Attributes +>; // Category endpoints -export type recommendationsCategoryPopularResponse = BaseCountedResponse; -export type recommendationsCategoryTrendingResponse = BaseProductResponse & { - count: number; - facets?: unknown | null; -}; -export type recommendationsCategoryNewResponse = BaseCountedResponse; -export type recommendationsCategoryPopularSubcategoriesResponse = BaseCountedResponse; +export type recommendationsCategoryPopularResponse = BaseCountedResponse< + Attributes +>; +export type recommendationsCategoryTrendingResponse = + & BaseProductResponse + & { + count: number; + facets?: unknown | null; + }; +export type recommendationsCategoryNewResponse = BaseCountedResponse< + Attributes +>; +export type recommendationsCategoryPopularSubcategoriesResponse = + BaseCountedResponse; // Visitor endpoints -export type recommendationsVisitorHistoryResponse = BaseProductResponse; -export type recommendationsVisitorComplementaryResponse = BaseProductResponse; -export type recommendationsVisitorSubstitutingResponse = BaseProductResponse; +export type recommendationsVisitorHistoryResponse = BaseProductResponse< + Attributes +>; +export type recommendationsVisitorComplementaryResponse = BaseProductResponse< + Attributes +>; +export type recommendationsVisitorSubstitutingResponse = BaseProductResponse< + Attributes +>; // Customer endpoints -export type recommendationsCustomerHistoryResponse = BaseProductResponse; -export type recommendationsCustomerComplementaryResponse = BaseProductResponse; -export type recommendationsCustomerSubstitutingResponse = BaseProductResponse; +export type recommendationsCustomerHistoryResponse = BaseProductResponse< + Attributes +>; +export type recommendationsCustomerComplementaryResponse = BaseProductResponse< + Attributes +>; +export type recommendationsCustomerSubstitutingResponse = BaseProductResponse< + Attributes +>; // Page endpoints export type recommendationsPageSubstitutingResponse = BaseResponse & { @@ -101,15 +136,23 @@ export type recommendationsPageSubstitutingResponse = BaseResponse & { product_data?: null[]; }; -export type recommendationsPageProductResponse = BaseProductResponse & { - pages: Page[]; -}; - -export type recommendationsPageCategoryResponse = BaseProductResponse & { - pages: Page[]; -}; - -export type recommendationsPageRelatedProductsResponse = BaseCountedResponse; -export type recommendationsPageRelatedCategoriesResponse = BaseCountedResponse & { - categories: Category[]; -}; +export type recommendationsPageProductResponse = + & BaseProductResponse + & { + pages: Page[]; + }; + +export type recommendationsPageCategoryResponse = + & BaseProductResponse + & { + pages: Page[]; + }; + +export type recommendationsPageRelatedProductsResponse = BaseCountedResponse< + Attributes +>; +export type recommendationsPageRelatedCategoriesResponse = + & BaseCountedResponse + & { + categories: Category[]; + };