From 187fc0bc8fa241b976296091140d0fd0b0960295 Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Thu, 31 Oct 2024 16:17:14 -0400 Subject: [PATCH 01/30] recs server-side working --- .../commerce-engine/commerce-engine.ssr.ts | 51 +++++++++++++---- .../src/app/commerce-ssr-engine/common.ts | 35 ++++++++++++ .../app/search-engine/search-engine.ssr.ts | 57 ++++++++++++++++--- .../headless/src/app/ssr-engine/common.ts | 6 +- .../src/app/ssr-engine/types/common.ts | 2 +- .../ssr-engine/types/hydrate-static-state.ts | 2 +- .../headless-recommendations.ssr.ts | 3 +- .../app/_components/pages/listing-page.tsx | 5 +- 8 files changed, 132 insertions(+), 29 deletions(-) diff --git a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts index dc32ca921ee..b3ff825c307 100644 --- a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts +++ b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts @@ -7,7 +7,10 @@ import {buildProductListing} from '../../controllers/commerce/product-listing/he import {buildSearch} from '../../controllers/commerce/search/headless-search.js'; import type {Controller} from '../../controllers/controller/headless-controller.js'; import {createWaitForActionMiddleware} from '../../utils/utils.js'; -import {buildControllerDefinitions} from '../commerce-ssr-engine/common.js'; +import { + buildControllerDefinitions, + buildRecommendationFilter, +} from '../commerce-ssr-engine/common.js'; import { ControllerDefinitionsMap, InferControllerStaticStateMapFromDefinitionsWithSolutionType, @@ -37,7 +40,7 @@ export interface SSRCommerceEngine extends CommerceEngine { /** * Waits for the search to be completed and returns a promise that resolves to a `SearchCompletedAction`. */ - waitForRequestCompletedAction(): Promise; + waitForRequestCompletedAction(): Promise[]; } export type CommerceEngineDefinitionOptions< @@ -56,13 +59,20 @@ function isSearchCompletedAction(action: unknown): action is Action { ); } +function isRecommendationCompletedAction(action: unknown): action is Action { + return /^commerce\/recommendations\/fetch\/(fulfilled|rejected)$/.test( + (action as UnknownAction).type + ); +} + function noSearchActionRequired(_action: unknown): _action is Action { return true; } function buildSSRCommerceEngine( solutionType: SolutionType, - options: CommerceEngineOptions + options: CommerceEngineOptions, + recommendationCount: number ): SSRCommerceEngine { let actionCompletionMiddleware: ReturnType< typeof createWaitForActionMiddleware @@ -85,11 +95,17 @@ function buildSSRCommerceEngine( ); } + const recommendationActionMiddlewares = Array.from( + {length: recommendationCount}, + () => createWaitForActionMiddleware(isRecommendationCompletedAction) + ); + const commerceEngine = buildCommerceEngine({ ...options, middlewares: [ ...(options.middlewares ?? []), actionCompletionMiddleware.middleware, + ...recommendationActionMiddlewares.map(({middleware}) => middleware), ], }); @@ -101,7 +117,10 @@ function buildSSRCommerceEngine( }, waitForRequestCompletedAction() { - return actionCompletionMiddleware.promise; + return [ + actionCompletionMiddleware.promise, + ...recommendationActionMiddlewares.map(({promise}) => promise), + ]; }, }; } @@ -163,6 +182,10 @@ export function defineCommerceEngine< type HydrateStaticStateFromBuildResultParameters = Parameters; + const recommendationHelper = buildRecommendationFilter( + controllerDefinitions ?? {} + ); + const getOptions = () => { return engineOptions; }; @@ -180,7 +203,8 @@ export function defineCommerceEngine< solutionType, buildOptions?.extend ? await buildOptions.extend(getOptions()) - : getOptions() + : getOptions(), + recommendationHelper.count ); const controllers = buildControllerDefinitions({ definitionsMap: (controllerDefinitions ?? {}) as TControllerDefinitions, @@ -232,10 +256,14 @@ export function defineCommerceEngine< buildSearch(engine).executeFirstSearch(); } - const searchAction = await engine.waitForRequestCompletedAction(); + recommendationHelper.refresh(controllers); + + const searchActions = await Promise.all( + engine.waitForRequestCompletedAction() + ); return createStaticState({ - searchAction, + searchActions, controllers, }) as EngineStaticState< UnknownAction, @@ -266,7 +294,7 @@ export function defineCommerceEngine< solutionType ).fromBuildResult({ buildResult, - searchAction: params[0]!.searchAction, + searchActions: params[0]!.searchActions, }); return staticState; }, @@ -277,10 +305,13 @@ export function defineCommerceEngine< const [ { buildResult: {engine, controllers}, - searchAction, + searchActions, }, ] = params; - engine.dispatch(searchAction); + + searchActions.forEach((action) => { + engine.dispatch(action); + }); await engine.waitForRequestCompletedAction(); return {engine, controllers}; }, diff --git a/packages/headless/src/app/commerce-ssr-engine/common.ts b/packages/headless/src/app/commerce-ssr-engine/common.ts index 2b8907bf678..e6a72dede23 100644 --- a/packages/headless/src/app/commerce-ssr-engine/common.ts +++ b/packages/headless/src/app/commerce-ssr-engine/common.ts @@ -1,3 +1,4 @@ +import {Recommendations} from '../../controllers/commerce/recommendations/headless-recommendations.js'; import {Controller} from '../../controllers/controller/headless-controller.js'; import {InvalidControllerDefinition} from '../../utils/errors.js'; import {filterObject, mapObject} from '../../utils/utils.js'; @@ -103,3 +104,37 @@ export function ensureAtLeastOneSolutionType( throw new InvalidControllerDefinition(); } } +export function buildRecommendationFilter< + TEngine extends CoreEngine | CoreEngineNext, + TControllerDefinitions extends ControllerDefinitionsMap, +>(controllerDefinitions: TControllerDefinitions) { + const keys = Object.entries(controllerDefinitions) + .filter(([_, value]) => 'isRecs' in value && value.isRecs) + .map(([key, _]) => key); + + return { + /** + * Gets the number of recommendation controllers from the controller definitions map. + * + * @returns {number} The number of recommendation controllers in the controller definition map + */ + get count() { + return keys.length; + }, + + /** + * Go through all the controllers passed in argument and only refresh recommendation controllers. + * + * @param controllers - A record of all controllers where the key is the controller name and the value is the controller instance. + */ + refresh(controllers: Record) { + const isRecommendationController = (key: string) => keys.includes(key); + + Object.entries(controllers) + .filter(([key, _]) => isRecommendationController(key)) + .forEach(([_, controller]) => + (controller as Recommendations).refresh?.() + ); + }, + }; +} diff --git a/packages/headless/src/app/search-engine/search-engine.ssr.ts b/packages/headless/src/app/search-engine/search-engine.ssr.ts index b1361574c7e..f14b5191c6f 100644 --- a/packages/headless/src/app/search-engine/search-engine.ssr.ts +++ b/packages/headless/src/app/search-engine/search-engine.ssr.ts @@ -5,6 +5,7 @@ import {UnknownAction} from '@reduxjs/toolkit'; import type {Controller} from '../../controllers/controller/headless-controller.js'; import {LegacySearchAction} from '../../features/analytics/analytics-utils.js'; import {createWaitForActionMiddleware} from '../../utils/utils.js'; +import {buildRecommendationFilter} from '../commerce-ssr-engine/common.js'; import {NavigatorContextProvider} from '../navigatorContextProvider.js'; import { buildControllerDefinitions, @@ -36,7 +37,7 @@ export interface SSRSearchEngine extends SearchEngine { /** * Waits for the search to be completed and returns a promise that resolves to a `SearchCompletedAction`. */ - waitForSearchCompletedAction(): Promise; + waitForSearchCompletedAction(): Promise[]; } /** @@ -60,13 +61,34 @@ function isSearchCompletedAction( ); } -function buildSSRSearchEngine(options: SearchEngineOptions): SSRSearchEngine { +function isRecommendationCompletedAction( + action: unknown +): action is SearchCompletedAction { + return /^recommendation\/get\/(fulfilled|rejected)$/.test( + (action as UnknownAction).type + ); +} + +function buildSSRSearchEngine( + options: SearchEngineOptions, + recommendationCount: number +): SSRSearchEngine { const {middleware, promise} = createWaitForActionMiddleware( isSearchCompletedAction ); + + const recommendationActionMiddlewares = Array.from( + {length: recommendationCount}, + () => createWaitForActionMiddleware(isRecommendationCompletedAction) + ); + const searchEngine = buildSearchEngine({ ...options, - middlewares: [...(options.middlewares ?? []), middleware], + middlewares: [ + ...(options.middlewares ?? []), + middleware, + ...recommendationActionMiddlewares.map(({middleware}) => middleware), + ], }); return { ...searchEngine, @@ -74,7 +96,10 @@ function buildSSRSearchEngine(options: SearchEngineOptions): SSRSearchEngine { return searchEngine.state; }, waitForSearchCompletedAction() { - return promise; + return [ + promise, + ...recommendationActionMiddlewares.map(({promise}) => promise), + ]; }, }; } @@ -121,6 +146,10 @@ export function defineSearchEngine< type HydrateStaticStateFromBuildResultParameters = Parameters; + const recommendationHelper = buildRecommendationFilter( + controllerDefinitions ?? {} + ); + const getOptions = () => { return engineOptions; }; @@ -135,7 +164,8 @@ export function defineSearchEngine< const engine = buildSSRSearchEngine( buildOptions?.extend ? await buildOptions.extend(getOptions()) - : getOptions() + : getOptions(), + recommendationHelper.count ); const controllers = buildControllerDefinitions({ definitionsMap: (controllerDefinitions ?? {}) as TControllerDefinitions, @@ -175,8 +205,14 @@ export function defineSearchEngine< ] = params; engine.executeFirstSearch(); + recommendationHelper.refresh(controllers); + + const searchActions = await Promise.all( + engine.waitForSearchCompletedAction() + ); + return createStaticState({ - searchAction: await engine.waitForSearchCompletedAction(), + searchActions, controllers, }) as EngineStaticState< UnknownAction, @@ -197,7 +233,7 @@ export function defineSearchEngine< const buildResult = await build(...(params as BuildParameters)); const staticState = await hydrateStaticState.fromBuildResult({ buildResult, - searchAction: params[0]!.searchAction, + searchActions: params[0]!.searchActions, }); return staticState; }, @@ -208,10 +244,13 @@ export function defineSearchEngine< const [ { buildResult: {engine, controllers}, - searchAction, + searchActions, }, ] = params; - engine.dispatch(searchAction); + + searchActions.forEach((action) => { + engine.dispatch(action); + }); await engine.waitForSearchCompletedAction(); return {engine, controllers}; }, diff --git a/packages/headless/src/app/ssr-engine/common.ts b/packages/headless/src/app/ssr-engine/common.ts index 26b0503f1dd..79eef38ef33 100644 --- a/packages/headless/src/app/ssr-engine/common.ts +++ b/packages/headless/src/app/ssr-engine/common.ts @@ -58,10 +58,10 @@ export function buildControllerDefinitions< } export function createStaticState({ - searchAction, + searchActions, controllers, }: { - searchAction: TSearchAction; + searchActions: TSearchAction[]; controllers: ControllersMap; }): EngineStaticState< TSearchAction, @@ -71,7 +71,7 @@ export function createStaticState({ controllers: mapObject(controllers, (controller) => ({ state: clone(controller.state), })) as InferControllerStaticStateMapFromControllers, - searchAction: clone(searchAction), + searchActions: searchActions.map((action) => clone(action)), }; } diff --git a/packages/headless/src/app/ssr-engine/types/common.ts b/packages/headless/src/app/ssr-engine/types/common.ts index 831b23ec423..853beb17cc5 100644 --- a/packages/headless/src/app/ssr-engine/types/common.ts +++ b/packages/headless/src/app/ssr-engine/types/common.ts @@ -105,7 +105,7 @@ export interface EngineStaticState< TSearchAction extends UnknownAction, TControllers extends ControllerStaticStateMap, > { - searchAction: TSearchAction; + searchActions: TSearchAction[]; controllers: TControllers; } diff --git a/packages/headless/src/app/ssr-engine/types/hydrate-static-state.ts b/packages/headless/src/app/ssr-engine/types/hydrate-static-state.ts index f7164fa0932..9f4a265badb 100644 --- a/packages/headless/src/app/ssr-engine/types/hydrate-static-state.ts +++ b/packages/headless/src/app/ssr-engine/types/hydrate-static-state.ts @@ -10,7 +10,7 @@ import { import {FromBuildResult} from './from-build-result.js'; export interface HydrateStaticStateOptions { - searchAction: TSearchAction; + searchActions: TSearchAction[]; } export type HydrateStaticState< diff --git a/packages/headless/src/controllers/commerce/recommendations/headless-recommendations.ssr.ts b/packages/headless/src/controllers/commerce/recommendations/headless-recommendations.ssr.ts index 2996ef6e9de..b0335ba6e90 100644 --- a/packages/headless/src/controllers/commerce/recommendations/headless-recommendations.ssr.ts +++ b/packages/headless/src/controllers/commerce/recommendations/headless-recommendations.ssr.ts @@ -20,11 +20,12 @@ export interface RecommendationsDefinition * */ export function defineRecommendations( props: RecommendationsProps -): RecommendationsDefinition { +): RecommendationsDefinition & {isRecs: true} { return { search: true, listing: true, standalone: true, + isRecs: true, build: (engine) => buildRecommendations(engine, props), }; } diff --git a/packages/samples/headless-ssr-commerce/app/_components/pages/listing-page.tsx b/packages/samples/headless-ssr-commerce/app/_components/pages/listing-page.tsx index 05612968ed2..73e7718fa58 100644 --- a/packages/samples/headless-ssr-commerce/app/_components/pages/listing-page.tsx +++ b/packages/samples/headless-ssr-commerce/app/_components/pages/listing-page.tsx @@ -34,13 +34,10 @@ export default function ListingPage({ useEffect(() => { listingEngineDefinition .hydrateStaticState({ - searchAction: staticState.searchAction, + searchActions: staticState.searchActions, }) .then(({engine, controllers}) => { setHydratedState({engine, controllers}); - // Refreshing recommendations in the browser after hydrating the state in the client-side - // Recommendation refresh in the server is not supported yet. - controllers.popularBoughtRecs.refresh(); }); }, [staticState]); From d571439c51d5b64e0b7591cea1f6ed64f82fe5da Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Thu, 31 Oct 2024 21:25:12 -0400 Subject: [PATCH 02/30] recognize search action promises --- .../commerce-engine/commerce-engine.ssr.ts | 18 +++++++--- packages/headless/src/utils/utils.ts | 36 +++++++++++++++++++ .../app/_components/pages/listing-page.tsx | 4 +++ .../app/_components/pages/product-page.tsx | 2 +- .../app/_components/pages/recommendation.tsx | 2 +- .../app/_components/pages/search-page.tsx | 2 +- 6 files changed, 56 insertions(+), 8 deletions(-) diff --git a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts index b3ff825c307..cb12cf95f40 100644 --- a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts +++ b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts @@ -6,7 +6,10 @@ import {stateKey} from '../../app/state-key.js'; import {buildProductListing} from '../../controllers/commerce/product-listing/headless-product-listing.js'; import {buildSearch} from '../../controllers/commerce/search/headless-search.js'; import type {Controller} from '../../controllers/controller/headless-controller.js'; -import {createWaitForActionMiddleware} from '../../utils/utils.js'; +import { + createWaitForActionMiddleware, + createWaitForActionMiddlewareForRecommendation, +} from '../../utils/utils.js'; import { buildControllerDefinitions, buildRecommendationFilter, @@ -95,9 +98,14 @@ function buildSSRCommerceEngine( ); } + const memo: Set = new Set(); const recommendationActionMiddlewares = Array.from( {length: recommendationCount}, - () => createWaitForActionMiddleware(isRecommendationCompletedAction) + () => + createWaitForActionMiddlewareForRecommendation( + isRecommendationCompletedAction, + memo + ) ); const commerceEngine = buildCommerceEngine({ @@ -182,7 +190,7 @@ export function defineCommerceEngine< type HydrateStaticStateFromBuildResultParameters = Parameters; - const recommendationHelper = buildRecommendationFilter( + const recommendationFilter = buildRecommendationFilter( controllerDefinitions ?? {} ); @@ -204,7 +212,7 @@ export function defineCommerceEngine< buildOptions?.extend ? await buildOptions.extend(getOptions()) : getOptions(), - recommendationHelper.count + recommendationFilter.count ); const controllers = buildControllerDefinitions({ definitionsMap: (controllerDefinitions ?? {}) as TControllerDefinitions, @@ -256,7 +264,7 @@ export function defineCommerceEngine< buildSearch(engine).executeFirstSearch(); } - recommendationHelper.refresh(controllers); + recommendationFilter.refresh(controllers); const searchActions = await Promise.all( engine.waitForRequestCompletedAction() diff --git a/packages/headless/src/utils/utils.ts b/packages/headless/src/utils/utils.ts index 92af128c4d1..4e6a52ce460 100644 --- a/packages/headless/src/utils/utils.ts +++ b/packages/headless/src/utils/utils.ts @@ -157,3 +157,39 @@ export function createWaitForActionMiddleware( return {promise, middleware}; } + +export function createWaitForActionMiddlewareForRecommendation< + TAction extends Action, +>( + isDesiredAction: (action: unknown) => action is TAction, + memo: Set +): {promise: Promise; middleware: Middleware} { + const {promise, resolve} = createDeferredPromise(); + let resolved = false; + + const middleware: Middleware = () => (next) => (action) => { + next(action); + // [x] Should not resolve the same action more than once + // [x] Do not resolve a recommendation action if it is in the slot id + if ( + isDesiredAction(action) && + // TODO: merge these two conditions + !resolved && + !memo.has( + (action as unknown as {meta: {arg: {slotId: string}}})?.meta?.arg + ?.slotId + ) //TODO:: this will not work for non recommendation action + ) { + resolved = true; + // TODO: fix this type casting + memo.add( + (action as unknown as {meta: {arg: {slotId: string}}})?.meta?.arg + ?.slotId + ); + console.log(' --- isDesiredAction ---', action); + resolve(action); + } + }; + + return {promise, middleware}; +} diff --git a/packages/samples/headless-ssr-commerce/app/_components/pages/listing-page.tsx b/packages/samples/headless-ssr-commerce/app/_components/pages/listing-page.tsx index 73e7718fa58..48f800bfa95 100644 --- a/packages/samples/headless-ssr-commerce/app/_components/pages/listing-page.tsx +++ b/packages/samples/headless-ssr-commerce/app/_components/pages/listing-page.tsx @@ -107,6 +107,10 @@ export default function ListingPage({ staticState={staticState.controllers.popularBoughtRecs.state} controller={hydratedState?.controllers.popularBoughtRecs} /> + diff --git a/packages/samples/headless-ssr-commerce/app/_components/pages/product-page.tsx b/packages/samples/headless-ssr-commerce/app/_components/pages/product-page.tsx index d964f2b9b08..733217d24f1 100644 --- a/packages/samples/headless-ssr-commerce/app/_components/pages/product-page.tsx +++ b/packages/samples/headless-ssr-commerce/app/_components/pages/product-page.tsx @@ -36,7 +36,7 @@ export default function ProductPage(props: IProductPageProps) { useEffect(() => { standaloneEngineDefinition .hydrateStaticState({ - searchAction: staticState.searchAction, + searchActions: staticState.searchActions, }) .then(({engine, controllers}) => { setHydratedState({engine, controllers}); diff --git a/packages/samples/headless-ssr-commerce/app/_components/pages/recommendation.tsx b/packages/samples/headless-ssr-commerce/app/_components/pages/recommendation.tsx index 55d7ce66d52..90ffd1d4600 100644 --- a/packages/samples/headless-ssr-commerce/app/_components/pages/recommendation.tsx +++ b/packages/samples/headless-ssr-commerce/app/_components/pages/recommendation.tsx @@ -28,7 +28,7 @@ export default function Recommendation({ useEffect(() => { standaloneEngineDefinition .hydrateStaticState({ - searchAction: staticState.searchAction, + searchActions: staticState.searchActions, }) .then(({engine, controllers}) => { setHydratedState({engine, controllers}); diff --git a/packages/samples/headless-ssr-commerce/app/_components/pages/search-page.tsx b/packages/samples/headless-ssr-commerce/app/_components/pages/search-page.tsx index 3e5d7644bcb..61cff84da1c 100644 --- a/packages/samples/headless-ssr-commerce/app/_components/pages/search-page.tsx +++ b/packages/samples/headless-ssr-commerce/app/_components/pages/search-page.tsx @@ -33,7 +33,7 @@ export default function SearchPage({ useEffect(() => { searchEngineDefinition .hydrateStaticState({ - searchAction: staticState.searchAction, + searchActions: staticState.searchActions, }) .then(({engine, controllers}) => { setHydratedState({engine, controllers}); From 4dc2717481e6cf03edf130618ed87335f6ff05a3 Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Fri, 1 Nov 2024 10:29:18 -0400 Subject: [PATCH 03/30] add typeguard --- packages/headless/src/utils/utils.ts | 41 +++++++++++-------- .../app/_lib/commerce-engine-config.ts | 8 ++++ 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/packages/headless/src/utils/utils.ts b/packages/headless/src/utils/utils.ts index 4e6a52ce460..badd60b0401 100644 --- a/packages/headless/src/utils/utils.ts +++ b/packages/headless/src/utils/utils.ts @@ -1,4 +1,5 @@ -import {Middleware, Action} from '@reduxjs/toolkit'; +import {Middleware, Action, PayloadAction} from '@reduxjs/toolkit'; +import {FetchRecommendationsPayload} from '../features/commerce/recommendations/recommendations-actions.js'; export const randomID = (prepend?: string, length = 5) => prepend + @@ -158,35 +159,41 @@ export function createWaitForActionMiddleware( return {promise, middleware}; } +function isRecommendationActionPayload

( + action: unknown +): action is PayloadAction { + // TODO: clean that thing!! + if (typeof action === 'object' && action !== null && 'meta' in action) { + return ( + (action as PayloadAction).meta + ?.arg?.slotId !== undefined + ); + } + return false; +} + export function createWaitForActionMiddlewareForRecommendation< TAction extends Action, >( isDesiredAction: (action: unknown) => action is TAction, memo: Set + //TODO:: this will not work for non recommendation action ): {promise: Promise; middleware: Middleware} { const {promise, resolve} = createDeferredPromise(); - let resolved = false; + let hasBeenResolved = false; + const hasSlotBeenProcessed = (slotId: string) => memo.has(slotId); const middleware: Middleware = () => (next) => (action) => { next(action); - // [x] Should not resolve the same action more than once - // [x] Do not resolve a recommendation action if it is in the slot id + if ( isDesiredAction(action) && - // TODO: merge these two conditions - !resolved && - !memo.has( - (action as unknown as {meta: {arg: {slotId: string}}})?.meta?.arg - ?.slotId - ) //TODO:: this will not work for non recommendation action + !hasBeenResolved && + isRecommendationActionPayload(action) && + !hasSlotBeenProcessed(action.meta.arg.slotId) ) { - resolved = true; - // TODO: fix this type casting - memo.add( - (action as unknown as {meta: {arg: {slotId: string}}})?.meta?.arg - ?.slotId - ); - console.log(' --- isDesiredAction ---', action); + hasBeenResolved = true; + memo.add(action.meta.arg.slotId); resolve(action); } }; diff --git a/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine-config.ts b/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine-config.ts index ce281375e97..e236754b8c3 100644 --- a/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine-config.ts +++ b/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine-config.ts @@ -53,6 +53,14 @@ export default { slotId: 'af4fb7ba-6641-4b67-9cf9-be67e9f30174', }, }), + // TODO: check for invalid slotId + // TODO: check for duplicate slotId + // TODO: encounter for multiple recommendations with same slot id + // popwularBoughtRecs: defineRecommendations({ + // options: { + // slotId: 'af4fb7ba-6641-4b67-9cf9-be67e9f30172', + // }, + // }), cart: defineCart(), searchBox: defineSearchBox(), context: defineContext(), From 2e5f79451e5948e11efe6f907fcf2be6360a33c6 Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Fri, 1 Nov 2024 16:29:04 -0400 Subject: [PATCH 04/30] prevent multiple recommendations with the same slot --- .../src/app/commerce-ssr-engine/common.ts | 24 ++++++++++++++++--- .../headless-recommendations.ssr.ts | 9 +++++-- packages/headless/src/utils/utils.ts | 10 ++++++++ .../app/_lib/commerce-engine-config.ts | 10 ++++++-- 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/packages/headless/src/app/commerce-ssr-engine/common.ts b/packages/headless/src/app/commerce-ssr-engine/common.ts index e6a72dede23..a103fdf0546 100644 --- a/packages/headless/src/app/commerce-ssr-engine/common.ts +++ b/packages/headless/src/app/commerce-ssr-engine/common.ts @@ -108,9 +108,27 @@ export function buildRecommendationFilter< TEngine extends CoreEngine | CoreEngineNext, TControllerDefinitions extends ControllerDefinitionsMap, >(controllerDefinitions: TControllerDefinitions) { - const keys = Object.entries(controllerDefinitions) - .filter(([_, value]) => 'isRecs' in value && value.isRecs) - .map(([key, _]) => key); + const seenSlotIds = new Set(); + const filtered = Object.entries(controllerDefinitions).filter( + ([_, value]) => { + if ('isRecs' in value && value.isRecs) { + const slotId = (value as unknown as {slotId: string}).slotId; // TODO: fix type and CLEAN THAT! + // TODO: use a combination of slotId and productId name to identify the controller + if (seenSlotIds.has(slotId)) { + console.log( + 'WARNING: Multiple recommendation controllers found for the same slotId', + slotId + ); + return false; + } else { + seenSlotIds.add(slotId); + return true; + } + } + } + ); + + const keys = filtered.map(([key, _]) => key); return { /** diff --git a/packages/headless/src/controllers/commerce/recommendations/headless-recommendations.ssr.ts b/packages/headless/src/controllers/commerce/recommendations/headless-recommendations.ssr.ts index b0335ba6e90..3dcafd6118c 100644 --- a/packages/headless/src/controllers/commerce/recommendations/headless-recommendations.ssr.ts +++ b/packages/headless/src/controllers/commerce/recommendations/headless-recommendations.ssr.ts @@ -20,12 +20,17 @@ export interface RecommendationsDefinition * */ export function defineRecommendations( props: RecommendationsProps -): RecommendationsDefinition & {isRecs: true} { +): RecommendationsDefinition & { + isRecs: true; +} & RecommendationsProps['options'] { + // TODO: have an extended recommendationDefinition that is not exposed return { search: true, listing: true, standalone: true, - isRecs: true, + // TODO: encapsulate into a single object called meta (e.g. meta: {isRecs: true, ...props.options}) + isRecs: true, // TODO: mark internal + ...props.options, // TODO: mark internal build: (engine) => buildRecommendations(engine, props), }; } diff --git a/packages/headless/src/utils/utils.ts b/packages/headless/src/utils/utils.ts index badd60b0401..ee2ec13dd87 100644 --- a/packages/headless/src/utils/utils.ts +++ b/packages/headless/src/utils/utils.ts @@ -186,12 +186,22 @@ export function createWaitForActionMiddlewareForRecommendation< const middleware: Middleware = () => (next) => (action) => { next(action); + // if (isDesiredAction(action) && isRecommendationActionPayload(action)) { + // console.log(''); + // console.log('slotId:', action.meta.arg.slotId); + // console.log( + // 'condition: ', + // hasBeenResolved, + // memo.has(action.meta.arg.slotId) + // ); + // } if ( isDesiredAction(action) && !hasBeenResolved && isRecommendationActionPayload(action) && !hasSlotBeenProcessed(action.meta.arg.slotId) ) { + // console.log(' --- RESOLVE ---'); hasBeenResolved = true; memo.add(action.meta.arg.slotId); resolve(action); diff --git a/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine-config.ts b/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine-config.ts index e236754b8c3..2598a2b4d54 100644 --- a/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine-config.ts +++ b/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine-config.ts @@ -53,8 +53,14 @@ export default { slotId: 'af4fb7ba-6641-4b67-9cf9-be67e9f30174', }, }), - // TODO: check for invalid slotId - // TODO: check for duplicate slotId + popularBoughtRecs_DUPLICATE: defineRecommendations({ + // TODO: support option to run only on specific + options: { + slotId: 'af4fb7ba-6641-4b67-9cf9-be67e9f30174', + }, + }), + // TODO: [x] check for invalid slotId => it will reject as expected + // TODO: [ ] check for duplicate slotId // TODO: encounter for multiple recommendations with same slot id // popwularBoughtRecs: defineRecommendations({ // options: { From de430495d723f33f333f9422cae35292e02b4e3b Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Sun, 3 Nov 2024 07:05:24 -0500 Subject: [PATCH 05/30] clean --- .../src/app/commerce-ssr-engine/common.ts | 49 ++++++++++++------- .../headless-recommendations.ssr.ts | 18 ++++--- 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/packages/headless/src/app/commerce-ssr-engine/common.ts b/packages/headless/src/app/commerce-ssr-engine/common.ts index a103fdf0546..6c3d8cb039d 100644 --- a/packages/headless/src/app/commerce-ssr-engine/common.ts +++ b/packages/headless/src/app/commerce-ssr-engine/common.ts @@ -1,4 +1,5 @@ import {Recommendations} from '../../controllers/commerce/recommendations/headless-recommendations.js'; +import {RecommendationsDefinitionMeta} from '../../controllers/commerce/recommendations/headless-recommendations.ssr.js'; import {Controller} from '../../controllers/controller/headless-controller.js'; import {InvalidControllerDefinition} from '../../utils/errors.js'; import {filterObject, mapObject} from '../../utils/utils.js'; @@ -104,31 +105,45 @@ export function ensureAtLeastOneSolutionType( throw new InvalidControllerDefinition(); } } + export function buildRecommendationFilter< TEngine extends CoreEngine | CoreEngineNext, TControllerDefinitions extends ControllerDefinitionsMap, >(controllerDefinitions: TControllerDefinitions) { - const seenSlotIds = new Set(); + const slotIdSet = new Set(); + + const isRecommendationDefinition = < + C extends ControllerDefinition, + >( + controller: C + ): controller is C & RecommendationsDefinitionMeta => { + return '_recommendationProps' in controller; + }; + + const warnDuplicateRecommendation = (slotId: string, productId?: string) => { + console.warn( + 'Multiple recommendation controllers found for the same slotId and productId', + {slotId, productId} + ); + }; + const filtered = Object.entries(controllerDefinitions).filter( ([_, value]) => { - if ('isRecs' in value && value.isRecs) { - const slotId = (value as unknown as {slotId: string}).slotId; // TODO: fix type and CLEAN THAT! - // TODO: use a combination of slotId and productId name to identify the controller - if (seenSlotIds.has(slotId)) { - console.log( - 'WARNING: Multiple recommendation controllers found for the same slotId', - slotId - ); - return false; - } else { - seenSlotIds.add(slotId); - return true; - } + if (!isRecommendationDefinition(value)) { + return false; + } + const {slotId, productId} = value._recommendationProps; + const key = `${slotId}${productId || ''}`; + if (slotIdSet.has(key)) { + warnDuplicateRecommendation(slotId, productId); + return false; } + slotIdSet.add(key); + return true; } ); - const keys = filtered.map(([key, _]) => key); + const name = filtered.map(([name, _]) => name); return { /** @@ -137,7 +152,7 @@ export function buildRecommendationFilter< * @returns {number} The number of recommendation controllers in the controller definition map */ get count() { - return keys.length; + return name.length; }, /** @@ -146,7 +161,7 @@ export function buildRecommendationFilter< * @param controllers - A record of all controllers where the key is the controller name and the value is the controller instance. */ refresh(controllers: Record) { - const isRecommendationController = (key: string) => keys.includes(key); + const isRecommendationController = (key: string) => name.includes(key); Object.entries(controllers) .filter(([key, _]) => isRecommendationController(key)) diff --git a/packages/headless/src/controllers/commerce/recommendations/headless-recommendations.ssr.ts b/packages/headless/src/controllers/commerce/recommendations/headless-recommendations.ssr.ts index 3dcafd6118c..542426a2377 100644 --- a/packages/headless/src/controllers/commerce/recommendations/headless-recommendations.ssr.ts +++ b/packages/headless/src/controllers/commerce/recommendations/headless-recommendations.ssr.ts @@ -8,6 +8,13 @@ import { export type {Recommendations, RecommendationsState}; +/** + * @internal + * */ +export type RecommendationsDefinitionMeta = { + _recommendationProps: {} & RecommendationsProps['options']; +}; + export interface RecommendationsDefinition extends UniversalControllerDefinitionWithoutProps {} /** @@ -20,17 +27,14 @@ export interface RecommendationsDefinition * */ export function defineRecommendations( props: RecommendationsProps -): RecommendationsDefinition & { - isRecs: true; -} & RecommendationsProps['options'] { - // TODO: have an extended recommendationDefinition that is not exposed +): RecommendationsDefinition & RecommendationsDefinitionMeta { return { search: true, listing: true, standalone: true, - // TODO: encapsulate into a single object called meta (e.g. meta: {isRecs: true, ...props.options}) - isRecs: true, // TODO: mark internal - ...props.options, // TODO: mark internal + _recommendationProps: { + ...props.options, + }, build: (engine) => buildRecommendations(engine, props), }; } From 9254b93383efb6b5fe79fe53d4ac1e0f4453e548 Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Thu, 14 Nov 2024 10:38:58 -0500 Subject: [PATCH 06/30] draft --- .../commerce-engine/commerce-engine.ssr.ts | 113 +++++++++++++----- .../src/app/commerce-ssr-engine/common.ts | 4 +- .../app/commerce-ssr-engine/types/common.ts | 21 ++++ .../commerce-ssr-engine/types/core-engine.ts | 59 ++++++--- .../src/app/ssr-engine/types/build.ts | 18 +++ .../ssr-engine/types/fetch-static-state.ts | 29 +++++ .../headless-recommendations.ssr.ts | 12 +- 7 files changed, 201 insertions(+), 55 deletions(-) diff --git a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts index cb12cf95f40..ed2714de111 100644 --- a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts +++ b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts @@ -81,16 +81,31 @@ function buildSSRCommerceEngine( typeof createWaitForActionMiddleware >; + const middlewares: ReturnType[] = []; + const memo: Set = new Set(); + switch (solutionType) { case SolutionType.listing: actionCompletionMiddleware = createWaitForActionMiddleware( isListingFetchCompletedAction ); + middlewares.push(actionCompletionMiddleware); break; case SolutionType.search: actionCompletionMiddleware = createWaitForActionMiddleware( isSearchCompletedAction ); + middlewares.push(actionCompletionMiddleware); + break; + case SolutionType.recommendation: + middlewares.push( + ...Array.from({length: recommendationCount}, () => + createWaitForActionMiddlewareForRecommendation( + isRecommendationCompletedAction, + memo + ) + ) + ); break; default: actionCompletionMiddleware = createWaitForActionMiddleware( @@ -98,22 +113,11 @@ function buildSSRCommerceEngine( ); } - const memo: Set = new Set(); - const recommendationActionMiddlewares = Array.from( - {length: recommendationCount}, - () => - createWaitForActionMiddlewareForRecommendation( - isRecommendationCompletedAction, - memo - ) - ); - const commerceEngine = buildCommerceEngine({ ...options, middlewares: [ ...(options.middlewares ?? []), - actionCompletionMiddleware.middleware, - ...recommendationActionMiddlewares.map(({middleware}) => middleware), + ...middlewares.map(({middleware}) => middleware), ], }); @@ -125,10 +129,7 @@ function buildSSRCommerceEngine( }, waitForRequestCompletedAction() { - return [ - actionCompletionMiddleware.promise, - ...recommendationActionMiddlewares.map(({promise}) => promise), - ]; + return [...middlewares.map(({promise}) => promise)]; }, }; } @@ -169,6 +170,10 @@ export function defineCommerceEngine< TControllerDefinitions, SolutionType.standalone >; + recommendationEngineDefinition: CommerceEngineDefinition< + TControllerDefinitions, + SolutionType.recommendation + >; } { const {controllers: controllerDefinitions, ...engineOptions} = options; type Definition = CommerceEngineDefinition< @@ -176,7 +181,7 @@ export function defineCommerceEngine< SolutionType >; type BuildFunction = Definition['build']; - type FetchStaticStateFunction = Definition['fetchStaticState']; + type FetchStaticStateFunction = Definition['fetchStaticState']; // TODO: avoir un fetch pour les recommendations type HydrateStaticStateFunction = Definition['hydrateStaticState']; type FetchStaticStateFromBuildResultFunction = FetchStaticStateFunction['fromBuildResult']; @@ -190,9 +195,9 @@ export function defineCommerceEngine< type HydrateStaticStateFromBuildResultParameters = Parameters; - const recommendationFilter = buildRecommendationFilter( - controllerDefinitions ?? {} - ); + // TODO: ideally , we only want to execute that for recommendation stuff + const recommendationFilter = () => + buildRecommendationFilter(controllerDefinitions ?? {}); const getOptions = () => { return engineOptions; @@ -203,7 +208,6 @@ export function defineCommerceEngine< ) => { engineOptions.navigatorContextProvider = navigatorContextProvider; }; - const buildFactory = (solutionType: T) => async (...[buildOptions]: BuildParameters) => { @@ -212,7 +216,10 @@ export function defineCommerceEngine< buildOptions?.extend ? await buildOptions.extend(getOptions()) : getOptions(), - recommendationFilter.count + // TODO: clean that + solutionType === SolutionType.recommendation + ? recommendationFilter().count + : 0 ); const controllers = buildControllerDefinitions({ definitionsMap: (controllerDefinitions ?? {}) as TControllerDefinitions, @@ -258,14 +265,19 @@ export function defineCommerceEngine< }, ] = params; - if (solutionType === SolutionType.listing) { - buildProductListing(engine).executeFirstRequest(); - } else if (solutionType === SolutionType.search) { - buildSearch(engine).executeFirstSearch(); + switch (solutionType) { + case SolutionType.listing: + buildProductListing(engine).executeFirstRequest(); + break; + case SolutionType.search: + buildSearch(engine).executeFirstSearch(); + break; + case SolutionType.recommendation: + recommendationFilter().refresh(controllers); + break; } - recommendationFilter.refresh(controllers); - + // TODO: should be only one searchAction for search and listing const searchActions = await Promise.all( engine.waitForRequestCompletedAction() ); @@ -317,6 +329,7 @@ export function defineCommerceEngine< }, ] = params; + // TODO: should be only one searchAction for search and listing searchActions.forEach((action) => { engine.dispatch(action); }); @@ -325,6 +338,7 @@ export function defineCommerceEngine< }, } ); + return { listingEngineDefinition: { build: buildFactory(SolutionType.listing), @@ -338,6 +352,18 @@ export function defineCommerceEngine< hydrateStaticState: hydrateStaticStateFactory(SolutionType.search), setNavigatorContextProvider, } as CommerceEngineDefinition, + recommendationEngineDefinition: { + build: buildFactory(SolutionType.recommendation), + fetchStaticState: fetchStaticStateFactory(SolutionType.recommendation), + hydrateStaticState: hydrateStaticStateFactory( + SolutionType.recommendation + ), + setNavigatorContextProvider, + } as CommerceEngineDefinition< + TControllerDefinitions, + SolutionType.recommendation + >, + // TODO: make the standaloneEngineDefinition not async since there are no search executed standaloneEngineDefinition: { build: buildFactory(SolutionType.standalone), fetchStaticState: fetchStaticStateFactory(SolutionType.standalone), @@ -349,3 +375,34 @@ export function defineCommerceEngine< >, }; } + +/// Sandbox +// const { +// recommendationEngineDefinition, +// searchEngineDefinition, +// standaloneEngineDefinition, +// } = defineCommerceEngine({ +// configuration: getSampleCommerceEngineConfiguration(), +// controllers: { +// standaloneSearchBox: defineStandaloneSearchBox({ +// options: {redirectionUrl: 'rest'}, +// }), +// facets: defineFacetGenerator(), +// trending: defineRecommendations({ +// options: {slotId: 'ttt'}, +// }), +// popular: defineRecommendations({ +// options: {slotId: 'ppp'}, +// }), +// }, +// }); + +// // TODO: should have a way to select which recommendation to fetch +// const r = await standaloneEngineDefinition.fetchStaticState(); +// r.controllers.standaloneSearchBox; + +// const b = await recommendationEngineDefinition.fetchStaticState(['trending']); +// b.controllers.trending; + +// const a = await searchEngineDefinition.fetchStaticState(); +// a.controllers; // TODO: should throw an error since it's not defined in search diff --git a/packages/headless/src/app/commerce-ssr-engine/common.ts b/packages/headless/src/app/commerce-ssr-engine/common.ts index 6c3d8cb039d..248cb51ad5e 100644 --- a/packages/headless/src/app/commerce-ssr-engine/common.ts +++ b/packages/headless/src/app/commerce-ssr-engine/common.ts @@ -117,7 +117,7 @@ export function buildRecommendationFilter< >( controller: C ): controller is C & RecommendationsDefinitionMeta => { - return '_recommendationProps' in controller; + return 'recommendation' in controller; }; const warnDuplicateRecommendation = (slotId: string, productId?: string) => { @@ -132,7 +132,7 @@ export function buildRecommendationFilter< if (!isRecommendationDefinition(value)) { return false; } - const {slotId, productId} = value._recommendationProps; + const {slotId, productId} = value.options; const key = `${slotId}${productId || ''}`; if (slotIdSet.has(key)) { warnDuplicateRecommendation(slotId, productId); diff --git a/packages/headless/src/app/commerce-ssr-engine/types/common.ts b/packages/headless/src/app/commerce-ssr-engine/types/common.ts index fdf21309c0e..6bbbb3101ba 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/common.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/common.ts @@ -19,6 +19,7 @@ export enum SolutionType { search = 'search', listing = 'listing', standalone = 'standalone', + recommendation = 'recommendation', } export interface ControllerDefinitionWithoutProps< @@ -172,6 +173,13 @@ interface ListingOnlyController { [SolutionType.listing]: true; } +interface RecommendationOnlyController { + /** + * @internal + */ + [SolutionType.recommendation]: true; +} + interface SearchAndListingController { /** * @internal @@ -205,6 +213,17 @@ export type ListingOnlyControllerDefinitionWithProps< > = ControllerDefinitionWithProps & ListingOnlyController; +export type RecommendationOnlyControllerDefinitionWithoutProps< + TController extends Controller, +> = ControllerDefinitionWithoutProps & + RecommendationOnlyController; + +export type RecommendationOnlyControllerDefinitionWithProps< + TController extends Controller, + TProps, +> = ControllerDefinitionWithProps & + RecommendationOnlyController; + export type UniversalControllerDefinitionWithoutProps< TController extends Controller, > = ControllerDefinitionWithoutProps & @@ -233,6 +252,7 @@ export type SubControllerDefinitionWithoutProps< : TDefinition extends {listing: false; search: false} ? InvalidControllerDefinition : never; +// TODO: add recommendation type here export type SubControllerDefinitionWithProps< TController extends Controller, @@ -247,3 +267,4 @@ export type SubControllerDefinitionWithProps< : TDefinition extends {listing: false; search: false} ? InvalidControllerDefinition : never; +// TODO: add recommendation type here diff --git a/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts b/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts index a9d39706164..3ff206497fe 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts @@ -3,9 +3,12 @@ import type {Controller} from '../../../controllers/controller/headless-controll import {EngineConfiguration} from '../../engine-configuration.js'; import {CoreEngine, CoreEngineNext} from '../../engine.js'; import {NavigatorContextProvider} from '../../navigatorContextProvider.js'; -import {Build} from '../../ssr-engine/types/build.js'; +import {Build, BuildWithList} from '../../ssr-engine/types/build.js'; import {InferControllerPropsMapFromDefinitions} from '../../ssr-engine/types/common.js'; -import {FetchStaticState} from '../../ssr-engine/types/fetch-static-state.js'; +import { + FetchStaticState, + FetchStaticStateWithList, +} from '../../ssr-engine/types/fetch-static-state.js'; import {HydrateStaticState} from '../../ssr-engine/types/hydrate-static-state.js'; import { ControllerDefinitionsMap, @@ -37,19 +40,32 @@ export interface EngineDefinition< /** * Fetches the static state on the server side using your engine definition. */ - fetchStaticState: FetchStaticState< - TEngine, - InferControllersMapFromDefinition, - UnknownAction, - InferControllerStaticStateMapFromDefinitionsWithSolutionType< - TControllers, - TSolutionType - >, - InferControllerPropsMapFromDefinitions - >; + // TODO: simplify be removing code duplication + fetchStaticState: TSolutionType extends SolutionType.recommendation + ? FetchStaticStateWithList< + TEngine, + InferControllersMapFromDefinition, + UnknownAction, + InferControllerStaticStateMapFromDefinitionsWithSolutionType< + TControllers, + TSolutionType + >, + InferControllerPropsMapFromDefinitions + > + : FetchStaticState< + TEngine, + InferControllersMapFromDefinition, + UnknownAction, + InferControllerStaticStateMapFromDefinitionsWithSolutionType< + TControllers, + TSolutionType + >, + InferControllerPropsMapFromDefinitions + >; /** * Fetches the hydrated state on the client side using your engine definition and the static state. */ + // TODO: Apply the same logic to recommendation hydration hydrateStaticState: HydrateStaticState< TEngine, InferControllersMapFromDefinition, @@ -59,12 +75,19 @@ export interface EngineDefinition< /** * Builds an engine and its controllers from an engine definition. */ - build: Build< - TEngine, - TEngineOptions, - InferControllersMapFromDefinition, - InferControllerPropsMapFromDefinitions - >; + build: TSolutionType extends SolutionType.recommendation + ? BuildWithList< + TEngine, + TEngineOptions, + InferControllersMapFromDefinition, + InferControllerPropsMapFromDefinitions + > + : Build< + TEngine, + TEngineOptions, + InferControllersMapFromDefinition, + InferControllerPropsMapFromDefinitions + >; /** * Sets the navigator context provider. diff --git a/packages/headless/src/app/ssr-engine/types/build.ts b/packages/headless/src/app/ssr-engine/types/build.ts index e7dcabba4dd..bb6c9eca00b 100644 --- a/packages/headless/src/app/ssr-engine/types/build.ts +++ b/packages/headless/src/app/ssr-engine/types/build.ts @@ -12,6 +12,24 @@ export interface BuildOptions { extend?: OptionsExtender; } +export interface BuildWithList< + TEngine extends CoreEngine | CoreEngineNext, + TEngineOptions, + TControllersMap extends ControllersMap, + TControllersProps extends ControllersPropsMap, +> { + /** + * Initializes an engine and controllers from the definition. + */ + ( + c: (keyof TControllersMap)[], + ...params: OptionsTuple< + BuildOptions & + EngineDefinitionControllersPropsOption + > + ): Promise>; +} + export interface Build< TEngine extends CoreEngine | CoreEngineNext, TEngineOptions, diff --git a/packages/headless/src/app/ssr-engine/types/fetch-static-state.ts b/packages/headless/src/app/ssr-engine/types/fetch-static-state.ts index a1f87a7781a..ad02be7dba8 100644 --- a/packages/headless/src/app/ssr-engine/types/fetch-static-state.ts +++ b/packages/headless/src/app/ssr-engine/types/fetch-static-state.ts @@ -38,3 +38,32 @@ export type FetchStaticState< EngineStaticState >; }; + +// TODO: find a better name +export type FetchStaticStateWithList< + TEngine extends CoreEngine | CoreEngineNext, + TControllers extends ControllersMap, + TSearchAction extends UnknownAction, + TControllersStaticState extends ControllerStaticStateMap, + TControllersProps extends ControllersPropsMap, +> = { + /** + * Executes only the initial search for a given configuration, then returns a resumable snapshot of engine state along with the state of the controllers. + * + * Useful for static generation and server-side rendering. + */ + ( + c: (keyof TControllers)[], + ...params: OptionsTuple< + FetchStaticStateOptions & + EngineDefinitionControllersPropsOption + > + ): Promise>; + + fromBuildResult: FromBuildResult< + TEngine, + TControllers, + FetchStaticStateOptions, + EngineStaticState + >; +}; diff --git a/packages/headless/src/controllers/commerce/recommendations/headless-recommendations.ssr.ts b/packages/headless/src/controllers/commerce/recommendations/headless-recommendations.ssr.ts index 542426a2377..23c6b0162ba 100644 --- a/packages/headless/src/controllers/commerce/recommendations/headless-recommendations.ssr.ts +++ b/packages/headless/src/controllers/commerce/recommendations/headless-recommendations.ssr.ts @@ -1,4 +1,4 @@ -import {UniversalControllerDefinitionWithoutProps} from '../../../app/commerce-ssr-engine/types/common.js'; +import {RecommendationOnlyControllerDefinitionWithoutProps} from '../../../app/commerce-ssr-engine/types/common.js'; import {RecommendationsState} from '../recommendations/headless-recommendations.js'; import { RecommendationsProps, @@ -12,11 +12,11 @@ export type {Recommendations, RecommendationsState}; * @internal * */ export type RecommendationsDefinitionMeta = { - _recommendationProps: {} & RecommendationsProps['options']; + options: {} & RecommendationsProps['options']; }; export interface RecommendationsDefinition - extends UniversalControllerDefinitionWithoutProps {} + extends RecommendationOnlyControllerDefinitionWithoutProps {} /** * @internal * Defines a `Recommendations` controller instance. @@ -29,10 +29,8 @@ export function defineRecommendations( props: RecommendationsProps ): RecommendationsDefinition & RecommendationsDefinitionMeta { return { - search: true, - listing: true, - standalone: true, - _recommendationProps: { + recommendation: true, + options: { ...props.options, }, build: (engine) => buildRecommendations(engine, props), From f2d1fa1413f443aee80b50d2a0ce11ffb9bc76f6 Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Fri, 15 Nov 2024 14:20:23 -0500 Subject: [PATCH 07/30] working draft --- .../commerce-engine/commerce-engine.ssr.ts | 122 ++++++++++++++---- .../src/app/commerce-ssr-engine/common.ts | 35 ++++- .../app/commerce-ssr-engine/types/build.ts | 42 ++++++ .../app/commerce-ssr-engine/types/common.ts | 14 +- .../commerce-ssr-engine/types/core-engine.ts | 56 ++++---- .../types/fetch-static-state.ts | 60 +++++++++ .../types/from-build-result.ts | 23 ++++ .../types/hydrate-static-state.ts | 43 ++++++ .../app/search-engine/search-engine.ssr.ts | 57 ++------ .../headless/src/app/ssr-engine/common.ts | 6 +- .../src/app/ssr-engine/types/build.ts | 10 +- .../src/app/ssr-engine/types/common.ts | 2 +- .../ssr-engine/types/hydrate-static-state.ts | 2 +- .../headless-core-parameter-manager.ssr.ts | 1 + .../app/_components/pages/recommendation.tsx | 19 +-- .../app/_lib/commerce-engine.ts | 8 ++ .../app/recommendation/page.tsx | 10 +- 17 files changed, 372 insertions(+), 138 deletions(-) create mode 100644 packages/headless/src/app/commerce-ssr-engine/types/build.ts create mode 100644 packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts create mode 100644 packages/headless/src/app/commerce-ssr-engine/types/from-build-result.ts create mode 100644 packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts diff --git a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts index ed2714de111..09610a3e59c 100644 --- a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts +++ b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts @@ -6,6 +6,13 @@ import {stateKey} from '../../app/state-key.js'; import {buildProductListing} from '../../controllers/commerce/product-listing/headless-product-listing.js'; import {buildSearch} from '../../controllers/commerce/search/headless-search.js'; import type {Controller} from '../../controllers/controller/headless-controller.js'; +// import { +// defineContext, +// defineParameterManager, +// defineRecommendations, +// defineStandaloneSearchBox, +// getSampleCommerceEngineConfiguration, +// } from '../../ssr-commerce.index.js'; import { createWaitForActionMiddleware, createWaitForActionMiddlewareForRecommendation, @@ -13,9 +20,13 @@ import { import { buildControllerDefinitions, buildRecommendationFilter, + createStaticState, } from '../commerce-ssr-engine/common.js'; import { ControllerDefinitionsMap, + EngineStaticState, + InferControllerPropsMapFromDefinitions, + InferControllersMapFromDefinition, InferControllerStaticStateMapFromDefinitionsWithSolutionType, SolutionType, } from '../commerce-ssr-engine/types/common.js'; @@ -25,11 +36,6 @@ import { } from '../commerce-ssr-engine/types/core-engine.js'; import {NavigatorContextProvider} from '../navigatorContextProvider.js'; import {composeFunction} from '../ssr-engine/common.js'; -import {createStaticState} from '../ssr-engine/common.js'; -import { - EngineStaticState, - InferControllerPropsMapFromDefinitions, -} from '../ssr-engine/types/common.js'; import { CommerceEngine, CommerceEngineOptions, @@ -196,6 +202,7 @@ export function defineCommerceEngine< Parameters; // TODO: ideally , we only want to execute that for recommendation stuff + // TODO: get rid of that here. need to be computed in the fetch static state now const recommendationFilter = () => buildRecommendationFilter(controllerDefinitions ?? {}); @@ -209,17 +216,15 @@ export function defineCommerceEngine< engineOptions.navigatorContextProvider = navigatorContextProvider; }; const buildFactory = - (solutionType: T) => + (solutionType: T, count: number = 0) => async (...[buildOptions]: BuildParameters) => { const engine = buildSSRCommerceEngine( solutionType, - buildOptions?.extend + buildOptions && 'extend' in buildOptions && buildOptions?.extend ? await buildOptions.extend(getOptions()) : getOptions(), // TODO: clean that - solutionType === SolutionType.recommendation - ? recommendationFilter().count - : 0 + solutionType === SolutionType.recommendation ? count : 0 // TODO: avoid this by creating a build factory for recs ); const controllers = buildControllerDefinitions({ definitionsMap: (controllerDefinitions ?? {}) as TControllerDefinitions, @@ -272,9 +277,6 @@ export function defineCommerceEngine< case SolutionType.search: buildSearch(engine).executeFirstSearch(); break; - case SolutionType.recommendation: - recommendationFilter().refresh(controllers); - break; } // TODO: should be only one searchAction for search and listing @@ -296,6 +298,77 @@ export function defineCommerceEngine< } ); + // TODO: enlever ce hack! + const controllerList: Set = new Set(); + + // TODO: remove factory + const fetchStaticStateFactoryForRecommendation: () => FetchStaticStateFunction = + () => + composeFunction( + async (...params: FetchStaticStateParameters) => { + // TODO: no need for ...params since it is a list of controllers + console.log(params, ((params || [])[0] as string[])?.length); // Something is wrong with the type here + // FIXME: just WOW + ((params || [])[0] as Array).forEach((controller) => { + controllerList.add(controller); + }); + if (!getOptions().navigatorContextProvider) { + // TODO: KIT-3409 - implement a logger to log SSR warnings/errors + console.warn( + '[WARNING] Missing navigator context in server-side code. Make sure to set it with `setNavigatorContextProvider` before calling fetchStaticState()' + ); + } + + const buildResult = (await buildFactory( + SolutionType.recommendation, + ((params || [])[0] as string[])?.length // TODO: fix that mess + )(...params)) as { + engine: SSRCommerceEngine; + controllers: InferControllersMapFromDefinition< + TControllerDefinitions, + SolutionType + >; + }; // TODO: check if can remove the cast + const staticState = + await fetchStaticStateFactoryForRecommendation().fromBuildResult({ + buildResult, + }); + return staticState; + }, + { + fromBuildResult: async ( + ...params: FetchStaticStateFromBuildResultParameters + ) => { + const [ + { + buildResult: {engine, controllers}, + }, + ] = params; + + recommendationFilter().refresh( + controllers, + Array.from(controllerList) // TODO: find the right type + ); // TODO: filter out the controllers to only include the one in the static state params + + // TODO: should be only one searchAction for search and listing + const searchActions = await Promise.all( + engine.waitForRequestCompletedAction() + ); + + return createStaticState({ + searchActions, + controllers, + }) as EngineStaticState< + UnknownAction, + InferControllerStaticStateMapFromDefinitionsWithSolutionType< + TControllerDefinitions, + SolutionType + > + >; + }, + } + ); + const hydrateStaticStateFactory: ( solutionType: SolutionType ) => HydrateStaticStateFunction = (solutionType: SolutionType) => @@ -354,7 +427,7 @@ export function defineCommerceEngine< } as CommerceEngineDefinition, recommendationEngineDefinition: { build: buildFactory(SolutionType.recommendation), - fetchStaticState: fetchStaticStateFactory(SolutionType.recommendation), + fetchStaticState: fetchStaticStateFactoryForRecommendation(), hydrateStaticState: hydrateStaticStateFactory( SolutionType.recommendation ), @@ -376,7 +449,7 @@ export function defineCommerceEngine< }; } -/// Sandbox +// // Sandbox // const { // recommendationEngineDefinition, // searchEngineDefinition, @@ -387,7 +460,8 @@ export function defineCommerceEngine< // standaloneSearchBox: defineStandaloneSearchBox({ // options: {redirectionUrl: 'rest'}, // }), -// facets: defineFacetGenerator(), +// facets: defineContext(), +// searchParam: defineParameterManager({search: false}), // trending: defineRecommendations({ // options: {slotId: 'ttt'}, // }), @@ -398,11 +472,15 @@ export function defineCommerceEngine< // }); // // TODO: should have a way to select which recommendation to fetch -// const r = await standaloneEngineDefinition.fetchStaticState(); -// r.controllers.standaloneSearchBox; +// const r = await standaloneEngineDefinition.fetchStaticState({ +// controllers: {searchParam: {initialState: {parameters: {q: 'test'}}}}, +// }); -// const b = await recommendationEngineDefinition.fetchStaticState(['trending']); -// b.controllers.trending; +// const b = await recommendationEngineDefinition.fetchStaticState([ +// 'trending', +// 'popular', +// ]); +// // b.controllers.; -// const a = await searchEngineDefinition.fetchStaticState(); -// a.controllers; // TODO: should throw an error since it's not defined in search +// const a = await searchEngineDefinition.fetchStaticState(); // TODO: fix typing if controller is set to {search: false} +// // a.controllers.; // TODO: should throw an error since it's not defined in search diff --git a/packages/headless/src/app/commerce-ssr-engine/common.ts b/packages/headless/src/app/commerce-ssr-engine/common.ts index 248cb51ad5e..2462bc462b3 100644 --- a/packages/headless/src/app/commerce-ssr-engine/common.ts +++ b/packages/headless/src/app/commerce-ssr-engine/common.ts @@ -1,20 +1,44 @@ +import {UnknownAction} from '@reduxjs/toolkit'; import {Recommendations} from '../../controllers/commerce/recommendations/headless-recommendations.js'; import {RecommendationsDefinitionMeta} from '../../controllers/commerce/recommendations/headless-recommendations.ssr.js'; import {Controller} from '../../controllers/controller/headless-controller.js'; import {InvalidControllerDefinition} from '../../utils/errors.js'; -import {filterObject, mapObject} from '../../utils/utils.js'; +import {clone, filterObject, mapObject} from '../../utils/utils.js'; import {CoreEngine, CoreEngineNext} from '../engine.js'; -import {InferControllerPropsMapFromDefinitions} from '../ssr-engine/types/common.js'; +import { + ControllersMap, + InferControllerStaticStateMapFromControllers, +} from '../ssr-engine/types/common.js'; import { ControllerDefinition, ControllerDefinitionOption, ControllerDefinitionsMap, + EngineStaticState, InferControllerFromDefinition, InferControllerPropsFromDefinition, + InferControllerPropsMapFromDefinitions, InferControllersMapFromDefinition, SolutionType, } from './types/common.js'; +export function createStaticState({ + searchActions, + controllers, +}: { + searchActions: TSearchAction[]; + controllers: ControllersMap; +}): EngineStaticState< + TSearchAction, + InferControllerStaticStateMapFromControllers +> { + return { + controllers: mapObject(controllers, (controller) => ({ + state: clone(controller.state), + })) as InferControllerStaticStateMapFromControllers, + searchActions: searchActions.map((action) => clone(action)), + }; +} + function buildControllerFromDefinition< TControllerDefinition extends ControllerDefinition, TEngine extends CoreEngine | CoreEngineNext, @@ -159,9 +183,12 @@ export function buildRecommendationFilter< * Go through all the controllers passed in argument and only refresh recommendation controllers. * * @param controllers - A record of all controllers where the key is the controller name and the value is the controller instance. + * @param controllerNames - A list of all recommendation controllers to refresh */ - refresh(controllers: Record) { - const isRecommendationController = (key: string) => name.includes(key); + refresh(controllers: Record, whitelist: string[]) { + // TODO: FIND a better way + const isRecommendationController = (key: string) => + name.includes(key) && whitelist.includes(key); Object.entries(controllers) .filter(([key, _]) => isRecommendationController(key)) diff --git a/packages/headless/src/app/commerce-ssr-engine/types/build.ts b/packages/headless/src/app/commerce-ssr-engine/types/build.ts new file mode 100644 index 00000000000..eae01f77a15 --- /dev/null +++ b/packages/headless/src/app/commerce-ssr-engine/types/build.ts @@ -0,0 +1,42 @@ +import {CoreEngine, CoreEngineNext} from '../../engine.js'; +import { + ControllersMap, + ControllersPropsMap, + EngineDefinitionBuildResult, + EngineDefinitionControllersPropsOption, + OptionsExtender, + OptionsTuple, +} from '../../ssr-engine/types/common.js'; + +export interface BuildOptions { + extend?: OptionsExtender; +} + +export interface BuildWithForRecommendations< + TEngine extends CoreEngine | CoreEngineNext, + TControllersMap extends ControllersMap, +> { + /** + * Initializes an engine and controllers from the definition. + */ + ( + c: (keyof TControllersMap)[] + ): Promise>; +} + +export interface Build< + TEngine extends CoreEngine | CoreEngineNext, + TEngineOptions, + TControllersMap extends ControllersMap, + TControllersProps extends ControllersPropsMap, +> { + /** + * Initializes an engine and controllers from the definition. + */ + ( + ...params: OptionsTuple< + BuildOptions & + EngineDefinitionControllersPropsOption + > + ): Promise>; +} diff --git a/packages/headless/src/app/commerce-ssr-engine/types/common.ts b/packages/headless/src/app/commerce-ssr-engine/types/common.ts index 6bbbb3101ba..845f0e0fd4f 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/common.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/common.ts @@ -1,3 +1,4 @@ +import {UnknownAction} from '@reduxjs/toolkit'; import type {Controller} from '../../../controllers/controller/headless-controller.js'; import type {InvalidControllerDefinition} from '../../../utils/errors.js'; import type {CommerceEngine} from '../../commerce-engine/commerce-engine.js'; @@ -7,6 +8,7 @@ import type { InferControllerStaticStateMapFromControllers, InferControllerStaticStateFromController, InferControllerPropsMapFromDefinitions, + ControllerStaticStateMap, } from '../../ssr-engine/types/common.js'; export type { @@ -56,6 +58,14 @@ export interface ControllerDefinitionWithProps< ): TController; } +export interface EngineStaticState< + TSearchAction extends UnknownAction, + TControllers extends ControllerStaticStateMap, +> { + searchActions: TSearchAction[]; + controllers: TControllers; +} + export type ControllerDefinition< TEngine extends CoreEngine | CoreEngineNext, TController extends Controller, @@ -252,7 +262,7 @@ export type SubControllerDefinitionWithoutProps< : TDefinition extends {listing: false; search: false} ? InvalidControllerDefinition : never; -// TODO: add recommendation type here +// TODO: add recommendation type here! export type SubControllerDefinitionWithProps< TController extends Controller, @@ -267,4 +277,4 @@ export type SubControllerDefinitionWithProps< : TDefinition extends {listing: false; search: false} ? InvalidControllerDefinition : never; -// TODO: add recommendation type here +// TODO: add recommendation type here! diff --git a/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts b/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts index 3ff206497fe..cf1cb3a944f 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts @@ -3,19 +3,17 @@ import type {Controller} from '../../../controllers/controller/headless-controll import {EngineConfiguration} from '../../engine-configuration.js'; import {CoreEngine, CoreEngineNext} from '../../engine.js'; import {NavigatorContextProvider} from '../../navigatorContextProvider.js'; -import {Build, BuildWithList} from '../../ssr-engine/types/build.js'; -import {InferControllerPropsMapFromDefinitions} from '../../ssr-engine/types/common.js'; -import { - FetchStaticState, - FetchStaticStateWithList, -} from '../../ssr-engine/types/fetch-static-state.js'; -import {HydrateStaticState} from '../../ssr-engine/types/hydrate-static-state.js'; +import {Build} from '../../ssr-engine/types/build'; +import {BuildWithForRecommendations} from './build.js'; import { ControllerDefinitionsMap, InferControllersMapFromDefinition, SolutionType, InferControllerStaticStateMapFromDefinitionsWithSolutionType, + InferControllerPropsMapFromDefinitions, } from './common.js'; +import {FetchStaticState} from './fetch-static-state.js'; +import {HydrateStaticState} from './hydrate-static-state.js'; export type {HydrateStaticState, FetchStaticState}; export type EngineDefinitionOptions< @@ -40,32 +38,20 @@ export interface EngineDefinition< /** * Fetches the static state on the server side using your engine definition. */ - // TODO: simplify be removing code duplication - fetchStaticState: TSolutionType extends SolutionType.recommendation - ? FetchStaticStateWithList< - TEngine, - InferControllersMapFromDefinition, - UnknownAction, - InferControllerStaticStateMapFromDefinitionsWithSolutionType< - TControllers, - TSolutionType - >, - InferControllerPropsMapFromDefinitions - > - : FetchStaticState< - TEngine, - InferControllersMapFromDefinition, - UnknownAction, - InferControllerStaticStateMapFromDefinitionsWithSolutionType< - TControllers, - TSolutionType - >, - InferControllerPropsMapFromDefinitions - >; + fetchStaticState: FetchStaticState< + TEngine, + InferControllersMapFromDefinition, + UnknownAction, + InferControllerStaticStateMapFromDefinitionsWithSolutionType< + TControllers, + TSolutionType + >, + InferControllerPropsMapFromDefinitions, + TSolutionType + >; /** * Fetches the hydrated state on the client side using your engine definition and the static state. */ - // TODO: Apply the same logic to recommendation hydration hydrateStaticState: HydrateStaticState< TEngine, InferControllersMapFromDefinition, @@ -76,11 +62,13 @@ export interface EngineDefinition< * Builds an engine and its controllers from an engine definition. */ build: TSolutionType extends SolutionType.recommendation - ? BuildWithList< + ? // TODO: THIS should not be a separate function, inside the build function instead + BuildWithForRecommendations< TEngine, - TEngineOptions, - InferControllersMapFromDefinition, - InferControllerPropsMapFromDefinitions + InferControllersMapFromDefinition< + TControllers, + SolutionType.recommendation + > > : Build< TEngine, diff --git a/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts b/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts new file mode 100644 index 00000000000..a1e128046e4 --- /dev/null +++ b/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts @@ -0,0 +1,60 @@ +import {UnknownAction} from '@reduxjs/toolkit'; +import {buildBaseCommerceAPIRequest} from '../../../features/commerce/common/actions.js'; +import { + EngineStaticState, + SolutionType, +} from '../../commerce-ssr-engine/types/common.js'; +import {CoreEngine, CoreEngineNext} from '../../engine.js'; +import { + ControllersMap, + ControllersPropsMap, + ControllerStaticStateMap, + EngineDefinitionControllersPropsOption, + OptionsTuple, +} from '../../ssr-engine/types/common.js'; +import {FromBuildResult} from '../../ssr-engine/types/from-build-result.js'; + +export type FetchStaticStateOptions = {}; + +export type FetchStaticState< + TEngine extends CoreEngine | CoreEngineNext, + TControllers extends ControllersMap, + TSearchAction extends UnknownAction, + TControllersStaticState extends ControllerStaticStateMap, + TControllersProps extends ControllersPropsMap, + TSolutionType extends SolutionType, + FromBuildResultFunction = FromBuildResult< + TEngine, + TControllers, + FetchStaticStateOptions, + EngineStaticState + >, +> = TSolutionType extends SolutionType.recommendation + ? { + /** + * Executes only the initial search for a given configuration, then returns a resumable snapshot of engine state along with the state of the controllers. + * + * Useful for static generation and server-side rendering. + */ + ( + controllers: Array // TODO: make the array unique + ): Promise>; + + fromBuildResult: FromBuildResultFunction; + } + : { + /** + * Executes only the initial search for a given configuration, then returns a resumable snapshot of engine state along with the state of the controllers. + * + * Useful for static generation and server-side rendering. + */ + ( + ...params: OptionsTuple< + FetchStaticStateOptions & + EngineDefinitionControllersPropsOption + > + ): Promise>; + + fromBuildResult: FromBuildResultFunction; + }; +buildBaseCommerceAPIRequest; diff --git a/packages/headless/src/app/commerce-ssr-engine/types/from-build-result.ts b/packages/headless/src/app/commerce-ssr-engine/types/from-build-result.ts new file mode 100644 index 00000000000..184a9bec544 --- /dev/null +++ b/packages/headless/src/app/commerce-ssr-engine/types/from-build-result.ts @@ -0,0 +1,23 @@ +import {CoreEngine, CoreEngineNext} from '../../engine.js'; +import { + ControllersMap, + EngineDefinitionBuildResult, +} from '../../ssr-engine/types/common.js'; + +export interface FromBuildResultOptions< + TEngine extends CoreEngine | CoreEngineNext, + TControllers extends ControllersMap, +> { + buildResult: EngineDefinitionBuildResult; +} + +export interface FromBuildResult< + TEngine extends CoreEngine | CoreEngineNext, + TControllers extends ControllersMap, + TOptions, + TReturn, +> { + ( + options: FromBuildResultOptions & TOptions + ): Promise; +} diff --git a/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts b/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts new file mode 100644 index 00000000000..e84d47e3a12 --- /dev/null +++ b/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts @@ -0,0 +1,43 @@ +import {UnknownAction} from '@reduxjs/toolkit'; +import {CoreEngine, CoreEngineNext} from '../../engine.js'; +import { + ControllersMap, + ControllersPropsMap, + EngineDefinitionControllersPropsOption, + HydratedState, + OptionsTuple, +} from '../../ssr-engine/types/common.js'; +import {FromBuildResult} from '../../ssr-engine/types/from-build-result.js'; + +export interface HydrateStaticStateOptions { + searchActions: TSearchAction[]; +} + +// TODO: check if need to create one hydrate function specific for recommendations. +// - If yes, then adjust this. +// - If not, then remove and reuse the interface from ssr-engine +export type HydrateStaticState< + TEngine extends CoreEngine | CoreEngineNext, + TControllers extends ControllersMap, + TSearchAction extends UnknownAction, + TControllersProps extends ControllersPropsMap, +> = { + /** + * Creates a new engine from the snapshot of the engine created in SSR with fetchStaticState. + * + * Useful when hydrating a server-side-rendered engine. + */ + ( + ...params: OptionsTuple< + HydrateStaticStateOptions & + EngineDefinitionControllersPropsOption + > + ): Promise>; + + fromBuildResult: FromBuildResult< + TEngine, + TControllers, + HydrateStaticStateOptions, + HydratedState + >; +}; diff --git a/packages/headless/src/app/search-engine/search-engine.ssr.ts b/packages/headless/src/app/search-engine/search-engine.ssr.ts index f14b5191c6f..b1361574c7e 100644 --- a/packages/headless/src/app/search-engine/search-engine.ssr.ts +++ b/packages/headless/src/app/search-engine/search-engine.ssr.ts @@ -5,7 +5,6 @@ import {UnknownAction} from '@reduxjs/toolkit'; import type {Controller} from '../../controllers/controller/headless-controller.js'; import {LegacySearchAction} from '../../features/analytics/analytics-utils.js'; import {createWaitForActionMiddleware} from '../../utils/utils.js'; -import {buildRecommendationFilter} from '../commerce-ssr-engine/common.js'; import {NavigatorContextProvider} from '../navigatorContextProvider.js'; import { buildControllerDefinitions, @@ -37,7 +36,7 @@ export interface SSRSearchEngine extends SearchEngine { /** * Waits for the search to be completed and returns a promise that resolves to a `SearchCompletedAction`. */ - waitForSearchCompletedAction(): Promise[]; + waitForSearchCompletedAction(): Promise; } /** @@ -61,34 +60,13 @@ function isSearchCompletedAction( ); } -function isRecommendationCompletedAction( - action: unknown -): action is SearchCompletedAction { - return /^recommendation\/get\/(fulfilled|rejected)$/.test( - (action as UnknownAction).type - ); -} - -function buildSSRSearchEngine( - options: SearchEngineOptions, - recommendationCount: number -): SSRSearchEngine { +function buildSSRSearchEngine(options: SearchEngineOptions): SSRSearchEngine { const {middleware, promise} = createWaitForActionMiddleware( isSearchCompletedAction ); - - const recommendationActionMiddlewares = Array.from( - {length: recommendationCount}, - () => createWaitForActionMiddleware(isRecommendationCompletedAction) - ); - const searchEngine = buildSearchEngine({ ...options, - middlewares: [ - ...(options.middlewares ?? []), - middleware, - ...recommendationActionMiddlewares.map(({middleware}) => middleware), - ], + middlewares: [...(options.middlewares ?? []), middleware], }); return { ...searchEngine, @@ -96,10 +74,7 @@ function buildSSRSearchEngine( return searchEngine.state; }, waitForSearchCompletedAction() { - return [ - promise, - ...recommendationActionMiddlewares.map(({promise}) => promise), - ]; + return promise; }, }; } @@ -146,10 +121,6 @@ export function defineSearchEngine< type HydrateStaticStateFromBuildResultParameters = Parameters; - const recommendationHelper = buildRecommendationFilter( - controllerDefinitions ?? {} - ); - const getOptions = () => { return engineOptions; }; @@ -164,8 +135,7 @@ export function defineSearchEngine< const engine = buildSSRSearchEngine( buildOptions?.extend ? await buildOptions.extend(getOptions()) - : getOptions(), - recommendationHelper.count + : getOptions() ); const controllers = buildControllerDefinitions({ definitionsMap: (controllerDefinitions ?? {}) as TControllerDefinitions, @@ -205,14 +175,8 @@ export function defineSearchEngine< ] = params; engine.executeFirstSearch(); - recommendationHelper.refresh(controllers); - - const searchActions = await Promise.all( - engine.waitForSearchCompletedAction() - ); - return createStaticState({ - searchActions, + searchAction: await engine.waitForSearchCompletedAction(), controllers, }) as EngineStaticState< UnknownAction, @@ -233,7 +197,7 @@ export function defineSearchEngine< const buildResult = await build(...(params as BuildParameters)); const staticState = await hydrateStaticState.fromBuildResult({ buildResult, - searchActions: params[0]!.searchActions, + searchAction: params[0]!.searchAction, }); return staticState; }, @@ -244,13 +208,10 @@ export function defineSearchEngine< const [ { buildResult: {engine, controllers}, - searchActions, + searchAction, }, ] = params; - - searchActions.forEach((action) => { - engine.dispatch(action); - }); + engine.dispatch(searchAction); await engine.waitForSearchCompletedAction(); return {engine, controllers}; }, diff --git a/packages/headless/src/app/ssr-engine/common.ts b/packages/headless/src/app/ssr-engine/common.ts index 79eef38ef33..26b0503f1dd 100644 --- a/packages/headless/src/app/ssr-engine/common.ts +++ b/packages/headless/src/app/ssr-engine/common.ts @@ -58,10 +58,10 @@ export function buildControllerDefinitions< } export function createStaticState({ - searchActions, + searchAction, controllers, }: { - searchActions: TSearchAction[]; + searchAction: TSearchAction; controllers: ControllersMap; }): EngineStaticState< TSearchAction, @@ -71,7 +71,7 @@ export function createStaticState({ controllers: mapObject(controllers, (controller) => ({ state: clone(controller.state), })) as InferControllerStaticStateMapFromControllers, - searchActions: searchActions.map((action) => clone(action)), + searchAction: clone(searchAction), }; } diff --git a/packages/headless/src/app/ssr-engine/types/build.ts b/packages/headless/src/app/ssr-engine/types/build.ts index bb6c9eca00b..62b11178821 100644 --- a/packages/headless/src/app/ssr-engine/types/build.ts +++ b/packages/headless/src/app/ssr-engine/types/build.ts @@ -12,21 +12,15 @@ export interface BuildOptions { extend?: OptionsExtender; } -export interface BuildWithList< +export interface BuildWithForRecommendations< TEngine extends CoreEngine | CoreEngineNext, - TEngineOptions, TControllersMap extends ControllersMap, - TControllersProps extends ControllersPropsMap, > { /** * Initializes an engine and controllers from the definition. */ ( - c: (keyof TControllersMap)[], - ...params: OptionsTuple< - BuildOptions & - EngineDefinitionControllersPropsOption - > + controllers: (keyof TControllersMap)[] ): Promise>; } diff --git a/packages/headless/src/app/ssr-engine/types/common.ts b/packages/headless/src/app/ssr-engine/types/common.ts index 853beb17cc5..831b23ec423 100644 --- a/packages/headless/src/app/ssr-engine/types/common.ts +++ b/packages/headless/src/app/ssr-engine/types/common.ts @@ -105,7 +105,7 @@ export interface EngineStaticState< TSearchAction extends UnknownAction, TControllers extends ControllerStaticStateMap, > { - searchActions: TSearchAction[]; + searchAction: TSearchAction; controllers: TControllers; } diff --git a/packages/headless/src/app/ssr-engine/types/hydrate-static-state.ts b/packages/headless/src/app/ssr-engine/types/hydrate-static-state.ts index 9f4a265badb..f7164fa0932 100644 --- a/packages/headless/src/app/ssr-engine/types/hydrate-static-state.ts +++ b/packages/headless/src/app/ssr-engine/types/hydrate-static-state.ts @@ -10,7 +10,7 @@ import { import {FromBuildResult} from './from-build-result.js'; export interface HydrateStaticStateOptions { - searchActions: TSearchAction[]; + searchAction: TSearchAction; } export type HydrateStaticState< diff --git a/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts b/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts index 2a4917db7d0..4b783b7453b 100644 --- a/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts +++ b/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts @@ -46,6 +46,7 @@ export function defineParameterManager< >(options?: TOptions) { ensureAtLeastOneSolutionType(options); return { + // TODO: we do now want to have to set the props if we set standalone to false... same thing with other components like cart ...options, buildWithProps: (engine, props, solutionType) => { if (solutionType === SolutionType.listing) { diff --git a/packages/samples/headless-ssr-commerce/app/_components/pages/recommendation.tsx b/packages/samples/headless-ssr-commerce/app/_components/pages/recommendation.tsx index 90ffd1d4600..e3c7d60fcc5 100644 --- a/packages/samples/headless-ssr-commerce/app/_components/pages/recommendation.tsx +++ b/packages/samples/headless-ssr-commerce/app/_components/pages/recommendation.tsx @@ -3,9 +3,9 @@ import {NavigatorContext} from '@coveo/headless/ssr-commerce'; import {useEffect, useState} from 'react'; import { - StandaloneStaticState, - StandaloneHydratedState, - standaloneEngineDefinition, + RecommendationStaticState, + RecommendationHydratedState, + recommendationEngineDefinition, } from '../../_lib/commerce-engine'; import {Recommendations} from '../recommendation-list'; @@ -13,30 +13,25 @@ export default function Recommendation({ staticState, navigatorContext, }: { - staticState: StandaloneStaticState; + staticState: RecommendationStaticState; navigatorContext: NavigatorContext; }) { const [hydratedState, setHydratedState] = useState< - StandaloneHydratedState | undefined + RecommendationHydratedState | undefined >(undefined); // Setting the navigator context provider also in client-side before hydrating the application - standaloneEngineDefinition.setNavigatorContextProvider( + recommendationEngineDefinition.setNavigatorContextProvider( () => navigatorContext ); useEffect(() => { - standaloneEngineDefinition + recommendationEngineDefinition .hydrateStaticState({ searchActions: staticState.searchActions, }) .then(({engine, controllers}) => { setHydratedState({engine, controllers}); - - // Refreshing recommendations in the browser after hydrating the state in the client-side - // Recommendation refresh in the server is not supported yet. - controllers.popularBoughtRecs.refresh(); - controllers.popularViewedRecs.refresh(); }); }, [staticState]); diff --git a/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine.ts b/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine.ts index 0d08774ac59..510d9aaaee8 100644 --- a/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine.ts +++ b/packages/samples/headless-ssr-commerce/app/_lib/commerce-engine.ts @@ -11,6 +11,7 @@ export const { listingEngineDefinition, searchEngineDefinition, standaloneEngineDefinition, + recommendationEngineDefinition, } = engineDefinition; export type ListingStaticState = InferStaticState< @@ -31,3 +32,10 @@ export type StandaloneStaticState = InferStaticState< export type StandaloneHydratedState = InferHydratedState< typeof standaloneEngineDefinition >; + +export type RecommendationStaticState = InferStaticState< + typeof recommendationEngineDefinition +>; +export type RecommendationHydratedState = InferHydratedState< + typeof recommendationEngineDefinition +>; diff --git a/packages/samples/headless-ssr-commerce/app/recommendation/page.tsx b/packages/samples/headless-ssr-commerce/app/recommendation/page.tsx index 3a27fdbfe0f..1c60d4ab0f9 100644 --- a/packages/samples/headless-ssr-commerce/app/recommendation/page.tsx +++ b/packages/samples/headless-ssr-commerce/app/recommendation/page.tsx @@ -1,6 +1,6 @@ import {headers} from 'next/headers'; import Recommendation from '../_components/pages/recommendation'; -import {standaloneEngineDefinition} from '../_lib/commerce-engine'; +import {recommendationEngineDefinition} from '../_lib/commerce-engine'; import {NextJsNavigatorContext} from '../_lib/navigatorContextProvider'; /** @@ -11,12 +11,16 @@ import {NextJsNavigatorContext} from '../_lib/navigatorContextProvider'; export default async function RecommendationPage() { // Sets the navigator context provider to use the newly created `navigatorContext` before fetching the app static state const navigatorContext = new NextJsNavigatorContext(headers()); - standaloneEngineDefinition.setNavigatorContextProvider( + recommendationEngineDefinition.setNavigatorContextProvider( () => navigatorContext ); // Fetches the static state of the app with initial state (when applicable) - const staticState = await standaloneEngineDefinition.fetchStaticState(); + const staticState = await recommendationEngineDefinition.fetchStaticState([ + 'popularBoughtRecs', + 'popularViewedRecs', + ]); + // TODO: cannot have the same controller twice return ( Date: Fri, 15 Nov 2024 14:21:14 -0500 Subject: [PATCH 08/30] test --- .../headless/src/app/commerce-engine/commerce-engine.ssr.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts index 09610a3e59c..28e0399ae34 100644 --- a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts +++ b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts @@ -308,7 +308,7 @@ export function defineCommerceEngine< async (...params: FetchStaticStateParameters) => { // TODO: no need for ...params since it is a list of controllers console.log(params, ((params || [])[0] as string[])?.length); // Something is wrong with the type here - // FIXME: just WOW + // FIXME: just WOW! ((params || [])[0] as Array).forEach((controller) => { controllerList.add(controller); }); From e60bfe5c78876d4e05c020b8f66b23ddcbf6c1bc Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Fri, 15 Nov 2024 15:14:24 -0500 Subject: [PATCH 09/30] update sample --- .../src/app/commerce-ssr-engine/common.ts | 11 +++- .../app/(listing)/[category]/page.tsx | 18 +++++- .../components/providers/listing-provider.tsx | 2 +- .../providers/recommendation-provider.tsx | 63 +++++++++++++++++++ .../components/providers/search-provider.tsx | 2 +- 5 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx diff --git a/packages/headless/src/app/commerce-ssr-engine/common.ts b/packages/headless/src/app/commerce-ssr-engine/common.ts index 2462bc462b3..13911666d78 100644 --- a/packages/headless/src/app/commerce-ssr-engine/common.ts +++ b/packages/headless/src/app/commerce-ssr-engine/common.ts @@ -97,10 +97,19 @@ export function buildControllerDefinitions< ? definition['standalone'] === false : false; + const unavailabeInRecs = + // TODO: use this disjunction pattern for all other conditions + (solutionType === SolutionType['recommendation'] && + !('recommendation' in definition)) || + ('recommendation' in definition && + definition['recommendation'] === false && + solutionType === SolutionType['recommendation']); + if ( unavailableInSearchSolutionType || unavailableInListingSolutionType || - unavailableInStandaloneSolutionType + unavailableInStandaloneSolutionType || + unavailabeInRecs ) { return null; } diff --git a/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx b/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx index 740a6869fa6..fe2cd83548f 100644 --- a/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx +++ b/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx @@ -6,11 +6,15 @@ import FacetGenerator from '@/components/facets/facet-generator'; import Pagination from '@/components/pagination'; import ProductList from '@/components/product-list'; import ListingProvider from '@/components/providers/listing-provider'; +import RecommendationProvider from '@/components/providers/recommendation-provider'; import Recommendations from '@/components/recommendation-list'; import Sort from '@/components/sort'; import StandaloneSearchBox from '@/components/standalone-search-box'; import Summary from '@/components/summary'; -import {listingEngineDefinition} from '@/lib/commerce-engine'; +import { + listingEngineDefinition, + recommendationEngineDefinition, +} from '@/lib/commerce-engine'; import {NextJsNavigatorContext} from '@/lib/navigatorContextProvider'; import {defaultContext} from '@/utils/context'; import {headers} from 'next/headers'; @@ -54,6 +58,11 @@ export default async function Listing({params}: {params: {category: string}}) { }, }); + const recStaticState = await recommendationEngineDefinition.fetchStaticState([ + 'popularBoughtRecs', + 'popularViewedRecs', + ]); + return (

- + + +
diff --git a/packages/samples/headless-ssr-commerce/components/providers/listing-provider.tsx b/packages/samples/headless-ssr-commerce/components/providers/listing-provider.tsx index d0112efb942..72641370628 100644 --- a/packages/samples/headless-ssr-commerce/components/providers/listing-provider.tsx +++ b/packages/samples/headless-ssr-commerce/components/providers/listing-provider.tsx @@ -28,7 +28,7 @@ export default function ListingProvider({ useEffect(() => { listingEngineDefinition .hydrateStaticState({ - searchAction: staticState.searchAction, + searchActions: staticState.searchActions, controllers: { cart: { initialState: {items: staticState.controllers.cart.state.items}, diff --git a/packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx b/packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx new file mode 100644 index 00000000000..deea79c460b --- /dev/null +++ b/packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx @@ -0,0 +1,63 @@ +'use client'; + +import { + recommendationEngineDefinition, + RecommendationHydratedState, + RecommendationStaticState, +} from '@/lib/commerce-engine'; +import {NavigatorContext} from '@coveo/headless-react/ssr-commerce'; +import {PropsWithChildren, useEffect, useState} from 'react'; + +interface RecommendationPageProps { + staticState: RecommendationStaticState; + navigatorContext: NavigatorContext; +} + +export default function RecommendationProvider({ + staticState, + navigatorContext, + children, +}: PropsWithChildren) { + const [hydratedState, setHydratedState] = useState< + RecommendationHydratedState | undefined + >(undefined); + + // Setting the navigator context provider also in client-side before hydrating the application + recommendationEngineDefinition.setNavigatorContextProvider( + () => navigatorContext + ); + + useEffect(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (recommendationEngineDefinition.hydrateStaticState as any)({ + searchActions: staticState.searchActions, + // controllers: { }, + }).then(({engine, controllers}) => { + setHydratedState({engine, controllers}); + // Refreshing recommendations in the browser after hydrating the state in the client-side + // Recommendation refresh in the server is not supported yet. + // controllers.popularBoughtRecs.refresh(); // FIXME: does not work + }); + }, [staticState]); + + if (hydratedState) { + return ( + + <>{children} + + ); + } else { + return ( + + {/* // TODO: Add KIT-3701: Type 'React.ReactNode' is not assignable to type 'import(".../node_modules/@types/react/index").ReactNode'. + Type 'bigint' is not assignable to type 'ReactNode'.*/} + <>{children} + + ); + } +} diff --git a/packages/samples/headless-ssr-commerce/components/providers/search-provider.tsx b/packages/samples/headless-ssr-commerce/components/providers/search-provider.tsx index e9e099e2869..7467928bb6b 100644 --- a/packages/samples/headless-ssr-commerce/components/providers/search-provider.tsx +++ b/packages/samples/headless-ssr-commerce/components/providers/search-provider.tsx @@ -28,7 +28,7 @@ export default function SearchProvider({ useEffect(() => { searchEngineDefinition .hydrateStaticState({ - searchAction: staticState.searchAction, + searchActions: staticState.searchActions, controllers: { cart: { initialState: {items: staticState.controllers.cart.state.items}, From be888c83c7e808a1f2fe3d383d61b9b85223b5ac Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Fri, 15 Nov 2024 15:48:48 -0500 Subject: [PATCH 10/30] recommendation hydration working --- .../commerce-engine/commerce-engine.ssr.ts | 53 +++++++++++++--- .../commerce-ssr-engine/types/core-engine.ts | 15 ++++- .../types/fetch-static-state.ts | 20 +++--- .../types/hydrate-static-state.ts | 61 ++++++++++++------- packages/headless/src/ssr-commerce.index.ts | 1 + .../app/(listing)/[category]/page.tsx | 1 - .../providers/recommendation-provider.tsx | 17 +++--- 7 files changed, 114 insertions(+), 54 deletions(-) diff --git a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts index 0e0146e3d51..b1796f4e37b 100644 --- a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts +++ b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts @@ -201,6 +201,13 @@ export function defineCommerceEngine< Parameters; type HydrateStaticStateFromBuildResultParameters = Parameters; + type BuildResult = { + engine: SSRCommerceEngine; + controllers: InferControllersMapFromDefinition< + TControllerDefinitions, + SolutionType + >; + }; // TODO: check if can remove the cast // TODO: ideally , we only want to execute that for recommendation stuff // TODO: get rid of that here. need to be computed in the fetch static state now @@ -323,13 +330,7 @@ export function defineCommerceEngine< const buildResult = (await buildFactory( SolutionType.recommendation, ((params || [])[0] as string[])?.length // TODO: fix that mess - )(...params)) as { - engine: SSRCommerceEngine; - controllers: InferControllersMapFromDefinition< - TControllerDefinitions, - SolutionType - >; - }; // TODO: check if can remove the cast + )(...params)) as BuildResult; // TODO: check if can remove the cast const staticState = await fetchStaticStateFactoryForRecommendation().fromBuildResult({ buildResult, @@ -407,6 +408,40 @@ export function defineCommerceEngine< } ); + const hydrateStaticStateFactoryForRecommendation: () => HydrateStaticStateFunction = + () => + composeFunction( + async (...params: HydrateStaticStateParameters) => { + const buildResult = (await buildFactory(SolutionType.recommendation)( + ...(params as BuildParameters) + )) as BuildResult; // TODO: check if can remove the cast + const staticState = + await hydrateStaticStateFactoryForRecommendation().fromBuildResult({ + buildResult, + searchActions: params[0]!.searchActions, + }); + return staticState; + }, + { + fromBuildResult: async ( + ...params: HydrateStaticStateFromBuildResultParameters + ) => { + const [ + { + buildResult: {engine, controllers}, + searchActions, + }, + ] = params; + + searchActions.forEach((action) => { + engine.dispatch(action); + }); + await engine.waitForRequestCompletedAction(); + return {engine, controllers}; + }, + } + ); + return { listingEngineDefinition: { build: buildFactory(SolutionType.listing), @@ -423,9 +458,7 @@ export function defineCommerceEngine< recommendationEngineDefinition: { build: buildFactory(SolutionType.recommendation), fetchStaticState: fetchStaticStateFactoryForRecommendation(), - hydrateStaticState: hydrateStaticStateFactory( - SolutionType.recommendation - ), + hydrateStaticState: hydrateStaticStateFactoryForRecommendation(), setNavigatorContextProvider, } as CommerceEngineDefinition< TControllerDefinitions, diff --git a/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts b/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts index 0b819d674c7..afee85158a9 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts @@ -15,9 +15,17 @@ import { FetchStaticState, FetchStaticStateOptions, } from './fetch-static-state.js'; -import {HydrateStaticState} from './hydrate-static-state.js'; +import { + HydrateStaticState, + HydrateStaticStateOptions, +} from './hydrate-static-state.js'; -export type {HydrateStaticState, FetchStaticState, FetchStaticStateOptions}; +export type { + HydrateStaticState, + HydrateStaticStateOptions, + FetchStaticState, + FetchStaticStateOptions, +}; export type EngineDefinitionOptions< TOptions extends {configuration: EngineConfiguration}, TControllers extends ControllerDefinitionsMap< @@ -58,7 +66,8 @@ export interface EngineDefinition< TEngine, InferControllersMapFromDefinition, UnknownAction, - InferControllerPropsMapFromDefinitions + InferControllerPropsMapFromDefinitions, + TSolutionType >; /** * Builds an engine and its controllers from an engine definition. diff --git a/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts b/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts index a1e128046e4..2a51e468ccd 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts @@ -23,12 +23,6 @@ export type FetchStaticState< TControllersStaticState extends ControllerStaticStateMap, TControllersProps extends ControllersPropsMap, TSolutionType extends SolutionType, - FromBuildResultFunction = FromBuildResult< - TEngine, - TControllers, - FetchStaticStateOptions, - EngineStaticState - >, > = TSolutionType extends SolutionType.recommendation ? { /** @@ -40,7 +34,12 @@ export type FetchStaticState< controllers: Array // TODO: make the array unique ): Promise>; - fromBuildResult: FromBuildResultFunction; + fromBuildResult: FromBuildResult< + TEngine, + TControllers, + FetchStaticStateOptions, + EngineStaticState + >; } : { /** @@ -55,6 +54,11 @@ export type FetchStaticState< > ): Promise>; - fromBuildResult: FromBuildResultFunction; + fromBuildResult: FromBuildResult< + TEngine, + TControllers, + FetchStaticStateOptions, + EngineStaticState + >; }; buildBaseCommerceAPIRequest; diff --git a/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts b/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts index e84d47e3a12..62e90c56f2b 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts @@ -8,36 +8,53 @@ import { OptionsTuple, } from '../../ssr-engine/types/common.js'; import {FromBuildResult} from '../../ssr-engine/types/from-build-result.js'; +import {SolutionType} from './common.js'; export interface HydrateStaticStateOptions { searchActions: TSearchAction[]; } -// TODO: check if need to create one hydrate function specific for recommendations. -// - If yes, then adjust this. -// - If not, then remove and reuse the interface from ssr-engine export type HydrateStaticState< TEngine extends CoreEngine | CoreEngineNext, TControllers extends ControllersMap, TSearchAction extends UnknownAction, TControllersProps extends ControllersPropsMap, -> = { - /** - * Creates a new engine from the snapshot of the engine created in SSR with fetchStaticState. - * - * Useful when hydrating a server-side-rendered engine. - */ - ( - ...params: OptionsTuple< - HydrateStaticStateOptions & - EngineDefinitionControllersPropsOption - > - ): Promise>; + TSolutionType extends SolutionType, +> = TSolutionType extends SolutionType.recommendation + ? { + /** + * Creates a new engine from the snapshot of the engine created in SSR with fetchStaticState. + * + * Useful when hydrating a server-side-rendered engine. + */ + ( + ...params: OptionsTuple> + ): Promise>; - fromBuildResult: FromBuildResult< - TEngine, - TControllers, - HydrateStaticStateOptions, - HydratedState - >; -}; + fromBuildResult: FromBuildResult< + TEngine, + TControllers, + HydrateStaticStateOptions, + HydratedState + >; + } + : { + /** + * Creates a new engine from the snapshot of the engine created in SSR with fetchStaticState. + * + * Useful when hydrating a server-side-rendered engine. + */ + ( + ...params: OptionsTuple< + HydrateStaticStateOptions & + EngineDefinitionControllersPropsOption + > + ): Promise>; + + fromBuildResult: FromBuildResult< + TEngine, + TControllers, + HydrateStaticStateOptions, + HydratedState + >; + }; diff --git a/packages/headless/src/ssr-commerce.index.ts b/packages/headless/src/ssr-commerce.index.ts index 8489903b27b..a232944cd89 100644 --- a/packages/headless/src/ssr-commerce.index.ts +++ b/packages/headless/src/ssr-commerce.index.ts @@ -156,6 +156,7 @@ export type { HydrateStaticState, FetchStaticState, FetchStaticStateOptions, + HydrateStaticStateOptions, } from './app/commerce-ssr-engine/types/core-engine.js'; // export type { // FromBuildResult, diff --git a/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx b/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx index fe2cd83548f..269a0d5b346 100644 --- a/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx +++ b/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx @@ -60,7 +60,6 @@ export default async function Listing({params}: {params: {category: string}}) { const recStaticState = await recommendationEngineDefinition.fetchStaticState([ 'popularBoughtRecs', - 'popularViewedRecs', ]); return ( diff --git a/packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx b/packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx index deea79c460b..fe80184f3a9 100644 --- a/packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx +++ b/packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx @@ -28,16 +28,13 @@ export default function RecommendationProvider({ ); useEffect(() => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (recommendationEngineDefinition.hydrateStaticState as any)({ - searchActions: staticState.searchActions, - // controllers: { }, - }).then(({engine, controllers}) => { - setHydratedState({engine, controllers}); - // Refreshing recommendations in the browser after hydrating the state in the client-side - // Recommendation refresh in the server is not supported yet. - // controllers.popularBoughtRecs.refresh(); // FIXME: does not work - }); + recommendationEngineDefinition + .hydrateStaticState({ + searchActions: staticState.searchActions, + }) // TODO: need to pass the search actions!!!! + .then(({engine, controllers}) => { + setHydratedState({engine, controllers}); + }); }, [staticState]); if (hydratedState) { From c4ff2b9e596487a4128178fa8d86354ed7bb5ec4 Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Fri, 15 Nov 2024 15:53:37 -0500 Subject: [PATCH 11/30] clean build function --- .../app/commerce-ssr-engine/types/build.ts | 47 +++++++++---------- .../commerce-ssr-engine/types/core-engine.ts | 24 ++++------ packages/headless/src/ssr-commerce.index.ts | 5 +- 3 files changed, 32 insertions(+), 44 deletions(-) diff --git a/packages/headless/src/app/commerce-ssr-engine/types/build.ts b/packages/headless/src/app/commerce-ssr-engine/types/build.ts index 58ab8d5973b..b92c3eee557 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/build.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/build.ts @@ -7,36 +7,35 @@ import { OptionsExtender, OptionsTuple, } from '../../ssr-engine/types/common.js'; +import {SolutionType} from './common.js'; export interface BuildOptions { extend?: OptionsExtender; } -export interface BuildWithList< - TEngine extends CoreEngine | CoreEngineNext, - TControllersMap extends ControllersMap, -> { - /** - * Initializes an engine and controllers from the definition. - */ - ( - c: (keyof TControllersMap)[] - ): Promise>; -} - -export interface Build< +export type Build< TEngine extends CoreEngine | CoreEngineNext, TEngineOptions, TControllersMap extends ControllersMap, TControllersProps extends ControllersPropsMap, -> { - /** - * Initializes an engine and controllers from the definition. - */ - ( - ...params: OptionsTuple< - BuildOptions & - EngineDefinitionControllersPropsOption - > - ): Promise>; -} + TSolutionType extends SolutionType, +> = TSolutionType extends SolutionType.recommendation + ? { + /** + * Initializes an engine and controllers from the definition. + */ + ( + c: (keyof TControllersMap)[] + ): Promise>; + } + : { + /** + * Initializes an engine and controllers from the definition. + */ + ( + ...params: OptionsTuple< + BuildOptions & + EngineDefinitionControllersPropsOption + > + ): Promise>; + }; diff --git a/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts b/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts index afee85158a9..75db55d7dcd 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts @@ -3,7 +3,7 @@ import type {Controller} from '../../../controllers/controller/headless-controll import {EngineConfiguration} from '../../engine-configuration.js'; import {CoreEngine, CoreEngineNext} from '../../engine.js'; import {NavigatorContextProvider} from '../../navigatorContextProvider.js'; -import {BuildWithList, Build} from './build.js'; +import {Build} from './build.js'; import { ControllerDefinitionsMap, InferControllersMapFromDefinition, @@ -72,21 +72,13 @@ export interface EngineDefinition< /** * Builds an engine and its controllers from an engine definition. */ - build: TSolutionType extends SolutionType.recommendation - ? // TODO: THIS should not be a separate function, inside the build function instead - BuildWithList< - TEngine, - InferControllersMapFromDefinition< - TControllers, - SolutionType.recommendation - > - > - : Build< - TEngine, - TEngineOptions, - InferControllersMapFromDefinition, - InferControllerPropsMapFromDefinitions - >; + build: Build< + TEngine, + TEngineOptions, + InferControllersMapFromDefinition, + InferControllerPropsMapFromDefinitions, + TSolutionType + >; /** * Sets the navigator context provider. diff --git a/packages/headless/src/ssr-commerce.index.ts b/packages/headless/src/ssr-commerce.index.ts index a232944cd89..7b8c9d67d98 100644 --- a/packages/headless/src/ssr-commerce.index.ts +++ b/packages/headless/src/ssr-commerce.index.ts @@ -144,10 +144,7 @@ export type { OptionsExtender, OptionsTuple, } from './app/ssr-engine/types/common.js'; -export type { - Build, - BuildWithList, // TODO: get rid of that once condition is inside Build type -} from './app/commerce-ssr-engine/types/build.js'; +export type {Build} from './app/commerce-ssr-engine/types/build.js'; export type { EngineDefinition, InferStaticState, From 2783740250cd47fe2a2a941a4c7997b6f9a25b82 Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Fri, 15 Nov 2024 17:14:17 -0500 Subject: [PATCH 12/30] clean exports --- .../app/commerce-ssr-engine/types/build.ts | 4 +- .../app/commerce-ssr-engine/types/common.ts | 8 +++ .../commerce-ssr-engine/types/core-engine.ts | 12 +++- .../types/fetch-static-state.ts | 14 ++--- .../types/from-build-result.ts | 23 -------- .../types/hydrate-static-state.ts | 8 +-- packages/headless/src/ssr-commerce.index.ts | 56 ++----------------- 7 files changed, 35 insertions(+), 90 deletions(-) delete mode 100644 packages/headless/src/app/commerce-ssr-engine/types/from-build-result.ts diff --git a/packages/headless/src/app/commerce-ssr-engine/types/build.ts b/packages/headless/src/app/commerce-ssr-engine/types/build.ts index b92c3eee557..a3b5fe1a238 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/build.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/build.ts @@ -1,5 +1,5 @@ -import {CoreEngine, CoreEngineNext} from '../../engine.js'; -import { +import type {CoreEngine, CoreEngineNext} from '../../engine.js'; +import type { ControllersMap, ControllersPropsMap, EngineDefinitionBuildResult, diff --git a/packages/headless/src/app/commerce-ssr-engine/types/common.ts b/packages/headless/src/app/commerce-ssr-engine/types/common.ts index af5fd9ab8a6..74cad63a186 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/common.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/common.ts @@ -9,9 +9,17 @@ import type { InferControllerStaticStateFromController, InferControllerPropsMapFromDefinitions, ControllerStaticStateMap, + EngineDefinitionBuildResult, + EngineDefinitionControllersPropsOption, + HydratedState, + OptionsTuple, } from '../../ssr-engine/types/common.js'; export type { + EngineDefinitionBuildResult, + EngineDefinitionControllersPropsOption, + HydratedState, + OptionsTuple, InferControllerStaticStateFromController, InferControllerStaticStateMapFromControllers, InferControllerPropsMapFromDefinitions, diff --git a/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts b/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts index 75db55d7dcd..e821601f3c6 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts @@ -3,7 +3,11 @@ import type {Controller} from '../../../controllers/controller/headless-controll import {EngineConfiguration} from '../../engine-configuration.js'; import {CoreEngine, CoreEngineNext} from '../../engine.js'; import {NavigatorContextProvider} from '../../navigatorContextProvider.js'; -import {Build} from './build.js'; +import type { + FromBuildResult, + FromBuildResultOptions, +} from '../../ssr-engine/types/from-build-result.js'; +import {Build, BuildOptions} from './build.js'; import { ControllerDefinitionsMap, InferControllersMapFromDefinition, @@ -20,11 +24,17 @@ import { HydrateStaticStateOptions, } from './hydrate-static-state.js'; +// TODO: why not use the one for commerce + export type { + FromBuildResult, + FromBuildResultOptions, HydrateStaticState, HydrateStaticStateOptions, FetchStaticState, FetchStaticStateOptions, + Build, + BuildOptions, }; export type EngineDefinitionOptions< TOptions extends {configuration: EngineConfiguration}, diff --git a/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts b/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts index 2a51e468ccd..d05a9cb5c15 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts @@ -1,18 +1,16 @@ -import {UnknownAction} from '@reduxjs/toolkit'; +import type {UnknownAction} from '@reduxjs/toolkit'; import {buildBaseCommerceAPIRequest} from '../../../features/commerce/common/actions.js'; -import { - EngineStaticState, - SolutionType, -} from '../../commerce-ssr-engine/types/common.js'; -import {CoreEngine, CoreEngineNext} from '../../engine.js'; -import { +import {SolutionType} from '../../commerce-ssr-engine/types/common.js'; +import type {EngineStaticState} from '../../commerce-ssr-engine/types/common.js'; +import type {CoreEngine, CoreEngineNext} from '../../engine.js'; +import type { ControllersMap, ControllersPropsMap, ControllerStaticStateMap, EngineDefinitionControllersPropsOption, OptionsTuple, } from '../../ssr-engine/types/common.js'; -import {FromBuildResult} from '../../ssr-engine/types/from-build-result.js'; +import type {FromBuildResult} from '../../ssr-engine/types/from-build-result.js'; export type FetchStaticStateOptions = {}; diff --git a/packages/headless/src/app/commerce-ssr-engine/types/from-build-result.ts b/packages/headless/src/app/commerce-ssr-engine/types/from-build-result.ts deleted file mode 100644 index 184a9bec544..00000000000 --- a/packages/headless/src/app/commerce-ssr-engine/types/from-build-result.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {CoreEngine, CoreEngineNext} from '../../engine.js'; -import { - ControllersMap, - EngineDefinitionBuildResult, -} from '../../ssr-engine/types/common.js'; - -export interface FromBuildResultOptions< - TEngine extends CoreEngine | CoreEngineNext, - TControllers extends ControllersMap, -> { - buildResult: EngineDefinitionBuildResult; -} - -export interface FromBuildResult< - TEngine extends CoreEngine | CoreEngineNext, - TControllers extends ControllersMap, - TOptions, - TReturn, -> { - ( - options: FromBuildResultOptions & TOptions - ): Promise; -} diff --git a/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts b/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts index 62e90c56f2b..34669305f9d 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts @@ -1,13 +1,13 @@ -import {UnknownAction} from '@reduxjs/toolkit'; -import {CoreEngine, CoreEngineNext} from '../../engine.js'; -import { +import type {UnknownAction} from '@reduxjs/toolkit'; +import type {CoreEngine, CoreEngineNext} from '../../engine.js'; +import type { ControllersMap, ControllersPropsMap, EngineDefinitionControllersPropsOption, HydratedState, OptionsTuple, } from '../../ssr-engine/types/common.js'; -import {FromBuildResult} from '../../ssr-engine/types/from-build-result.js'; +import type {FromBuildResult} from '../../ssr-engine/types/from-build-result.js'; import {SolutionType} from './common.js'; export interface HydrateStaticStateOptions { diff --git a/packages/headless/src/ssr-commerce.index.ts b/packages/headless/src/ssr-commerce.index.ts index 7b8c9d67d98..708cbbefe4f 100644 --- a/packages/headless/src/ssr-commerce.index.ts +++ b/packages/headless/src/ssr-commerce.index.ts @@ -97,54 +97,12 @@ export type { InferControllerStaticStateMapFromControllers, InferControllerStaticStateMapFromDefinitionsWithSolutionType, InferControllerPropsMapFromDefinitions, - // TODO: check if need to export these types - RecommendationOnlyControllerDefinitionWithProps, - RecommendationOnlyControllerDefinitionWithoutProps, - UniversalControllerDefinitionWithProps, - ControllerDefinitionWithoutProps, - ControllerDefinitionWithProps, - ControllerDefinition, - ControllerDefinitionOption, EngineStaticState, - InferControllerPropsFromDefinition, - ListingOnlyControllerDefinitionWithProps, - ListingOnlyControllerDefinitionWithoutProps, - SearchAndListingControllerDefinitionWithProps, - SearchAndListingControllerDefinitionWithoutProps, - SearchOnlyControllerDefinitionWithProps, - SearchOnlyControllerDefinitionWithoutProps, - SubControllerDefinitionWithProps, - SubControllerDefinitionWithoutProps, - UniversalControllerDefinitionWithoutProps, -} from './app/commerce-ssr-engine/types/common.js'; -// TODO: check if need to export these types -export type { - // ControllerDefinition, - // ControllerDefinitionWithProps, - // ControllerDefinitionWithoutProps, - // ControllerDefinitionsMap, - ControllerStaticState, - ControllerStaticStateMap, - ControllersMap, - ControllersPropsMap, EngineDefinitionBuildResult, EngineDefinitionControllersPropsOption, - // EngineStaticState, - ExtractRequiredOptions, - HasKey, - HasKeys, HydratedState, - // InferControllerFromDefinition, - // InferControllerPropsFromDefinition, - // InferControllerPropsMapFromDefinitions, - // InferControllerStaticStateFromController, - // InferControllerStaticStateMapFromControllers, - InferControllerStaticStateMapFromDefinitions, - // InferControllersMapFromDefinition, - OptionsExtender, OptionsTuple, -} from './app/ssr-engine/types/common.js'; -export type {Build} from './app/commerce-ssr-engine/types/build.js'; +} from './app/commerce-ssr-engine/types/common.js'; export type { EngineDefinition, InferStaticState, @@ -154,17 +112,11 @@ export type { FetchStaticState, FetchStaticStateOptions, HydrateStaticStateOptions, -} from './app/commerce-ssr-engine/types/core-engine.js'; -// export type { -// FromBuildResult, -// // TODO: check if need to export these types -// FromBuildResultOptions, -// } from './app/commerce-ssr-engine/types/from-build-result.js'; -export type { - // // TODO: check if need to export these types + Build, + BuildOptions, FromBuildResult, FromBuildResultOptions, -} from './app/ssr-engine/types/from-build-result.js'; +} from './app/commerce-ssr-engine/types/core-engine.js'; export type {LoggerOptions} from './app/logger.js'; export type { NavigatorContext, From 8a6f9d8f6844a9c7e2978d84dd6d5dbca371796a Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Sat, 16 Nov 2024 13:17:35 -0500 Subject: [PATCH 13/30] filter invalid and duplicate recommendations --- .../commerce-engine/commerce-engine.ssr.ts | 67 ++++++++++--------- .../src/app/commerce-ssr-engine/common.ts | 31 ++++----- .../app/ssr-engine/types/from-build-result.ts | 9 +++ 3 files changed, 60 insertions(+), 47 deletions(-) diff --git a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts index b1796f4e37b..516236dac7f 100644 --- a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts +++ b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts @@ -19,8 +19,8 @@ import { } from '../../utils/utils.js'; import { buildControllerDefinitions, - buildRecommendationFilter, createStaticState, + filterRecommendationControllers, } from '../commerce-ssr-engine/common.js'; import { ControllerDefinitionsMap, @@ -188,7 +188,7 @@ export function defineCommerceEngine< SolutionType >; type BuildFunction = Definition['build']; - type FetchStaticStateFunction = Definition['fetchStaticState']; // TODO: avoir un fetch pour les recommendations + type FetchStaticStateFunction = Definition['fetchStaticState']; type HydrateStaticStateFunction = Definition['hydrateStaticState']; type FetchStaticStateFromBuildResultFunction = FetchStaticStateFunction['fromBuildResult']; @@ -201,18 +201,16 @@ export function defineCommerceEngine< Parameters; type HydrateStaticStateFromBuildResultParameters = Parameters; + type Controllers = InferControllersMapFromDefinition< + TControllerDefinitions, + SolutionType + >; + type ControllerDefinitionKeys = keyof Controllers; type BuildResult = { engine: SSRCommerceEngine; - controllers: InferControllersMapFromDefinition< - TControllerDefinitions, - SolutionType - >; - }; // TODO: check if can remove the cast - - // TODO: ideally , we only want to execute that for recommendation stuff - // TODO: get rid of that here. need to be computed in the fetch static state now - const recommendationFilter = () => - buildRecommendationFilter(controllerDefinitions ?? {}); + controllers: Controllers; + }; + const logger = buildLogger(options.loggerOptions); const getOptions = () => { return engineOptions; @@ -226,7 +224,6 @@ export function defineCommerceEngine< const buildFactory = (solutionType: T, count: number = 0) => async (...[buildOptions]: BuildParameters) => { - const logger = buildLogger(options.loggerOptions); if (!getOptions().navigatorContextProvider) { logger.warn( '[WARNING] Missing navigator context in server-side code. Make sure to set it with `setNavigatorContextProvider` before calling fetchStaticState()' @@ -306,34 +303,40 @@ export function defineCommerceEngine< } ); - // TODO: enlever ce hack! - const controllerList: Set = new Set(); - // TODO: remove factory const fetchStaticStateFactoryForRecommendation: () => FetchStaticStateFunction = () => composeFunction( - async (...params: FetchStaticStateParameters) => { - // TODO: no need for ...params since it is a list of controllers - console.log(params, ((params || [])[0] as string[])?.length); // Something is wrong with the type here - // FIXME: just WOW! - ((params || [])[0] as Array).forEach((controller) => { - controllerList.add(controller); - }); + async ( + ...params: [controllerKeys: Array] + ) => { + const [controllerKeys] = params; + const uniqueControllerKeys = Array.from(new Set(controllerKeys)); + if (uniqueControllerKeys.length !== controllerKeys.length) { + logger.warn( + '[WARNING] Duplicate controller keys detected in recommendation fetchStaticState call. Make sure to provide only unique controller keys.' + ); + } + + const validControllerNames = Object.keys(controllerDefinitions ?? {}); + const allowedRecommendationKeys = uniqueControllerKeys.filter( + (key: string) => validControllerNames.includes(key) + ); + if (!getOptions().navigatorContextProvider) { - // TODO: KIT-3409 - implement a logger to log SSR warnings/errors - console.warn( + logger.warn( '[WARNING] Missing navigator context in server-side code. Make sure to set it with `setNavigatorContextProvider` before calling fetchStaticState()' ); } const buildResult = (await buildFactory( SolutionType.recommendation, - ((params || [])[0] as string[])?.length // TODO: fix that mess + allowedRecommendationKeys.length )(...params)) as BuildResult; // TODO: check if can remove the cast const staticState = await fetchStaticStateFactoryForRecommendation().fromBuildResult({ buildResult, + recommendationControllerKeys: allowedRecommendationKeys, }); return staticState; }, @@ -344,15 +347,15 @@ export function defineCommerceEngine< const [ { buildResult: {engine, controllers}, + recommendationControllerKeys, }, ] = params; - recommendationFilter().refresh( + filterRecommendationControllers( controllers, - Array.from(controllerList) // TODO: find the right type - ); // TODO: filter out the controllers to only include the one in the static state params + controllerDefinitions ?? {} + ).refresh(recommendationControllerKeys); - // TODO: should be only one searchAction for search and listing const searchActions = await Promise.all( engine.waitForRequestCompletedAction() ); @@ -477,7 +480,7 @@ export function defineCommerceEngine< }; } -// // Sandbox +// Sandbox // const { // recommendationEngineDefinition, // searchEngineDefinition, @@ -506,8 +509,8 @@ export function defineCommerceEngine< // const b = await recommendationEngineDefinition.fetchStaticState([ // 'trending', -// 'popular', // ]); +// const b = await recommendationEngineDefinition.fetchStaticState.fromBuildResult({buildResult:{controllers:{}}}) // // b.controllers.; // const a = await searchEngineDefinition.fetchStaticState(); // TODO: fix typing if controller is set to {search: false} diff --git a/packages/headless/src/app/commerce-ssr-engine/common.ts b/packages/headless/src/app/commerce-ssr-engine/common.ts index 13911666d78..7575ee88b31 100644 --- a/packages/headless/src/app/commerce-ssr-engine/common.ts +++ b/packages/headless/src/app/commerce-ssr-engine/common.ts @@ -139,18 +139,24 @@ export function ensureAtLeastOneSolutionType( } } -export function buildRecommendationFilter< +export function filterRecommendationControllers< TEngine extends CoreEngine | CoreEngineNext, TControllerDefinitions extends ControllerDefinitionsMap, ->(controllerDefinitions: TControllerDefinitions) { +>( + controllers: Record, // TODO: or InferControllersMapFromDefinition + controllerDefinitions: TControllerDefinitions +) { const slotIdSet = new Set(); const isRecommendationDefinition = < C extends ControllerDefinition, >( - controller: C - ): controller is C & RecommendationsDefinitionMeta => { - return 'recommendation' in controller; + controllerDefinition: C + ): controllerDefinition is C & RecommendationsDefinitionMeta => { + return ( + 'recommendation' in controllerDefinition && + controllerDefinition.recommendation === true + ); }; const warnDuplicateRecommendation = (slotId: string, productId?: string) => { @@ -179,23 +185,18 @@ export function buildRecommendationFilter< const name = filtered.map(([name, _]) => name); return { - /** - * Gets the number of recommendation controllers from the controller definitions map. - * - * @returns {number} The number of recommendation controllers in the controller definition map - */ - get count() { - return name.length; - }, - /** * Go through all the controllers passed in argument and only refresh recommendation controllers. * * @param controllers - A record of all controllers where the key is the controller name and the value is the controller instance. * @param controllerNames - A list of all recommendation controllers to refresh */ - refresh(controllers: Record, whitelist: string[]) { + refresh(whitelist?: string[]) { + if (whitelist === undefined) { + return; + } // TODO: FIND a better way + // TODO: do not refresh multiple recommendation controllers for the same slotId and productId const isRecommendationController = (key: string) => name.includes(key) && whitelist.includes(key); diff --git a/packages/headless/src/app/ssr-engine/types/from-build-result.ts b/packages/headless/src/app/ssr-engine/types/from-build-result.ts index 5851651b59b..43212747352 100644 --- a/packages/headless/src/app/ssr-engine/types/from-build-result.ts +++ b/packages/headless/src/app/ssr-engine/types/from-build-result.ts @@ -5,7 +5,16 @@ export interface FromBuildResultOptions< TEngine extends CoreEngine | CoreEngineNext, TControllers extends ControllersMap, > { + /** + * The build result of the engine + */ buildResult: EngineDefinitionBuildResult; + /** + * An optional array of keys representing the recommendation controllers to refresh. + * If a recommendation key defined in your engine definition is present in this list, a recommendation query will be triggered against the API. + * This is applicable only if the engine is a recommendation engine. + */ + recommendationControllerKeys?: (keyof TControllers)[]; } export interface FromBuildResult< From 025b34f72449dfef34d78c926d4190589e3acaf7 Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Sat, 16 Nov 2024 15:19:50 -0500 Subject: [PATCH 14/30] refacto --- .../commerce-engine/commerce-engine.ssr.ts | 425 ++---------------- .../factories/build-factory.ts | 175 ++++++++ .../factories/hydrate-state-factory.ts | 68 +++ .../recommendation-hydrate-state-factory.ts | 61 +++ .../recommendation-static-state-factory.ts | 104 +++++ .../factories/static-state-factory.ts | 88 ++++ .../commerce-ssr-engine/types/core-engine.ts | 60 +++ .../types/fetch-static-state.ts | 2 - packages/headless/src/ssr-commerce.index.ts | 11 +- 9 files changed, 604 insertions(+), 390 deletions(-) create mode 100644 packages/headless/src/app/commerce-ssr-engine/factories/build-factory.ts create mode 100644 packages/headless/src/app/commerce-ssr-engine/factories/hydrate-state-factory.ts create mode 100644 packages/headless/src/app/commerce-ssr-engine/factories/recommendation-hydrate-state-factory.ts create mode 100644 packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts create mode 100644 packages/headless/src/app/commerce-ssr-engine/factories/static-state-factory.ts diff --git a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts index 516236dac7f..6538f7e5756 100644 --- a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts +++ b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts @@ -1,10 +1,6 @@ /** * Utility functions to be used for Commerce Server Side Rendering. */ -import {Action, UnknownAction} from '@reduxjs/toolkit'; -import {stateKey} from '../../app/state-key.js'; -import {buildProductListing} from '../../controllers/commerce/product-listing/headless-product-listing.js'; -import {buildSearch} from '../../controllers/commerce/search/headless-search.js'; import type {Controller} from '../../controllers/controller/headless-controller.js'; // import { // defineContext, @@ -14,132 +10,21 @@ import type {Controller} from '../../controllers/controller/headless-controller. // getSampleCommerceEngineConfiguration, // } from '../../ssr-commerce.index.js'; import { - createWaitForActionMiddleware, - createWaitForActionMiddlewareForRecommendation, -} from '../../utils/utils.js'; -import { - buildControllerDefinitions, - createStaticState, - filterRecommendationControllers, -} from '../commerce-ssr-engine/common.js'; + buildFactory, + CommerceEngineDefinitionOptions, + SSRCommerceEngine, +} from '../commerce-ssr-engine/factories/build-factory.js'; +import {hydrateStaticStateFactory} from '../commerce-ssr-engine/factories/hydrate-state-factory.js'; +import {hydrateRecommendationStaticStateFactory} from '../commerce-ssr-engine/factories/recommendation-hydrate-state-factory.js'; +import {fetchRecommendationStaticStateFactory} from '../commerce-ssr-engine/factories/recommendation-static-state-factory.js'; +import {fetchStaticStateFactory} from '../commerce-ssr-engine/factories/static-state-factory.js'; import { ControllerDefinitionsMap, - EngineStaticState, - InferControllerPropsMapFromDefinitions, - InferControllersMapFromDefinition, - InferControllerStaticStateMapFromDefinitionsWithSolutionType, SolutionType, } from '../commerce-ssr-engine/types/common.js'; -import { - EngineDefinition, - EngineDefinitionOptions, -} from '../commerce-ssr-engine/types/core-engine.js'; -import {buildLogger} from '../logger.js'; +import {EngineDefinition} from '../commerce-ssr-engine/types/core-engine.js'; import {NavigatorContextProvider} from '../navigatorContextProvider.js'; -import {composeFunction} from '../ssr-engine/common.js'; -import { - CommerceEngine, - CommerceEngineOptions, - buildCommerceEngine, -} from './commerce-engine.js'; - -/** - * The SSR commerce engine. - */ -export interface SSRCommerceEngine extends CommerceEngine { - /** - * Waits for the search to be completed and returns a promise that resolves to a `SearchCompletedAction`. - */ - waitForRequestCompletedAction(): Promise[]; -} - -export type CommerceEngineDefinitionOptions< - TControllers extends ControllerDefinitionsMap, -> = EngineDefinitionOptions; - -function isListingFetchCompletedAction(action: unknown): action is Action { - return /^commerce\/productListing\/fetch\/(fulfilled|rejected)$/.test( - (action as UnknownAction).type - ); -} - -function isSearchCompletedAction(action: unknown): action is Action { - return /^commerce\/search\/executeSearch\/(fulfilled|rejected)$/.test( - (action as UnknownAction).type - ); -} - -function isRecommendationCompletedAction(action: unknown): action is Action { - return /^commerce\/recommendations\/fetch\/(fulfilled|rejected)$/.test( - (action as UnknownAction).type - ); -} - -function noSearchActionRequired(_action: unknown): _action is Action { - return true; -} - -function buildSSRCommerceEngine( - solutionType: SolutionType, - options: CommerceEngineOptions, - recommendationCount: number -): SSRCommerceEngine { - let actionCompletionMiddleware: ReturnType< - typeof createWaitForActionMiddleware - >; - - const middlewares: ReturnType[] = []; - const memo: Set = new Set(); - - switch (solutionType) { - case SolutionType.listing: - actionCompletionMiddleware = createWaitForActionMiddleware( - isListingFetchCompletedAction - ); - middlewares.push(actionCompletionMiddleware); - break; - case SolutionType.search: - actionCompletionMiddleware = createWaitForActionMiddleware( - isSearchCompletedAction - ); - middlewares.push(actionCompletionMiddleware); - break; - case SolutionType.recommendation: - middlewares.push( - ...Array.from({length: recommendationCount}, () => - createWaitForActionMiddlewareForRecommendation( - isRecommendationCompletedAction, - memo - ) - ) - ); - break; - default: - actionCompletionMiddleware = createWaitForActionMiddleware( - noSearchActionRequired - ); - } - - const commerceEngine = buildCommerceEngine({ - ...options, - middlewares: [ - ...(options.middlewares ?? []), - ...middlewares.map(({middleware}) => middleware), - ], - }); - - return { - ...commerceEngine, - - get [stateKey]() { - return commerceEngine[stateKey]; - }, - - waitForRequestCompletedAction() { - return [...middlewares.map(({promise}) => promise)]; - }, - }; -} +import {CommerceEngineOptions} from './commerce-engine.js'; export interface CommerceEngineDefinition< TControllers extends ControllerDefinitionsMap, @@ -183,34 +68,6 @@ export function defineCommerceEngine< >; } { const {controllers: controllerDefinitions, ...engineOptions} = options; - type Definition = CommerceEngineDefinition< - TControllerDefinitions, - SolutionType - >; - type BuildFunction = Definition['build']; - type FetchStaticStateFunction = Definition['fetchStaticState']; - type HydrateStaticStateFunction = Definition['hydrateStaticState']; - type FetchStaticStateFromBuildResultFunction = - FetchStaticStateFunction['fromBuildResult']; - type HydrateStaticStateFromBuildResultFunction = - HydrateStaticStateFunction['fromBuildResult']; - type BuildParameters = Parameters; - type FetchStaticStateParameters = Parameters; - type HydrateStaticStateParameters = Parameters; - type FetchStaticStateFromBuildResultParameters = - Parameters; - type HydrateStaticStateFromBuildResultParameters = - Parameters; - type Controllers = InferControllersMapFromDefinition< - TControllerDefinitions, - SolutionType - >; - type ControllerDefinitionKeys = keyof Controllers; - type BuildResult = { - engine: SSRCommerceEngine; - controllers: Controllers; - }; - const logger = buildLogger(options.loggerOptions); const getOptions = () => { return engineOptions; @@ -221,247 +78,49 @@ export function defineCommerceEngine< ) => { engineOptions.navigatorContextProvider = navigatorContextProvider; }; - const buildFactory = - (solutionType: T, count: number = 0) => - async (...[buildOptions]: BuildParameters) => { - if (!getOptions().navigatorContextProvider) { - logger.warn( - '[WARNING] Missing navigator context in server-side code. Make sure to set it with `setNavigatorContextProvider` before calling fetchStaticState()' - ); - } - const engine = buildSSRCommerceEngine( - solutionType, - buildOptions && 'extend' in buildOptions && buildOptions?.extend - ? await buildOptions.extend(getOptions()) - : getOptions(), - // TODO: clean that - solutionType === SolutionType.recommendation ? count : 0 // TODO: avoid this by creating a build factory for recs - ); - const controllers = buildControllerDefinitions({ - definitionsMap: (controllerDefinitions ?? {}) as TControllerDefinitions, - engine, - solutionType, - propsMap: (buildOptions && 'controllers' in buildOptions - ? buildOptions.controllers - : {}) as InferControllerPropsMapFromDefinitions, - }); - - return { - engine, - controllers, - }; - }; - - const fetchStaticStateFactory: ( - solutionType: SolutionType - ) => FetchStaticStateFunction = (solutionType: SolutionType) => - composeFunction( - async (...params: FetchStaticStateParameters) => { - const buildResult = await buildFactory(solutionType)(...params); - const staticState = await fetchStaticStateFactory( - solutionType - ).fromBuildResult({ - buildResult, - }); - return staticState; - }, - { - fromBuildResult: async ( - ...params: FetchStaticStateFromBuildResultParameters - ) => { - const [ - { - buildResult: {engine, controllers}, - }, - ] = params; - - switch (solutionType) { - case SolutionType.listing: - buildProductListing(engine).executeFirstRequest(); - break; - case SolutionType.search: - buildSearch(engine).executeFirstSearch(); - break; - } - // TODO: should be only one searchAction for search and listing - const searchActions = await Promise.all( - engine.waitForRequestCompletedAction() - ); + const build = buildFactory( + controllerDefinitions!, // TODO: find a way to get rid of the ! + getOptions() + ); + const fetchStaticState = fetchStaticStateFactory( + controllerDefinitions!, + getOptions() + ); + const hydrateStaticState = hydrateStaticStateFactory( + controllerDefinitions!, + getOptions() + ); - return createStaticState({ - searchActions, - controllers, - }) as EngineStaticState< - UnknownAction, - InferControllerStaticStateMapFromDefinitionsWithSolutionType< - TControllerDefinitions, - SolutionType - > - >; - }, - } + const fetchRecommendationStaticState = + fetchRecommendationStaticStateFactory( + controllerDefinitions!, + getOptions() // TODO: add count here ); - // TODO: remove factory - const fetchStaticStateFactoryForRecommendation: () => FetchStaticStateFunction = - () => - composeFunction( - async ( - ...params: [controllerKeys: Array] - ) => { - const [controllerKeys] = params; - const uniqueControllerKeys = Array.from(new Set(controllerKeys)); - if (uniqueControllerKeys.length !== controllerKeys.length) { - logger.warn( - '[WARNING] Duplicate controller keys detected in recommendation fetchStaticState call. Make sure to provide only unique controller keys.' - ); - } - - const validControllerNames = Object.keys(controllerDefinitions ?? {}); - const allowedRecommendationKeys = uniqueControllerKeys.filter( - (key: string) => validControllerNames.includes(key) - ); - - if (!getOptions().navigatorContextProvider) { - logger.warn( - '[WARNING] Missing navigator context in server-side code. Make sure to set it with `setNavigatorContextProvider` before calling fetchStaticState()' - ); - } - - const buildResult = (await buildFactory( - SolutionType.recommendation, - allowedRecommendationKeys.length - )(...params)) as BuildResult; // TODO: check if can remove the cast - const staticState = - await fetchStaticStateFactoryForRecommendation().fromBuildResult({ - buildResult, - recommendationControllerKeys: allowedRecommendationKeys, - }); - return staticState; - }, - { - fromBuildResult: async ( - ...params: FetchStaticStateFromBuildResultParameters - ) => { - const [ - { - buildResult: {engine, controllers}, - recommendationControllerKeys, - }, - ] = params; - - filterRecommendationControllers( - controllers, - controllerDefinitions ?? {} - ).refresh(recommendationControllerKeys); - - const searchActions = await Promise.all( - engine.waitForRequestCompletedAction() - ); - - return createStaticState({ - searchActions, - controllers, - }) as EngineStaticState< - UnknownAction, - InferControllerStaticStateMapFromDefinitionsWithSolutionType< - TControllerDefinitions, - SolutionType - > - >; - }, - } - ); - - const hydrateStaticStateFactory: ( - solutionType: SolutionType - ) => HydrateStaticStateFunction = (solutionType: SolutionType) => - composeFunction( - async (...params: HydrateStaticStateParameters) => { - const buildResult = await buildFactory(solutionType)( - ...(params as BuildParameters) - ); - const staticState = await hydrateStaticStateFactory( - solutionType - ).fromBuildResult({ - buildResult, - searchActions: params[0]!.searchActions, - }); - return staticState; - }, - { - fromBuildResult: async ( - ...params: HydrateStaticStateFromBuildResultParameters - ) => { - const [ - { - buildResult: {engine, controllers}, - searchActions, - }, - ] = params; - - // TODO: should be only one searchAction for search and listing - searchActions.forEach((action) => { - engine.dispatch(action); - }); - await engine.waitForRequestCompletedAction(); - return {engine, controllers}; - }, - } + const hydrateRecommendationStaticState = + hydrateRecommendationStaticStateFactory( + controllerDefinitions!, + getOptions() ); - const hydrateStaticStateFactoryForRecommendation: () => HydrateStaticStateFunction = - () => - composeFunction( - async (...params: HydrateStaticStateParameters) => { - const buildResult = (await buildFactory(SolutionType.recommendation)( - ...(params as BuildParameters) - )) as BuildResult; // TODO: check if can remove the cast - const staticState = - await hydrateStaticStateFactoryForRecommendation().fromBuildResult({ - buildResult, - searchActions: params[0]!.searchActions, - }); - return staticState; - }, - { - fromBuildResult: async ( - ...params: HydrateStaticStateFromBuildResultParameters - ) => { - const [ - { - buildResult: {engine, controllers}, - searchActions, - }, - ] = params; - - searchActions.forEach((action) => { - engine.dispatch(action); - }); - await engine.waitForRequestCompletedAction(); - return {engine, controllers}; - }, - } - ); - return { listingEngineDefinition: { - build: buildFactory(SolutionType.listing), - fetchStaticState: fetchStaticStateFactory(SolutionType.listing), - hydrateStaticState: hydrateStaticStateFactory(SolutionType.listing), + build: build(SolutionType.listing), + fetchStaticState: fetchStaticState(SolutionType.listing), + hydrateStaticState: hydrateStaticState(SolutionType.listing), setNavigatorContextProvider, } as CommerceEngineDefinition, searchEngineDefinition: { - build: buildFactory(SolutionType.search), - fetchStaticState: fetchStaticStateFactory(SolutionType.search), - hydrateStaticState: hydrateStaticStateFactory(SolutionType.search), + build: build(SolutionType.search), + fetchStaticState: fetchStaticState(SolutionType.search), + hydrateStaticState: hydrateStaticState(SolutionType.search), setNavigatorContextProvider, } as CommerceEngineDefinition, recommendationEngineDefinition: { - build: buildFactory(SolutionType.recommendation), - fetchStaticState: fetchStaticStateFactoryForRecommendation(), - hydrateStaticState: hydrateStaticStateFactoryForRecommendation(), + build: build(SolutionType.recommendation), // TODO: add count here + fetchStaticState: fetchRecommendationStaticState, + hydrateStaticState: hydrateRecommendationStaticState, setNavigatorContextProvider, } as CommerceEngineDefinition< TControllerDefinitions, @@ -469,9 +128,9 @@ export function defineCommerceEngine< >, // TODO: make the standaloneEngineDefinition not async since there are no search executed standaloneEngineDefinition: { - build: buildFactory(SolutionType.standalone), - fetchStaticState: fetchStaticStateFactory(SolutionType.standalone), - hydrateStaticState: hydrateStaticStateFactory(SolutionType.standalone), + build: build(SolutionType.standalone), + fetchStaticState: fetchStaticState(SolutionType.standalone), + hydrateStaticState: hydrateStaticState(SolutionType.standalone), setNavigatorContextProvider, } as CommerceEngineDefinition< TControllerDefinitions, diff --git a/packages/headless/src/app/commerce-ssr-engine/factories/build-factory.ts b/packages/headless/src/app/commerce-ssr-engine/factories/build-factory.ts new file mode 100644 index 00000000000..fcf386f7fa7 --- /dev/null +++ b/packages/headless/src/app/commerce-ssr-engine/factories/build-factory.ts @@ -0,0 +1,175 @@ +import {Action, UnknownAction} from '@reduxjs/toolkit'; +import {stateKey} from '../../../app/state-key.js'; +import {Controller} from '../../../controllers/controller/headless-controller.js'; +import { + createWaitForActionMiddleware, + createWaitForActionMiddlewareForRecommendation, +} from '../../../utils/utils.js'; +import { + buildCommerceEngine, + CommerceEngine, + CommerceEngineOptions, +} from '../../commerce-engine/commerce-engine.js'; +import {buildLogger} from '../../logger.js'; +import {buildControllerDefinitions} from '../common.js'; +import { + ControllerDefinitionsMap, + InferControllerPropsMapFromDefinitions, + SolutionType, +} from '../types/common.js'; +import { + BuildParameters, + EngineDefinitionOptions, +} from '../types/core-engine.js'; + +// TODO: rename +export interface RecommendationExtraOptions { + // TODO: rename + count: number; +} + +/** + * The SSR commerce engine. + */ +export interface SSRCommerceEngine extends CommerceEngine { + /** + * Waits for the search to be completed and returns a promise that resolves to a `SearchCompletedAction`. + */ + waitForRequestCompletedAction(): Promise[]; +} + +export type CommerceEngineDefinitionOptions< + TControllers extends ControllerDefinitionsMap, +> = EngineDefinitionOptions; + +function isListingFetchCompletedAction(action: unknown): action is Action { + return /^commerce\/productListing\/fetch\/(fulfilled|rejected)$/.test( + (action as UnknownAction).type + ); +} + +function isSearchCompletedAction(action: unknown): action is Action { + return /^commerce\/search\/executeSearch\/(fulfilled|rejected)$/.test( + (action as UnknownAction).type + ); +} + +function isRecommendationCompletedAction(action: unknown): action is Action { + return /^commerce\/recommendations\/fetch\/(fulfilled|rejected)$/.test( + (action as UnknownAction).type + ); +} + +function noSearchActionRequired(_action: unknown): _action is Action { + return true; +} + +function buildSSRCommerceEngine( + solutionType: SolutionType, + options: CommerceEngineOptions, + recommendationCount: number +): SSRCommerceEngine { + let actionCompletionMiddleware: ReturnType< + typeof createWaitForActionMiddleware + >; + + const middlewares: ReturnType[] = []; + const memo: Set = new Set(); + + switch (solutionType) { + case SolutionType.listing: + actionCompletionMiddleware = createWaitForActionMiddleware( + isListingFetchCompletedAction + ); + middlewares.push(actionCompletionMiddleware); + break; + case SolutionType.search: + actionCompletionMiddleware = createWaitForActionMiddleware( + isSearchCompletedAction + ); + middlewares.push(actionCompletionMiddleware); + break; + case SolutionType.recommendation: + middlewares.push( + ...Array.from({length: recommendationCount}, () => + createWaitForActionMiddlewareForRecommendation( + isRecommendationCompletedAction, + memo + ) + ) + ); + break; + case SolutionType.standalone: + actionCompletionMiddleware = createWaitForActionMiddleware( + noSearchActionRequired + ); + break; + default: + throw new Error('Unsupported solution type', solutionType); + } + + const commerceEngine = buildCommerceEngine({ + ...options, + middlewares: [ + ...(options.middlewares ?? []), + ...middlewares.map(({middleware}) => middleware), + ], + }); + + return { + ...commerceEngine, + + get [stateKey]() { + return commerceEngine[stateKey]; + }, + + waitForRequestCompletedAction() { + return [...middlewares.map(({promise}) => promise)]; + }, + }; +} + +export const buildFactory = + < + TControllerDefinitions extends ControllerDefinitionsMap< + SSRCommerceEngine, + Controller + >, + >( + controllerDefinitions: TControllerDefinitions, + options: CommerceEngineDefinitionOptions + ) => + ( + solutionType: T, + solutionTypeOptions?: RecommendationExtraOptions + ) => + async (...[buildOptions]: BuildParameters) => { + const logger = buildLogger(options.loggerOptions); + if (!options.navigatorContextProvider) { + logger.warn( + '[WARNING] Missing navigator context in server-side code. Make sure to set it with `setNavigatorContextProvider` before calling fetchStaticState()' + ); + } + const engine = buildSSRCommerceEngine( + solutionType, + buildOptions && 'extend' in buildOptions && buildOptions?.extend + ? await buildOptions.extend(options) + : options, + solutionType === SolutionType.recommendation + ? solutionTypeOptions?.count || 0 + : 0 + ); + const controllers = buildControllerDefinitions({ + definitionsMap: (controllerDefinitions ?? {}) as TControllerDefinitions, + engine, + solutionType, + propsMap: (buildOptions && 'controllers' in buildOptions + ? buildOptions.controllers + : {}) as InferControllerPropsMapFromDefinitions, + }); + + return { + engine, + controllers, + }; + }; diff --git a/packages/headless/src/app/commerce-ssr-engine/factories/hydrate-state-factory.ts b/packages/headless/src/app/commerce-ssr-engine/factories/hydrate-state-factory.ts new file mode 100644 index 00000000000..ba7bfeaf985 --- /dev/null +++ b/packages/headless/src/app/commerce-ssr-engine/factories/hydrate-state-factory.ts @@ -0,0 +1,68 @@ +import {composeFunction} from '../../ssr-engine/common.js'; +import {SolutionType} from '../types/common.js'; +import { + BuildParameters, + HydrateStaticStateFromBuildResultParameters, + HydrateStaticStateFunction, + HydrateStaticStateParameters, + CommerceControllerDefinitionsMap, +} from '../types/core-engine.js'; +import { + buildFactory, + CommerceEngineDefinitionOptions, +} from './build-factory.js'; + +export const hydrateStaticStateFactory: < + TControllerDefinitions extends CommerceControllerDefinitionsMap, +>( + controllerDefinitions: TControllerDefinitions, + options: CommerceEngineDefinitionOptions +) => ( + solutionType: SolutionType +) => HydrateStaticStateFunction = + ( + controllerDefinitions: TControllerDefinitions, + options: CommerceEngineDefinitionOptions + ) => + (solutionType: SolutionType) => + composeFunction( + async ( + ...params: HydrateStaticStateParameters + ) => { + const solutionTypeBuild = await buildFactory(controllerDefinitions, { + ...options, + })(solutionType); + const buildResult = await solutionTypeBuild( + ...(params as BuildParameters) + ); + const staticStateBuild = + await hydrateStaticStateFactory( + controllerDefinitions, + options + )(solutionType); + const staticState = await staticStateBuild.fromBuildResult({ + buildResult, + searchActions: params[0]!.searchActions, + }); + return staticState; + }, + { + fromBuildResult: async ( + ...params: HydrateStaticStateFromBuildResultParameters + ) => { + const [ + { + buildResult: {engine, controllers}, + searchActions, + }, + ] = params; + + // TODO: should be only one searchAction for search and listing + searchActions.forEach((action) => { + engine.dispatch(action); + }); + await engine.waitForRequestCompletedAction(); + return {engine, controllers}; + }, + } + ); diff --git a/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-hydrate-state-factory.ts b/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-hydrate-state-factory.ts new file mode 100644 index 00000000000..e2fbf28a605 --- /dev/null +++ b/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-hydrate-state-factory.ts @@ -0,0 +1,61 @@ +import {composeFunction} from '../../ssr-engine/common.js'; +import {SolutionType} from '../types/common.js'; +import { + BuildParameters, + BuildResult, + HydrateStaticStateFromBuildResultParameters, + HydrateStaticStateFunction, + HydrateStaticStateParameters, + CommerceControllerDefinitionsMap, +} from '../types/core-engine.js'; +import { + buildFactory, + CommerceEngineDefinitionOptions, +} from './build-factory.js'; + +export function hydrateRecommendationStaticStateFactory< + TControllerDefinitions extends CommerceControllerDefinitionsMap, +>( + controllerDefinitions: TControllerDefinitions, + options: CommerceEngineDefinitionOptions +): HydrateStaticStateFunction { + return composeFunction( + async (...params: HydrateStaticStateParameters) => { + const solutionTypeBuild = await buildFactory( + controllerDefinitions, + options + )(SolutionType.recommendation, {count: 1111}); // TODO: FIXME: + + const buildResult = (await solutionTypeBuild( + ...(params as BuildParameters) + )) as BuildResult; // TODO: check if can remove the cast + + const staticState = await hydrateRecommendationStaticStateFactory( + controllerDefinitions, + options + ).fromBuildResult({ + buildResult, + searchActions: params[0]!.searchActions, + }); + return staticState; + }, + { + fromBuildResult: async ( + ...params: HydrateStaticStateFromBuildResultParameters + ) => { + const [ + { + buildResult: {engine, controllers}, + searchActions, + }, + ] = params; + + searchActions.forEach((action) => { + engine.dispatch(action); + }); + await engine.waitForRequestCompletedAction(); + return {engine, controllers}; + }, + } + ); +} diff --git a/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts b/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts new file mode 100644 index 00000000000..39065944f4e --- /dev/null +++ b/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts @@ -0,0 +1,104 @@ +import {UnknownAction} from '@reduxjs/toolkit'; +import {buildLogger} from '../../logger.js'; +import {composeFunction} from '../../ssr-engine/common.js'; +import {createStaticState, filterRecommendationControllers} from '../common.js'; +import { + EngineStaticState, + InferControllerStaticStateMapFromDefinitionsWithSolutionType, + SolutionType, +} from '../types/common.js'; +import { + BuildResult, + Controllers, + FetchStaticStateFromBuildResultParameters, + FetchStaticStateFunction, + CommerceControllerDefinitionsMap, +} from '../types/core-engine.js'; +import { + buildFactory, + CommerceEngineDefinitionOptions, +} from './build-factory.js'; + +export function fetchRecommendationStaticStateFactory< + TControllerDefinitions extends CommerceControllerDefinitionsMap, +>( + controllerDefinitions: TControllerDefinitions, + options: CommerceEngineDefinitionOptions +): FetchStaticStateFunction { + type ControllerDefinitionKeys = keyof Controllers; + + const logger = buildLogger(options.loggerOptions); + + return composeFunction( + async (...params: [controllerKeys: Array]) => { + const [controllerKeys] = params; + const uniqueControllerKeys = Array.from(new Set(controllerKeys)); + if (uniqueControllerKeys.length !== controllerKeys.length) { + logger.warn( + '[WARNING] Duplicate controller keys detected in recommendation fetchStaticState call. Make sure to provide only unique controller keys.' + ); + } + + const validControllerNames = Object.keys(controllerDefinitions ?? {}); + const allowedRecommendationKeys = uniqueControllerKeys.filter( + (key: string) => validControllerNames.includes(key) + ); + + if (!options.navigatorContextProvider) { + logger.warn( + '[WARNING] Missing navigator context in server-side code. Make sure to set it with `setNavigatorContextProvider` before calling fetchStaticState()' + ); + } + + const solutionTypeBuild = await buildFactory( + controllerDefinitions, + options + )(SolutionType.recommendation, {count: 1111}); // TODO: FIXME: + + const buildResult = (await solutionTypeBuild( + ...params + )) as BuildResult; // TODO: check if can remove the cast + + const staticState = await fetchRecommendationStaticStateFactory( + controllerDefinitions, + options + ).fromBuildResult({ + buildResult, + recommendationControllerKeys: allowedRecommendationKeys, + }); + return staticState; + }, + { + fromBuildResult: async ( + ...params: FetchStaticStateFromBuildResultParameters + ) => { + const [ + { + buildResult: {engine, controllers}, + recommendationControllerKeys, + }, + ] = params; + + filterRecommendationControllers( + controllers, + controllerDefinitions ?? {} + ).refresh(recommendationControllerKeys); + + const searchActions = await Promise.all( + engine.waitForRequestCompletedAction() + ); + + return createStaticState({ + searchActions, + controllers, + }) as EngineStaticState< + UnknownAction, + InferControllerStaticStateMapFromDefinitionsWithSolutionType< + TControllerDefinitions, + SolutionType + > + >; + }, + } + ); +} diff --git a/packages/headless/src/app/commerce-ssr-engine/factories/static-state-factory.ts b/packages/headless/src/app/commerce-ssr-engine/factories/static-state-factory.ts new file mode 100644 index 00000000000..10d7f8de969 --- /dev/null +++ b/packages/headless/src/app/commerce-ssr-engine/factories/static-state-factory.ts @@ -0,0 +1,88 @@ +import {UnknownAction} from '@reduxjs/toolkit'; +import {buildProductListing} from '../../../controllers/commerce/product-listing/headless-product-listing.js'; +import {buildSearch} from '../../../controllers/commerce/search/headless-search.js'; +import {composeFunction} from '../../ssr-engine/common.js'; +import {createStaticState} from '../common.js'; +import { + EngineStaticState, + InferControllerStaticStateMapFromDefinitionsWithSolutionType, + SolutionType, +} from '../types/common.js'; +import { + FetchStaticStateFromBuildResultParameters, + FetchStaticStateFunction, + FetchStaticStateParameters, + CommerceControllerDefinitionsMap, +} from '../types/core-engine.js'; +import { + buildFactory, + CommerceEngineDefinitionOptions, +} from './build-factory.js'; + +// TODO: this is not a factory. either make it a factory or rename it +export const fetchStaticStateFactory: < + TControllerDefinitions extends CommerceControllerDefinitionsMap, +>( + controllerDefinitions: TControllerDefinitions, + options: CommerceEngineDefinitionOptions +) => ( + solutionType: SolutionType +) => FetchStaticStateFunction = + ( + controllerDefinitions: TControllerDefinitions, + options: CommerceEngineDefinitionOptions + ) => + (solutionType: SolutionType) => + composeFunction( + async (...params: FetchStaticStateParameters) => { + const solutionTypeBuild = await buildFactory(controllerDefinitions, { + ...options, + })(solutionType); + const buildResult = await solutionTypeBuild(...params); + const staticStateBuild = await fetchStaticStateFactory( + controllerDefinitions, + options + )(solutionType); + const staticState = await staticStateBuild.fromBuildResult({ + buildResult, + // recommendationControllerKeys // TODO: is missing + }); + return staticState; + }, + { + fromBuildResult: async ( + ...params: FetchStaticStateFromBuildResultParameters + ) => { + const [ + { + buildResult: {engine, controllers}, + }, + ] = params; + + switch (solutionType) { + case SolutionType.listing: + buildProductListing(engine).executeFirstRequest(); + break; + case SolutionType.search: + buildSearch(engine).executeFirstSearch(); + break; + } + + // TODO: should be only one searchAction for search and listing + const searchActions = await Promise.all( + engine.waitForRequestCompletedAction() + ); + + return createStaticState({ + searchActions, + controllers, + }) as EngineStaticState< + UnknownAction, + InferControllerStaticStateMapFromDefinitionsWithSolutionType< + TControllerDefinitions, + SolutionType + > + >; + }, + } + ); diff --git a/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts b/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts index e821601f3c6..37eba9acadd 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts @@ -1,5 +1,6 @@ import {UnknownAction} from '@reduxjs/toolkit'; import type {Controller} from '../../../controllers/controller/headless-controller.js'; +import {CommerceEngineDefinition} from '../../commerce-engine/commerce-engine.ssr.js'; import {EngineConfiguration} from '../../engine-configuration.js'; import {CoreEngine, CoreEngineNext} from '../../engine.js'; import {NavigatorContextProvider} from '../../navigatorContextProvider.js'; @@ -7,6 +8,7 @@ import type { FromBuildResult, FromBuildResultOptions, } from '../../ssr-engine/types/from-build-result.js'; +import {SSRCommerceEngine} from '../factories/build-factory.js'; import {Build, BuildOptions} from './build.js'; import { ControllerDefinitionsMap, @@ -118,3 +120,61 @@ export type InferBuildResult< build(...args: unknown[]): Promise; }, > = Awaited>; + +type CustomMap = ControllerDefinitionsMap; + +export type CommerceControllerDefinitionsMap = ControllerDefinitionsMap< + SSRCommerceEngine, + Controller +>; + +type Definition< + TControllerDefinitions extends CommerceControllerDefinitionsMap, +> = CommerceEngineDefinition; + +export type BuildFunction = + Definition['build']; + +export type FetchStaticStateFunction = + Definition['fetchStaticState']; + +export type HydrateStaticStateFunction< + TControllerDefinitions extends CustomMap, +> = Definition['hydrateStaticState']; + +export type FetchStaticStateFromBuildResultFunction< + TControllerDefinitions extends CustomMap, +> = FetchStaticStateFunction['fromBuildResult']; + +export type HydrateStaticStateFromBuildResultFunction< + TControllerDefinitions extends CustomMap, +> = HydrateStaticStateFunction['fromBuildResult']; + +export type BuildParameters = + Parameters>; + +export type FetchStaticStateParameters< + TControllerDefinitions extends CustomMap, +> = Parameters>; + +export type HydrateStaticStateParameters< + TControllerDefinitions extends CustomMap, +> = Parameters>; + +export type FetchStaticStateFromBuildResultParameters< + TControllerDefinitions extends CustomMap, +> = Parameters>; + +export type HydrateStaticStateFromBuildResultParameters< + TControllerDefinitions extends CustomMap, +> = Parameters< + HydrateStaticStateFromBuildResultFunction +>; + +export type Controllers = + InferControllersMapFromDefinition; + +export type BuildResult = { + engine: SSRCommerceEngine; + controllers: Controllers; +}; diff --git a/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts b/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts index d05a9cb5c15..9bc2dbf0699 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts @@ -1,5 +1,4 @@ import type {UnknownAction} from '@reduxjs/toolkit'; -import {buildBaseCommerceAPIRequest} from '../../../features/commerce/common/actions.js'; import {SolutionType} from '../../commerce-ssr-engine/types/common.js'; import type {EngineStaticState} from '../../commerce-ssr-engine/types/common.js'; import type {CoreEngine, CoreEngineNext} from '../../engine.js'; @@ -59,4 +58,3 @@ export type FetchStaticState< EngineStaticState >; }; -buildBaseCommerceAPIRequest; diff --git a/packages/headless/src/ssr-commerce.index.ts b/packages/headless/src/ssr-commerce.index.ts index 708cbbefe4f..7c35d848638 100644 --- a/packages/headless/src/ssr-commerce.index.ts +++ b/packages/headless/src/ssr-commerce.index.ts @@ -67,17 +67,18 @@ * @module SSR Commerce */ +export type { + CommerceEngineDefinitionOptions, + SSRCommerceEngine, +} from './app/commerce-ssr-engine/factories/build-factory.js'; + export type {Unsubscribe, Middleware} from '@reduxjs/toolkit'; export type {Relay} from '@coveo/relay'; // Main App export type {CommerceEngineOptions} from './app/commerce-engine/commerce-engine.js'; export type {CommerceEngineConfiguration} from './app/commerce-engine/commerce-engine-configuration.js'; -export type { - SSRCommerceEngine as CommerceEngine, - CommerceEngineDefinition, - CommerceEngineDefinitionOptions, -} from './app/commerce-engine/commerce-engine.ssr.js'; +export type {CommerceEngineDefinition} from './app/commerce-engine/commerce-engine.ssr.js'; export {defineCommerceEngine} from './app/commerce-engine/commerce-engine.ssr.js'; export {getSampleCommerceEngineConfiguration} from './app/commerce-engine/commerce-engine-configuration.js'; From 07d1ec8e41b15dd810e6f6097d840f14d2c86965 Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Sat, 16 Nov 2024 23:35:14 -0500 Subject: [PATCH 15/30] simplify build factory --- .../commerce-engine/commerce-engine.ssr.ts | 33 ++++++++----------- .../factories/build-factory.ts | 26 +++++---------- ...e-factory.ts => hydrated-state-factory.ts} | 5 ++- ... recommendation-hydrated-state-factory.ts} | 8 ++--- .../recommendation-static-state-factory.ts | 12 +++---- .../factories/static-state-factory.ts | 3 -- .../app/ssr-engine/types/from-build-result.ts | 6 ++-- packages/headless/src/test/mock-engine-v2.ts | 2 +- 8 files changed, 39 insertions(+), 56 deletions(-) rename packages/headless/src/app/commerce-ssr-engine/factories/{hydrate-state-factory.ts => hydrated-state-factory.ts} (92%) rename packages/headless/src/app/commerce-ssr-engine/factories/{recommendation-hydrate-state-factory.ts => recommendation-hydrated-state-factory.ts} (85%) diff --git a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts index 6538f7e5756..7ef14861c7c 100644 --- a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts +++ b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts @@ -14,8 +14,8 @@ import { CommerceEngineDefinitionOptions, SSRCommerceEngine, } from '../commerce-ssr-engine/factories/build-factory.js'; -import {hydrateStaticStateFactory} from '../commerce-ssr-engine/factories/hydrate-state-factory.js'; -import {hydrateRecommendationStaticStateFactory} from '../commerce-ssr-engine/factories/recommendation-hydrate-state-factory.js'; +import {hydratedStaticStateFactory} from '../commerce-ssr-engine/factories/hydrated-state-factory.js'; +import {hydratedRecommendationStaticStateFactory} from '../commerce-ssr-engine/factories/recommendation-hydrated-state-factory.js'; import {fetchRecommendationStaticStateFactory} from '../commerce-ssr-engine/factories/recommendation-static-state-factory.js'; import {fetchStaticStateFactory} from '../commerce-ssr-engine/factories/static-state-factory.js'; import { @@ -69,9 +69,7 @@ export function defineCommerceEngine< } { const {controllers: controllerDefinitions, ...engineOptions} = options; - const getOptions = () => { - return engineOptions; - }; + const getOptions = () => engineOptions; const setNavigatorContextProvider = ( navigatorContextProvider: NavigatorContextProvider @@ -87,7 +85,7 @@ export function defineCommerceEngine< controllerDefinitions!, getOptions() ); - const hydrateStaticState = hydrateStaticStateFactory( + const hydrateStaticState = hydratedStaticStateFactory( controllerDefinitions!, getOptions() ); @@ -95,11 +93,11 @@ export function defineCommerceEngine< const fetchRecommendationStaticState = fetchRecommendationStaticStateFactory( controllerDefinitions!, - getOptions() // TODO: add count here + getOptions() ); const hydrateRecommendationStaticState = - hydrateRecommendationStaticStateFactory( + hydratedRecommendationStaticStateFactory( controllerDefinitions!, getOptions() ); @@ -118,7 +116,7 @@ export function defineCommerceEngine< setNavigatorContextProvider, } as CommerceEngineDefinition, recommendationEngineDefinition: { - build: build(SolutionType.recommendation), // TODO: add count here + build: build(SolutionType.recommendation), fetchStaticState: fetchRecommendationStaticState, hydrateStaticState: hydrateRecommendationStaticState, setNavigatorContextProvider, @@ -126,7 +124,7 @@ export function defineCommerceEngine< TControllerDefinitions, SolutionType.recommendation >, - // TODO: make the standaloneEngineDefinition not async since there are no search executed + // TODO: The standaloneEngineDefinition should not be async since no request is sent to the API standaloneEngineDefinition: { build: build(SolutionType.standalone), fetchStaticState: fetchStaticState(SolutionType.standalone), @@ -150,8 +148,8 @@ export function defineCommerceEngine< // standaloneSearchBox: defineStandaloneSearchBox({ // options: {redirectionUrl: 'rest'}, // }), -// facets: defineContext(), -// searchParam: defineParameterManager({search: false}), +// // facets: defineContext(), +// // searchParam: defineParameterManager({search: false}), // trending: defineRecommendations({ // options: {slotId: 'ttt'}, // }), @@ -162,14 +160,11 @@ export function defineCommerceEngine< // }); // // TODO: should have a way to select which recommendation to fetch -// const r = await standaloneEngineDefinition.fetchStaticState({ -// controllers: {searchParam: {initialState: {parameters: {q: 'test'}}}}, -// }); +// const r = await standaloneEngineDefinition.fetchStaticState() + +// const b = await recommendationEngineDefinition.build(['']) -// const b = await recommendationEngineDefinition.fetchStaticState([ -// 'trending', -// ]); -// const b = await recommendationEngineDefinition.fetchStaticState.fromBuildResult({buildResult:{controllers:{}}}) +// const b = await recommendationEngineDefinition.fetchStaticState.fromBuildResult({}) // // b.controllers.; // const a = await searchEngineDefinition.fetchStaticState(); // TODO: fix typing if controller is set to {search: false} diff --git a/packages/headless/src/app/commerce-ssr-engine/factories/build-factory.ts b/packages/headless/src/app/commerce-ssr-engine/factories/build-factory.ts index fcf386f7fa7..09d1b84c038 100644 --- a/packages/headless/src/app/commerce-ssr-engine/factories/build-factory.ts +++ b/packages/headless/src/app/commerce-ssr-engine/factories/build-factory.ts @@ -19,15 +19,10 @@ import { } from '../types/common.js'; import { BuildParameters, + CommerceControllerDefinitionsMap, EngineDefinitionOptions, } from '../types/core-engine.js'; -// TODO: rename -export interface RecommendationExtraOptions { - // TODO: rename - count: number; -} - /** * The SSR commerce engine. */ @@ -130,19 +125,11 @@ function buildSSRCommerceEngine( } export const buildFactory = - < - TControllerDefinitions extends ControllerDefinitionsMap< - SSRCommerceEngine, - Controller - >, - >( + ( controllerDefinitions: TControllerDefinitions, options: CommerceEngineDefinitionOptions ) => - ( - solutionType: T, - solutionTypeOptions?: RecommendationExtraOptions - ) => + (solutionType: T) => async (...[buildOptions]: BuildParameters) => { const logger = buildLogger(options.loggerOptions); if (!options.navigatorContextProvider) { @@ -150,15 +137,18 @@ export const buildFactory = '[WARNING] Missing navigator context in server-side code. Make sure to set it with `setNavigatorContextProvider` before calling fetchStaticState()' ); } + const engine = buildSSRCommerceEngine( solutionType, buildOptions && 'extend' in buildOptions && buildOptions?.extend ? await buildOptions.extend(options) : options, - solutionType === SolutionType.recommendation - ? solutionTypeOptions?.count || 0 + solutionType === SolutionType.recommendation && + Array.isArray(buildOptions) + ? buildOptions.length : 0 ); + const controllers = buildControllerDefinitions({ definitionsMap: (controllerDefinitions ?? {}) as TControllerDefinitions, engine, diff --git a/packages/headless/src/app/commerce-ssr-engine/factories/hydrate-state-factory.ts b/packages/headless/src/app/commerce-ssr-engine/factories/hydrated-state-factory.ts similarity index 92% rename from packages/headless/src/app/commerce-ssr-engine/factories/hydrate-state-factory.ts rename to packages/headless/src/app/commerce-ssr-engine/factories/hydrated-state-factory.ts index ba7bfeaf985..2141cda25ea 100644 --- a/packages/headless/src/app/commerce-ssr-engine/factories/hydrate-state-factory.ts +++ b/packages/headless/src/app/commerce-ssr-engine/factories/hydrated-state-factory.ts @@ -12,7 +12,7 @@ import { CommerceEngineDefinitionOptions, } from './build-factory.js'; -export const hydrateStaticStateFactory: < +export const hydratedStaticStateFactory: < TControllerDefinitions extends CommerceControllerDefinitionsMap, >( controllerDefinitions: TControllerDefinitions, @@ -36,7 +36,7 @@ export const hydrateStaticStateFactory: < ...(params as BuildParameters) ); const staticStateBuild = - await hydrateStaticStateFactory( + await hydratedStaticStateFactory( controllerDefinitions, options )(solutionType); @@ -57,7 +57,6 @@ export const hydrateStaticStateFactory: < }, ] = params; - // TODO: should be only one searchAction for search and listing searchActions.forEach((action) => { engine.dispatch(action); }); diff --git a/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-hydrate-state-factory.ts b/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-hydrated-state-factory.ts similarity index 85% rename from packages/headless/src/app/commerce-ssr-engine/factories/recommendation-hydrate-state-factory.ts rename to packages/headless/src/app/commerce-ssr-engine/factories/recommendation-hydrated-state-factory.ts index e2fbf28a605..cd4f1b9755c 100644 --- a/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-hydrate-state-factory.ts +++ b/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-hydrated-state-factory.ts @@ -13,7 +13,7 @@ import { CommerceEngineDefinitionOptions, } from './build-factory.js'; -export function hydrateRecommendationStaticStateFactory< +export function hydratedRecommendationStaticStateFactory< TControllerDefinitions extends CommerceControllerDefinitionsMap, >( controllerDefinitions: TControllerDefinitions, @@ -24,13 +24,13 @@ export function hydrateRecommendationStaticStateFactory< const solutionTypeBuild = await buildFactory( controllerDefinitions, options - )(SolutionType.recommendation, {count: 1111}); // TODO: FIXME: + )(SolutionType.recommendation); const buildResult = (await solutionTypeBuild( ...(params as BuildParameters) - )) as BuildResult; // TODO: check if can remove the cast + )) as BuildResult; - const staticState = await hydrateRecommendationStaticStateFactory( + const staticState = await hydratedRecommendationStaticStateFactory( controllerDefinitions, options ).fromBuildResult({ diff --git a/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts b/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts index 39065944f4e..11949733709 100644 --- a/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts +++ b/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts @@ -53,18 +53,18 @@ export function fetchRecommendationStaticStateFactory< const solutionTypeBuild = await buildFactory( controllerDefinitions, options - )(SolutionType.recommendation, {count: 1111}); // TODO: FIXME: + )(SolutionType.recommendation); const buildResult = (await solutionTypeBuild( - ...params - )) as BuildResult; // TODO: check if can remove the cast + allowedRecommendationKeys + )) as BuildResult; const staticState = await fetchRecommendationStaticStateFactory( controllerDefinitions, options ).fromBuildResult({ buildResult, - recommendationControllerKeys: allowedRecommendationKeys, + allowedRecommendationKeys, }); return staticState; }, @@ -75,14 +75,14 @@ export function fetchRecommendationStaticStateFactory< const [ { buildResult: {engine, controllers}, - recommendationControllerKeys, + allowedRecommendationKeys, }, ] = params; filterRecommendationControllers( controllers, controllerDefinitions ?? {} - ).refresh(recommendationControllerKeys); + ).refresh(allowedRecommendationKeys); const searchActions = await Promise.all( engine.waitForRequestCompletedAction() diff --git a/packages/headless/src/app/commerce-ssr-engine/factories/static-state-factory.ts b/packages/headless/src/app/commerce-ssr-engine/factories/static-state-factory.ts index 10d7f8de969..448c822bb7d 100644 --- a/packages/headless/src/app/commerce-ssr-engine/factories/static-state-factory.ts +++ b/packages/headless/src/app/commerce-ssr-engine/factories/static-state-factory.ts @@ -19,7 +19,6 @@ import { CommerceEngineDefinitionOptions, } from './build-factory.js'; -// TODO: this is not a factory. either make it a factory or rename it export const fetchStaticStateFactory: < TControllerDefinitions extends CommerceControllerDefinitionsMap, >( @@ -45,7 +44,6 @@ export const fetchStaticStateFactory: < )(solutionType); const staticState = await staticStateBuild.fromBuildResult({ buildResult, - // recommendationControllerKeys // TODO: is missing }); return staticState; }, @@ -68,7 +66,6 @@ export const fetchStaticStateFactory: < break; } - // TODO: should be only one searchAction for search and listing const searchActions = await Promise.all( engine.waitForRequestCompletedAction() ); diff --git a/packages/headless/src/app/ssr-engine/types/from-build-result.ts b/packages/headless/src/app/ssr-engine/types/from-build-result.ts index 43212747352..ce380b391de 100644 --- a/packages/headless/src/app/ssr-engine/types/from-build-result.ts +++ b/packages/headless/src/app/ssr-engine/types/from-build-result.ts @@ -11,10 +11,12 @@ export interface FromBuildResultOptions< buildResult: EngineDefinitionBuildResult; /** * An optional array of keys representing the recommendation controllers to refresh. - * If a recommendation key defined in your engine definition is present in this list, a recommendation query will be triggered against the API. + * If a recommendation key defined in your engine definition is present in this list, the associate recommendation controller + * will query the API. + * * This is applicable only if the engine is a recommendation engine. */ - recommendationControllerKeys?: (keyof TControllers)[]; + allowedRecommendationKeys?: (keyof TControllers)[]; } export interface FromBuildResult< diff --git a/packages/headless/src/test/mock-engine-v2.ts b/packages/headless/src/test/mock-engine-v2.ts index dd43748f7a7..866548e7200 100644 --- a/packages/headless/src/test/mock-engine-v2.ts +++ b/packages/headless/src/test/mock-engine-v2.ts @@ -3,7 +3,7 @@ import {pino, Logger} from 'pino'; import {vi, Mock} from 'vitest'; import {CaseAssistEngine} from '../app/case-assist-engine/case-assist-engine.js'; import {CommerceEngine} from '../app/commerce-engine/commerce-engine.js'; -import {SSRCommerceEngine} from '../app/commerce-engine/commerce-engine.ssr.js'; +import {SSRCommerceEngine} from '../app/commerce-ssr-engine/factories/build-factory.js'; import type {CoreEngine, CoreEngineNext} from '../app/engine.js'; import {InsightEngine} from '../app/insight-engine/insight-engine.js'; import {defaultNodeJSNavigatorContextProvider} from '../app/navigatorContextProvider.js'; From 9b049fdac11da58f9580ca96ef2ce2b8cff33673 Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Sat, 16 Nov 2024 23:48:59 -0500 Subject: [PATCH 16/30] adjust factory params and throw error on bad engine definition --- .../src/app/commerce-engine/commerce-engine.ssr.ts | 10 +++++----- .../app/commerce-ssr-engine/factories/build-factory.ts | 2 +- .../factories/hydrated-state-factory.ts | 4 ++-- .../factories/recommendation-hydrated-state-factory.ts | 2 +- .../factories/recommendation-static-state-factory.ts | 2 +- .../factories/static-state-factory.ts | 8 ++++++-- 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts index 7ef14861c7c..56ce7a08b2b 100644 --- a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts +++ b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts @@ -78,27 +78,27 @@ export function defineCommerceEngine< }; const build = buildFactory( - controllerDefinitions!, // TODO: find a way to get rid of the ! + controllerDefinitions, getOptions() ); const fetchStaticState = fetchStaticStateFactory( - controllerDefinitions!, + controllerDefinitions, getOptions() ); const hydrateStaticState = hydratedStaticStateFactory( - controllerDefinitions!, + controllerDefinitions, getOptions() ); const fetchRecommendationStaticState = fetchRecommendationStaticStateFactory( - controllerDefinitions!, + controllerDefinitions, getOptions() ); const hydrateRecommendationStaticState = hydratedRecommendationStaticStateFactory( - controllerDefinitions!, + controllerDefinitions, getOptions() ); diff --git a/packages/headless/src/app/commerce-ssr-engine/factories/build-factory.ts b/packages/headless/src/app/commerce-ssr-engine/factories/build-factory.ts index 09d1b84c038..f9bc6535353 100644 --- a/packages/headless/src/app/commerce-ssr-engine/factories/build-factory.ts +++ b/packages/headless/src/app/commerce-ssr-engine/factories/build-factory.ts @@ -126,7 +126,7 @@ function buildSSRCommerceEngine( export const buildFactory = ( - controllerDefinitions: TControllerDefinitions, + controllerDefinitions: TControllerDefinitions | undefined, options: CommerceEngineDefinitionOptions ) => (solutionType: T) => diff --git a/packages/headless/src/app/commerce-ssr-engine/factories/hydrated-state-factory.ts b/packages/headless/src/app/commerce-ssr-engine/factories/hydrated-state-factory.ts index 2141cda25ea..b004b174ea6 100644 --- a/packages/headless/src/app/commerce-ssr-engine/factories/hydrated-state-factory.ts +++ b/packages/headless/src/app/commerce-ssr-engine/factories/hydrated-state-factory.ts @@ -15,13 +15,13 @@ import { export const hydratedStaticStateFactory: < TControllerDefinitions extends CommerceControllerDefinitionsMap, >( - controllerDefinitions: TControllerDefinitions, + controllerDefinitions: TControllerDefinitions | undefined, options: CommerceEngineDefinitionOptions ) => ( solutionType: SolutionType ) => HydrateStaticStateFunction = ( - controllerDefinitions: TControllerDefinitions, + controllerDefinitions: TControllerDefinitions | undefined, options: CommerceEngineDefinitionOptions ) => (solutionType: SolutionType) => diff --git a/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-hydrated-state-factory.ts b/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-hydrated-state-factory.ts index cd4f1b9755c..321880e1432 100644 --- a/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-hydrated-state-factory.ts +++ b/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-hydrated-state-factory.ts @@ -16,7 +16,7 @@ import { export function hydratedRecommendationStaticStateFactory< TControllerDefinitions extends CommerceControllerDefinitionsMap, >( - controllerDefinitions: TControllerDefinitions, + controllerDefinitions: TControllerDefinitions | undefined, options: CommerceEngineDefinitionOptions ): HydrateStaticStateFunction { return composeFunction( diff --git a/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts b/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts index 11949733709..3bb87a8ed09 100644 --- a/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts +++ b/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts @@ -22,7 +22,7 @@ import { export function fetchRecommendationStaticStateFactory< TControllerDefinitions extends CommerceControllerDefinitionsMap, >( - controllerDefinitions: TControllerDefinitions, + controllerDefinitions: TControllerDefinitions | undefined, options: CommerceEngineDefinitionOptions ): FetchStaticStateFunction { type ControllerDefinitionKeys = keyof Controllers; diff --git a/packages/headless/src/app/commerce-ssr-engine/factories/static-state-factory.ts b/packages/headless/src/app/commerce-ssr-engine/factories/static-state-factory.ts index 448c822bb7d..5ee3776eb31 100644 --- a/packages/headless/src/app/commerce-ssr-engine/factories/static-state-factory.ts +++ b/packages/headless/src/app/commerce-ssr-engine/factories/static-state-factory.ts @@ -22,13 +22,13 @@ import { export const fetchStaticStateFactory: < TControllerDefinitions extends CommerceControllerDefinitionsMap, >( - controllerDefinitions: TControllerDefinitions, + controllerDefinitions: TControllerDefinitions | undefined, options: CommerceEngineDefinitionOptions ) => ( solutionType: SolutionType ) => FetchStaticStateFunction = ( - controllerDefinitions: TControllerDefinitions, + controllerDefinitions: TControllerDefinitions | undefined, options: CommerceEngineDefinitionOptions ) => (solutionType: SolutionType) => @@ -64,6 +64,10 @@ export const fetchStaticStateFactory: < case SolutionType.search: buildSearch(engine).executeFirstSearch(); break; + case SolutionType.recommendation: + throw new Error( + 'You are using the wrong engine definition. For recommendations, use the recommendation engine definition instead' + ); } const searchActions = await Promise.all( From 84e5c53397009743deda20411852e741ce71dac2 Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Sun, 17 Nov 2024 00:42:26 -0500 Subject: [PATCH 17/30] do not ask props for disabled controllers --- .../commerce-engine/commerce-engine.ssr.ts | 19 ++++++++------- .../src/app/commerce-ssr-engine/common.ts | 8 +++---- .../recommendation-static-state-factory.ts | 3 ++- .../app/commerce-ssr-engine/types/build.ts | 20 ++++++++++++---- .../app/commerce-ssr-engine/types/common.ts | 24 +++++++++++++++++-- .../commerce-ssr-engine/types/core-engine.ts | 5 ++-- .../types/fetch-static-state.ts | 20 ++++++++++++---- .../types/hydrate-static-state.ts | 18 +++++++++++--- .../ssr-engine/types/fetch-static-state.ts | 2 +- 9 files changed, 89 insertions(+), 30 deletions(-) diff --git a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts index 56ce7a08b2b..e4f63467236 100644 --- a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts +++ b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts @@ -137,19 +137,20 @@ export function defineCommerceEngine< }; } -// Sandbox +/* Sandbox */ // const { // recommendationEngineDefinition, // searchEngineDefinition, // standaloneEngineDefinition, +// listingEngineDefinition // } = defineCommerceEngine({ // configuration: getSampleCommerceEngineConfiguration(), // controllers: { -// standaloneSearchBox: defineStandaloneSearchBox({ -// options: {redirectionUrl: 'rest'}, -// }), -// // facets: defineContext(), -// // searchParam: defineParameterManager({search: false}), +// // standaloneSearchBox: defineStandaloneSearchBox({ +// // options: {redirectionUrl: 'rest'}, +// // }), +// // context: defineContext(), +// searchParam: defineParameterManager({search: false}), // trending: defineRecommendations({ // options: {slotId: 'ttt'}, // }), @@ -160,11 +161,11 @@ export function defineCommerceEngine< // }); // // TODO: should have a way to select which recommendation to fetch -// const r = await standaloneEngineDefinition.fetchStaticState() +// const r = await listingEngineDefinition.hydrateStaticState({}) // TODO: now do the same with hydration -// const b = await recommendationEngineDefinition.build(['']) +// const b = await standaloneEngineDefinition.fetchStaticState() -// const b = await recommendationEngineDefinition.fetchStaticState.fromBuildResult({}) +// const b = await recommendationEngineDefinition.fetchStaticState(['popular']) // // b.controllers.; // const a = await searchEngineDefinition.fetchStaticState(); // TODO: fix typing if controller is set to {search: false} diff --git a/packages/headless/src/app/commerce-ssr-engine/common.ts b/packages/headless/src/app/commerce-ssr-engine/common.ts index 7575ee88b31..d2d4b7e60c0 100644 --- a/packages/headless/src/app/commerce-ssr-engine/common.ts +++ b/packages/headless/src/app/commerce-ssr-engine/common.ts @@ -1,4 +1,5 @@ import {UnknownAction} from '@reduxjs/toolkit'; +import {Logger} from 'pino'; import {Recommendations} from '../../controllers/commerce/recommendations/headless-recommendations.js'; import {RecommendationsDefinitionMeta} from '../../controllers/commerce/recommendations/headless-recommendations.ssr.js'; import {Controller} from '../../controllers/controller/headless-controller.js'; @@ -144,7 +145,8 @@ export function filterRecommendationControllers< TControllerDefinitions extends ControllerDefinitionsMap, >( controllers: Record, // TODO: or InferControllersMapFromDefinition - controllerDefinitions: TControllerDefinitions + controllerDefinitions: TControllerDefinitions, + logger: Logger ) { const slotIdSet = new Set(); @@ -160,7 +162,7 @@ export function filterRecommendationControllers< }; const warnDuplicateRecommendation = (slotId: string, productId?: string) => { - console.warn( + logger.warn( 'Multiple recommendation controllers found for the same slotId and productId', {slotId, productId} ); @@ -195,8 +197,6 @@ export function filterRecommendationControllers< if (whitelist === undefined) { return; } - // TODO: FIND a better way - // TODO: do not refresh multiple recommendation controllers for the same slotId and productId const isRecommendationController = (key: string) => name.includes(key) && whitelist.includes(key); diff --git a/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts b/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts index 3bb87a8ed09..3432e762a78 100644 --- a/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts +++ b/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts @@ -81,7 +81,8 @@ export function fetchRecommendationStaticStateFactory< filterRecommendationControllers( controllers, - controllerDefinitions ?? {} + controllerDefinitions ?? {}, + logger ).refresh(allowedRecommendationKeys); const searchActions = await Promise.all( diff --git a/packages/headless/src/app/commerce-ssr-engine/types/build.ts b/packages/headless/src/app/commerce-ssr-engine/types/build.ts index a3b5fe1a238..3333d066720 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/build.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/build.ts @@ -1,13 +1,17 @@ +import type {Controller} from '../../../controllers/controller/headless-controller.js'; import type {CoreEngine, CoreEngineNext} from '../../engine.js'; import type { ControllersMap, ControllersPropsMap, EngineDefinitionBuildResult, - EngineDefinitionControllersPropsOption, OptionsExtender, OptionsTuple, } from '../../ssr-engine/types/common.js'; -import {SolutionType} from './common.js'; +import { + ControllerDefinitionsMap, + EngineDefinitionControllersPropsOption, + SolutionType, +} from './common.js'; export interface BuildOptions { extend?: OptionsExtender; @@ -18,6 +22,10 @@ export type Build< TEngineOptions, TControllersMap extends ControllersMap, TControllersProps extends ControllersPropsMap, + TControllersDefinitionsMap extends ControllerDefinitionsMap< + TEngine, + Controller + >, TSolutionType extends SolutionType, > = TSolutionType extends SolutionType.recommendation ? { @@ -25,7 +33,7 @@ export type Build< * Initializes an engine and controllers from the definition. */ ( - c: (keyof TControllersMap)[] + controllers: (keyof TControllersMap)[] ): Promise>; } : { @@ -35,7 +43,11 @@ export type Build< ( ...params: OptionsTuple< BuildOptions & - EngineDefinitionControllersPropsOption + EngineDefinitionControllersPropsOption< + TControllersDefinitionsMap, + TControllersProps, + TSolutionType + > > ): Promise>; }; diff --git a/packages/headless/src/app/commerce-ssr-engine/types/common.ts b/packages/headless/src/app/commerce-ssr-engine/types/common.ts index 74cad63a186..0f6b6a7bb94 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/common.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/common.ts @@ -10,14 +10,14 @@ import type { InferControllerPropsMapFromDefinitions, ControllerStaticStateMap, EngineDefinitionBuildResult, - EngineDefinitionControllersPropsOption, HydratedState, OptionsTuple, + ControllersPropsMap, + HasKeys, } from '../../ssr-engine/types/common.js'; export type { EngineDefinitionBuildResult, - EngineDefinitionControllersPropsOption, HydratedState, OptionsTuple, InferControllerStaticStateFromController, @@ -149,6 +149,26 @@ export type InferControllerStaticStateMapFromDefinitionsWithSolutionType< >; }; +export type EngineDefinitionControllersPropsOption< + TControllers extends ControllerDefinitionsMap< + CoreEngine | CoreEngineNext, + Controller + >, + TControllersPropsMap extends ControllersPropsMap, + TSolutionType extends SolutionType, +> = { + [K in keyof TControllers as HasKey< + TControllers[K], + TSolutionType + > extends never + ? never + : K]: HasKeys extends false + ? {} + : { + controllers: TControllersPropsMap; + }; +}; + export interface ControllerDefinitionOption { /** * Whether the controller will be used in a product listing context. diff --git a/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts b/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts index 37eba9acadd..4b5730db093 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts @@ -26,8 +26,6 @@ import { HydrateStaticStateOptions, } from './hydrate-static-state.js'; -// TODO: why not use the one for commerce - export type { FromBuildResult, FromBuildResultOptions, @@ -69,6 +67,7 @@ export interface EngineDefinition< TSolutionType >, InferControllerPropsMapFromDefinitions, + TControllers, TSolutionType >; /** @@ -79,6 +78,7 @@ export interface EngineDefinition< InferControllersMapFromDefinition, UnknownAction, InferControllerPropsMapFromDefinitions, + TControllers, TSolutionType >; /** @@ -89,6 +89,7 @@ export interface EngineDefinition< TEngineOptions, InferControllersMapFromDefinition, InferControllerPropsMapFromDefinitions, + TControllers, TSolutionType >; diff --git a/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts b/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts index 9bc2dbf0699..2469152c61c 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts @@ -1,12 +1,16 @@ import type {UnknownAction} from '@reduxjs/toolkit'; +import type {Controller} from '../../../controllers/controller/headless-controller.js'; import {SolutionType} from '../../commerce-ssr-engine/types/common.js'; -import type {EngineStaticState} from '../../commerce-ssr-engine/types/common.js'; +import type { + ControllerDefinitionsMap, + EngineDefinitionControllersPropsOption, + EngineStaticState, +} from '../../commerce-ssr-engine/types/common.js'; import type {CoreEngine, CoreEngineNext} from '../../engine.js'; import type { ControllersMap, ControllersPropsMap, ControllerStaticStateMap, - EngineDefinitionControllersPropsOption, OptionsTuple, } from '../../ssr-engine/types/common.js'; import type {FromBuildResult} from '../../ssr-engine/types/from-build-result.js'; @@ -19,6 +23,10 @@ export type FetchStaticState< TSearchAction extends UnknownAction, TControllersStaticState extends ControllerStaticStateMap, TControllersProps extends ControllersPropsMap, + TControllersDefinitionsMap extends ControllerDefinitionsMap< + TEngine, + Controller + >, TSolutionType extends SolutionType, > = TSolutionType extends SolutionType.recommendation ? { @@ -28,7 +36,7 @@ export type FetchStaticState< * Useful for static generation and server-side rendering. */ ( - controllers: Array // TODO: make the array unique + controllers: Array ): Promise>; fromBuildResult: FromBuildResult< @@ -47,7 +55,11 @@ export type FetchStaticState< ( ...params: OptionsTuple< FetchStaticStateOptions & - EngineDefinitionControllersPropsOption + EngineDefinitionControllersPropsOption< + TControllersDefinitionsMap, + TControllersProps, + TSolutionType + > > ): Promise>; diff --git a/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts b/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts index 34669305f9d..0896a068a03 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts @@ -1,14 +1,18 @@ import type {UnknownAction} from '@reduxjs/toolkit'; +import type {Controller} from '../../../controllers/controller/headless-controller.js'; import type {CoreEngine, CoreEngineNext} from '../../engine.js'; import type { ControllersMap, ControllersPropsMap, - EngineDefinitionControllersPropsOption, HydratedState, OptionsTuple, } from '../../ssr-engine/types/common.js'; import type {FromBuildResult} from '../../ssr-engine/types/from-build-result.js'; -import {SolutionType} from './common.js'; +import { + ControllerDefinitionsMap, + EngineDefinitionControllersPropsOption, + SolutionType, +} from './common.js'; export interface HydrateStaticStateOptions { searchActions: TSearchAction[]; @@ -19,6 +23,10 @@ export type HydrateStaticState< TControllers extends ControllersMap, TSearchAction extends UnknownAction, TControllersProps extends ControllersPropsMap, + TControllersDefinitionsMap extends ControllerDefinitionsMap< + TEngine, + Controller + >, TSolutionType extends SolutionType, > = TSolutionType extends SolutionType.recommendation ? { @@ -47,7 +55,11 @@ export type HydrateStaticState< ( ...params: OptionsTuple< HydrateStaticStateOptions & - EngineDefinitionControllersPropsOption + EngineDefinitionControllersPropsOption< + TControllersDefinitionsMap, + TControllersProps, + TSolutionType + > > ): Promise>; diff --git a/packages/headless/src/app/ssr-engine/types/fetch-static-state.ts b/packages/headless/src/app/ssr-engine/types/fetch-static-state.ts index ad02be7dba8..bc0ade2d47a 100644 --- a/packages/headless/src/app/ssr-engine/types/fetch-static-state.ts +++ b/packages/headless/src/app/ssr-engine/types/fetch-static-state.ts @@ -53,7 +53,7 @@ export type FetchStaticStateWithList< * Useful for static generation and server-side rendering. */ ( - c: (keyof TControllers)[], + controllers: (keyof TControllers)[], ...params: OptionsTuple< FetchStaticStateOptions & EngineDefinitionControllersPropsOption From 018c0be01999d7896def20a77e64c0cce5e62d14 Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Sun, 17 Nov 2024 00:43:21 -0500 Subject: [PATCH 18/30] remove comments --- .../commerce-engine/commerce-engine.ssr.ts | 43 ------------------- 1 file changed, 43 deletions(-) diff --git a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts index e4f63467236..a82fc09ceb6 100644 --- a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts +++ b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts @@ -2,13 +2,6 @@ * Utility functions to be used for Commerce Server Side Rendering. */ import type {Controller} from '../../controllers/controller/headless-controller.js'; -// import { -// defineContext, -// defineParameterManager, -// defineRecommendations, -// defineStandaloneSearchBox, -// getSampleCommerceEngineConfiguration, -// } from '../../ssr-commerce.index.js'; import { buildFactory, CommerceEngineDefinitionOptions, @@ -89,13 +82,11 @@ export function defineCommerceEngine< controllerDefinitions, getOptions() ); - const fetchRecommendationStaticState = fetchRecommendationStaticStateFactory( controllerDefinitions, getOptions() ); - const hydrateRecommendationStaticState = hydratedRecommendationStaticStateFactory( controllerDefinitions, @@ -136,37 +127,3 @@ export function defineCommerceEngine< >, }; } - -/* Sandbox */ -// const { -// recommendationEngineDefinition, -// searchEngineDefinition, -// standaloneEngineDefinition, -// listingEngineDefinition -// } = defineCommerceEngine({ -// configuration: getSampleCommerceEngineConfiguration(), -// controllers: { -// // standaloneSearchBox: defineStandaloneSearchBox({ -// // options: {redirectionUrl: 'rest'}, -// // }), -// // context: defineContext(), -// searchParam: defineParameterManager({search: false}), -// trending: defineRecommendations({ -// options: {slotId: 'ttt'}, -// }), -// popular: defineRecommendations({ -// options: {slotId: 'ppp'}, -// }), -// }, -// }); - -// // TODO: should have a way to select which recommendation to fetch -// const r = await listingEngineDefinition.hydrateStaticState({}) // TODO: now do the same with hydration - -// const b = await standaloneEngineDefinition.fetchStaticState() - -// const b = await recommendationEngineDefinition.fetchStaticState(['popular']) -// // b.controllers.; - -// const a = await searchEngineDefinition.fetchStaticState(); // TODO: fix typing if controller is set to {search: false} -// // a.controllers.; // TODO: should throw an error since it's not defined in search From a7200d87f37deb07b20eca55a3489b746ed14731 Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Sun, 17 Nov 2024 00:47:44 -0500 Subject: [PATCH 19/30] update warning message --- .../app/commerce-ssr-engine/factories/static-state-factory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/headless/src/app/commerce-ssr-engine/factories/static-state-factory.ts b/packages/headless/src/app/commerce-ssr-engine/factories/static-state-factory.ts index 5ee3776eb31..b152e585798 100644 --- a/packages/headless/src/app/commerce-ssr-engine/factories/static-state-factory.ts +++ b/packages/headless/src/app/commerce-ssr-engine/factories/static-state-factory.ts @@ -66,7 +66,7 @@ export const fetchStaticStateFactory: < break; case SolutionType.recommendation: throw new Error( - 'You are using the wrong engine definition. For recommendations, use the recommendation engine definition instead' + '[WARNING] Invalid engine definition. For recommendations, use the recommendation engine definition instead' ); } From 785951f84a4399371a146894d6d5faf78c5e0402 Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Sun, 17 Nov 2024 00:49:43 -0500 Subject: [PATCH 20/30] fix export --- packages/headless/src/ssr-commerce.index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/headless/src/ssr-commerce.index.ts b/packages/headless/src/ssr-commerce.index.ts index 7c35d848638..aac2508d21c 100644 --- a/packages/headless/src/ssr-commerce.index.ts +++ b/packages/headless/src/ssr-commerce.index.ts @@ -69,7 +69,7 @@ export type { CommerceEngineDefinitionOptions, - SSRCommerceEngine, + SSRCommerceEngine as CommerceEngine, } from './app/commerce-ssr-engine/factories/build-factory.js'; export type {Unsubscribe, Middleware} from '@reduxjs/toolkit'; From f342da5932de12cbccfe69dc0e5ee5a7ae13e943 Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Mon, 18 Nov 2024 07:13:55 +0100 Subject: [PATCH 21/30] clean PR --- .../src/app/commerce-ssr-engine/common.ts | 72 ----------------- .../recommendation-static-state-factory.ts | 78 ++++++++++++++++++- .../app/commerce-ssr-engine/types/common.ts | 2 - .../ssr-engine/types/fetch-static-state.ts | 29 ------- .../headless-core-parameter-manager.ssr.ts | 1 - packages/headless/src/utils/utils.ts | 19 ++--- .../app/(listing)/[category]/page.tsx | 17 +--- .../app/_components/pages/recommendation.tsx | 50 ------------ .../components/pages/product-page.tsx | 2 + .../components/providers/listing-provider.tsx | 4 +- .../providers/recommendation-provider.tsx | 60 -------------- .../lib/commerce-engine-config.ts | 14 ---- .../lib/commerce-engine.ts | 8 -- 13 files changed, 88 insertions(+), 268 deletions(-) delete mode 100644 packages/samples/headless-ssr-commerce/app/_components/pages/recommendation.tsx delete mode 100644 packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx diff --git a/packages/headless/src/app/commerce-ssr-engine/common.ts b/packages/headless/src/app/commerce-ssr-engine/common.ts index d2d4b7e60c0..377b79424d5 100644 --- a/packages/headless/src/app/commerce-ssr-engine/common.ts +++ b/packages/headless/src/app/commerce-ssr-engine/common.ts @@ -1,7 +1,4 @@ import {UnknownAction} from '@reduxjs/toolkit'; -import {Logger} from 'pino'; -import {Recommendations} from '../../controllers/commerce/recommendations/headless-recommendations.js'; -import {RecommendationsDefinitionMeta} from '../../controllers/commerce/recommendations/headless-recommendations.ssr.js'; import {Controller} from '../../controllers/controller/headless-controller.js'; import {InvalidControllerDefinition} from '../../utils/errors.js'; import {clone, filterObject, mapObject} from '../../utils/utils.js'; @@ -139,72 +136,3 @@ export function ensureAtLeastOneSolutionType( throw new InvalidControllerDefinition(); } } - -export function filterRecommendationControllers< - TEngine extends CoreEngine | CoreEngineNext, - TControllerDefinitions extends ControllerDefinitionsMap, ->( - controllers: Record, // TODO: or InferControllersMapFromDefinition - controllerDefinitions: TControllerDefinitions, - logger: Logger -) { - const slotIdSet = new Set(); - - const isRecommendationDefinition = < - C extends ControllerDefinition, - >( - controllerDefinition: C - ): controllerDefinition is C & RecommendationsDefinitionMeta => { - return ( - 'recommendation' in controllerDefinition && - controllerDefinition.recommendation === true - ); - }; - - const warnDuplicateRecommendation = (slotId: string, productId?: string) => { - logger.warn( - 'Multiple recommendation controllers found for the same slotId and productId', - {slotId, productId} - ); - }; - - const filtered = Object.entries(controllerDefinitions).filter( - ([_, value]) => { - if (!isRecommendationDefinition(value)) { - return false; - } - const {slotId, productId} = value.options; - const key = `${slotId}${productId || ''}`; - if (slotIdSet.has(key)) { - warnDuplicateRecommendation(slotId, productId); - return false; - } - slotIdSet.add(key); - return true; - } - ); - - const name = filtered.map(([name, _]) => name); - - return { - /** - * Go through all the controllers passed in argument and only refresh recommendation controllers. - * - * @param controllers - A record of all controllers where the key is the controller name and the value is the controller instance. - * @param controllerNames - A list of all recommendation controllers to refresh - */ - refresh(whitelist?: string[]) { - if (whitelist === undefined) { - return; - } - const isRecommendationController = (key: string) => - name.includes(key) && whitelist.includes(key); - - Object.entries(controllers) - .filter(([key, _]) => isRecommendationController(key)) - .forEach(([_, controller]) => - (controller as Recommendations).refresh?.() - ); - }, - }; -} diff --git a/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts b/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts index 3432e762a78..2d58a08c4ba 100644 --- a/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts +++ b/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts @@ -1,8 +1,15 @@ import {UnknownAction} from '@reduxjs/toolkit'; +import {Logger} from 'pino'; +import {Recommendations} from '../../../controllers/commerce/recommendations/headless-recommendations.js'; +import {RecommendationsDefinitionMeta} from '../../../controllers/commerce/recommendations/headless-recommendations.ssr.js'; +import {Controller} from '../../../controllers/controller/headless-controller.js'; +import {CoreEngine, CoreEngineNext} from '../../engine.js'; import {buildLogger} from '../../logger.js'; import {composeFunction} from '../../ssr-engine/common.js'; -import {createStaticState, filterRecommendationControllers} from '../common.js'; +import {createStaticState} from '../common.js'; import { + ControllerDefinition, + ControllerDefinitionsMap, EngineStaticState, InferControllerStaticStateMapFromDefinitionsWithSolutionType, SolutionType, @@ -103,3 +110,72 @@ export function fetchRecommendationStaticStateFactory< } ); } + +function filterRecommendationControllers< + TEngine extends CoreEngine | CoreEngineNext, + TControllerDefinitions extends ControllerDefinitionsMap, +>( + controllers: Record, // TODO: or InferControllersMapFromDefinition + controllerDefinitions: TControllerDefinitions, + logger: Logger +) { + const slotIdSet = new Set(); + + const isRecommendationDefinition = < + C extends ControllerDefinition, + >( + controllerDefinition: C + ): controllerDefinition is C & RecommendationsDefinitionMeta => { + return ( + 'recommendation' in controllerDefinition && + controllerDefinition.recommendation === true + ); + }; + + const warnDuplicateRecommendation = (slotId: string, productId?: string) => { + logger.warn( + 'Multiple recommendation controllers found for the same slotId and productId', + {slotId, productId} + ); + }; + + const filtered = Object.entries(controllerDefinitions).filter( + ([_, value]) => { + if (!isRecommendationDefinition(value)) { + return false; + } + const {slotId, productId} = value.options; + const key = `${slotId}${productId || ''}`; + if (slotIdSet.has(key)) { + warnDuplicateRecommendation(slotId, productId); + return false; + } + slotIdSet.add(key); + return true; + } + ); + + const name = filtered.map(([name, _]) => name); + + return { + /** + * Go through all the controllers passed in argument and only refresh recommendation controllers. + * + * @param controllers - A record of all controllers where the key is the controller name and the value is the controller instance. + * @param controllerNames - A list of all recommendation controllers to refresh + */ + refresh(whitelist?: string[]) { + if (whitelist === undefined) { + return; + } + const isRecommendationController = (key: string) => + name.includes(key) && whitelist.includes(key); + + Object.entries(controllers) + .filter(([key, _]) => isRecommendationController(key)) + .forEach(([_, controller]) => + (controller as Recommendations).refresh?.() + ); + }, + }; +} diff --git a/packages/headless/src/app/commerce-ssr-engine/types/common.ts b/packages/headless/src/app/commerce-ssr-engine/types/common.ts index 0f6b6a7bb94..087b02b50d2 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/common.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/common.ts @@ -296,7 +296,6 @@ export type SubControllerDefinitionWithoutProps< : TDefinition extends {listing: false; search: false} ? InvalidControllerDefinition : never; -// TODO: add recommendation type here! export type SubControllerDefinitionWithProps< TController extends Controller, @@ -311,4 +310,3 @@ export type SubControllerDefinitionWithProps< : TDefinition extends {listing: false; search: false} ? InvalidControllerDefinition : never; -// TODO: add recommendation type here! diff --git a/packages/headless/src/app/ssr-engine/types/fetch-static-state.ts b/packages/headless/src/app/ssr-engine/types/fetch-static-state.ts index bc0ade2d47a..a1f87a7781a 100644 --- a/packages/headless/src/app/ssr-engine/types/fetch-static-state.ts +++ b/packages/headless/src/app/ssr-engine/types/fetch-static-state.ts @@ -38,32 +38,3 @@ export type FetchStaticState< EngineStaticState >; }; - -// TODO: find a better name -export type FetchStaticStateWithList< - TEngine extends CoreEngine | CoreEngineNext, - TControllers extends ControllersMap, - TSearchAction extends UnknownAction, - TControllersStaticState extends ControllerStaticStateMap, - TControllersProps extends ControllersPropsMap, -> = { - /** - * Executes only the initial search for a given configuration, then returns a resumable snapshot of engine state along with the state of the controllers. - * - * Useful for static generation and server-side rendering. - */ - ( - controllers: (keyof TControllers)[], - ...params: OptionsTuple< - FetchStaticStateOptions & - EngineDefinitionControllersPropsOption - > - ): Promise>; - - fromBuildResult: FromBuildResult< - TEngine, - TControllers, - FetchStaticStateOptions, - EngineStaticState - >; -}; diff --git a/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts b/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts index 4b783b7453b..2a4917db7d0 100644 --- a/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts +++ b/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts @@ -46,7 +46,6 @@ export function defineParameterManager< >(options?: TOptions) { ensureAtLeastOneSolutionType(options); return { - // TODO: we do now want to have to set the props if we set standalone to false... same thing with other components like cart ...options, buildWithProps: (engine, props, solutionType) => { if (solutionType === SolutionType.listing) { diff --git a/packages/headless/src/utils/utils.ts b/packages/headless/src/utils/utils.ts index ee2ec13dd87..99af274dd43 100644 --- a/packages/headless/src/utils/utils.ts +++ b/packages/headless/src/utils/utils.ts @@ -162,13 +162,17 @@ export function createWaitForActionMiddleware( function isRecommendationActionPayload

( action: unknown ): action is PayloadAction { - // TODO: clean that thing!! - if (typeof action === 'object' && action !== null && 'meta' in action) { + if (action === null || action === undefined) { + return false; + } + + if (typeof action === 'object' && 'meta' in action) { return ( (action as PayloadAction).meta ?.arg?.slotId !== undefined ); } + return false; } @@ -177,7 +181,6 @@ export function createWaitForActionMiddlewareForRecommendation< >( isDesiredAction: (action: unknown) => action is TAction, memo: Set - //TODO:: this will not work for non recommendation action ): {promise: Promise; middleware: Middleware} { const {promise, resolve} = createDeferredPromise(); let hasBeenResolved = false; @@ -186,22 +189,12 @@ export function createWaitForActionMiddlewareForRecommendation< const middleware: Middleware = () => (next) => (action) => { next(action); - // if (isDesiredAction(action) && isRecommendationActionPayload(action)) { - // console.log(''); - // console.log('slotId:', action.meta.arg.slotId); - // console.log( - // 'condition: ', - // hasBeenResolved, - // memo.has(action.meta.arg.slotId) - // ); - // } if ( isDesiredAction(action) && !hasBeenResolved && isRecommendationActionPayload(action) && !hasSlotBeenProcessed(action.meta.arg.slotId) ) { - // console.log(' --- RESOLVE ---'); hasBeenResolved = true; memo.add(action.meta.arg.slotId); resolve(action); diff --git a/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx b/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx index 269a0d5b346..740a6869fa6 100644 --- a/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx +++ b/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx @@ -6,15 +6,11 @@ import FacetGenerator from '@/components/facets/facet-generator'; import Pagination from '@/components/pagination'; import ProductList from '@/components/product-list'; import ListingProvider from '@/components/providers/listing-provider'; -import RecommendationProvider from '@/components/providers/recommendation-provider'; import Recommendations from '@/components/recommendation-list'; import Sort from '@/components/sort'; import StandaloneSearchBox from '@/components/standalone-search-box'; import Summary from '@/components/summary'; -import { - listingEngineDefinition, - recommendationEngineDefinition, -} from '@/lib/commerce-engine'; +import {listingEngineDefinition} from '@/lib/commerce-engine'; import {NextJsNavigatorContext} from '@/lib/navigatorContextProvider'; import {defaultContext} from '@/utils/context'; import {headers} from 'next/headers'; @@ -58,10 +54,6 @@ export default async function Listing({params}: {params: {category: string}}) { }, }); - const recStaticState = await recommendationEngineDefinition.fetchStaticState([ - 'popularBoughtRecs', - ]); - return (

- - - +
diff --git a/packages/samples/headless-ssr-commerce/app/_components/pages/recommendation.tsx b/packages/samples/headless-ssr-commerce/app/_components/pages/recommendation.tsx deleted file mode 100644 index e3c7d60fcc5..00000000000 --- a/packages/samples/headless-ssr-commerce/app/_components/pages/recommendation.tsx +++ /dev/null @@ -1,50 +0,0 @@ -'use client'; - -import {NavigatorContext} from '@coveo/headless/ssr-commerce'; -import {useEffect, useState} from 'react'; -import { - RecommendationStaticState, - RecommendationHydratedState, - recommendationEngineDefinition, -} from '../../_lib/commerce-engine'; -import {Recommendations} from '../recommendation-list'; - -export default function Recommendation({ - staticState, - navigatorContext, -}: { - staticState: RecommendationStaticState; - navigatorContext: NavigatorContext; -}) { - const [hydratedState, setHydratedState] = useState< - RecommendationHydratedState | undefined - >(undefined); - - // Setting the navigator context provider also in client-side before hydrating the application - recommendationEngineDefinition.setNavigatorContextProvider( - () => navigatorContext - ); - - useEffect(() => { - recommendationEngineDefinition - .hydrateStaticState({ - searchActions: staticState.searchActions, - }) - .then(({engine, controllers}) => { - setHydratedState({engine, controllers}); - }); - }, [staticState]); - - return ( - <> - - - - ); -} diff --git a/packages/samples/headless-ssr-commerce/components/pages/product-page.tsx b/packages/samples/headless-ssr-commerce/components/pages/product-page.tsx index 91b90767b71..7dd0e40d8a7 100644 --- a/packages/samples/headless-ssr-commerce/components/pages/product-page.tsx +++ b/packages/samples/headless-ssr-commerce/components/pages/product-page.tsx @@ -46,6 +46,8 @@ export default function ProductPage(props: IProductPageProps) { }) .then(({engine, controllers}) => { setHydratedState({engine, controllers}); + + // TODO: use recommendationEngineDefinition for engines controllers }); }, [staticState]); diff --git a/packages/samples/headless-ssr-commerce/components/providers/listing-provider.tsx b/packages/samples/headless-ssr-commerce/components/providers/listing-provider.tsx index 72641370628..46e777db338 100644 --- a/packages/samples/headless-ssr-commerce/components/providers/listing-provider.tsx +++ b/packages/samples/headless-ssr-commerce/components/providers/listing-provider.tsx @@ -38,9 +38,7 @@ export default function ListingProvider({ }) .then(({engine, controllers}) => { setHydratedState({engine, controllers}); - // Refreshing recommendations in the browser after hydrating the state in the client-side - // Recommendation refresh in the server is not supported yet. - // controllers.popularBoughtRecs.refresh(); // FIXME: does not work + // TODO: use recommendationEngineDefinition for recommendation controllers }); }, [staticState]); diff --git a/packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx b/packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx deleted file mode 100644 index fe80184f3a9..00000000000 --- a/packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx +++ /dev/null @@ -1,60 +0,0 @@ -'use client'; - -import { - recommendationEngineDefinition, - RecommendationHydratedState, - RecommendationStaticState, -} from '@/lib/commerce-engine'; -import {NavigatorContext} from '@coveo/headless-react/ssr-commerce'; -import {PropsWithChildren, useEffect, useState} from 'react'; - -interface RecommendationPageProps { - staticState: RecommendationStaticState; - navigatorContext: NavigatorContext; -} - -export default function RecommendationProvider({ - staticState, - navigatorContext, - children, -}: PropsWithChildren) { - const [hydratedState, setHydratedState] = useState< - RecommendationHydratedState | undefined - >(undefined); - - // Setting the navigator context provider also in client-side before hydrating the application - recommendationEngineDefinition.setNavigatorContextProvider( - () => navigatorContext - ); - - useEffect(() => { - recommendationEngineDefinition - .hydrateStaticState({ - searchActions: staticState.searchActions, - }) // TODO: need to pass the search actions!!!! - .then(({engine, controllers}) => { - setHydratedState({engine, controllers}); - }); - }, [staticState]); - - if (hydratedState) { - return ( - - <>{children} - - ); - } else { - return ( - - {/* // TODO: Add KIT-3701: Type 'React.ReactNode' is not assignable to type 'import(".../node_modules/@types/react/index").ReactNode'. - Type 'bigint' is not assignable to type 'ReactNode'.*/} - <>{children} - - ); - } -} diff --git a/packages/samples/headless-ssr-commerce/lib/commerce-engine-config.ts b/packages/samples/headless-ssr-commerce/lib/commerce-engine-config.ts index 7cb742017ae..e4545e20d9f 100644 --- a/packages/samples/headless-ssr-commerce/lib/commerce-engine-config.ts +++ b/packages/samples/headless-ssr-commerce/lib/commerce-engine-config.ts @@ -47,20 +47,6 @@ export default { slotId: 'af4fb7ba-6641-4b67-9cf9-be67e9f30174', }, }), - popularBoughtRecs_DUPLICATE: defineRecommendations({ - // TODO: support option to run only on specific - options: { - slotId: 'af4fb7ba-6641-4b67-9cf9-be67e9f30174', - }, - }), - // TODO: [x] check for invalid slotId => it will reject as expected - // TODO: [ ] check for duplicate slotId - // TODO: encounter for multiple recommendations with same slot id - // popwularBoughtRecs: defineRecommendations({ - // options: { - // slotId: 'af4fb7ba-6641-4b67-9cf9-be67e9f30172', - // }, - // }), cart: defineCart(), searchBox: defineSearchBox(), context: defineContext(), diff --git a/packages/samples/headless-ssr-commerce/lib/commerce-engine.ts b/packages/samples/headless-ssr-commerce/lib/commerce-engine.ts index a2a7ed771f5..91ea7d56473 100644 --- a/packages/samples/headless-ssr-commerce/lib/commerce-engine.ts +++ b/packages/samples/headless-ssr-commerce/lib/commerce-engine.ts @@ -11,7 +11,6 @@ export const { listingEngineDefinition, searchEngineDefinition, standaloneEngineDefinition, - recommendationEngineDefinition, useEngine, } = engineDefinition; @@ -55,10 +54,3 @@ export type StandaloneStaticState = InferStaticState< export type StandaloneHydratedState = InferHydratedState< typeof standaloneEngineDefinition >; - -export type RecommendationStaticState = InferStaticState< - typeof recommendationEngineDefinition ->; -export type RecommendationHydratedState = InferHydratedState< - typeof recommendationEngineDefinition ->; From 677fd451c1b88b4507157a7e55b2272fdef9df13 Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Mon, 18 Nov 2024 08:25:59 +0100 Subject: [PATCH 22/30] clean controller build condition --- .../src/app/commerce-ssr-engine/common.ts | 35 +++++-------------- .../recommendation-static-state-factory.ts | 2 +- .../headless-core-breadcrumb-manager.ssr.ts | 2 ++ .../headless-commerce-facet-generator.ssr.ts | 2 ++ .../headless-core-commerce-pagination.ssr.ts | 2 ++ .../headless-core-parameter-manager.ssr.ts | 2 ++ .../sort/headless-core-commerce-sort.ssr.ts | 2 ++ .../headless-sub-controller.ssr.ts | 2 ++ .../core/summary/headless-core-summary.ssr.ts | 2 ++ .../product-list/headless-product-list.ssr.ts | 2 ++ 10 files changed, 26 insertions(+), 27 deletions(-) diff --git a/packages/headless/src/app/commerce-ssr-engine/common.ts b/packages/headless/src/app/commerce-ssr-engine/common.ts index 377b79424d5..27892b9f6ea 100644 --- a/packages/headless/src/app/commerce-ssr-engine/common.ts +++ b/packages/headless/src/app/commerce-ssr-engine/common.ts @@ -80,34 +80,17 @@ export function buildControllerDefinitions< TSolutionType > { const controllerMap = mapObject(definitionsMap, (definition, key) => { - const unavailableInSearchSolutionType = - 'search' in definition && - definition['search'] === false && - solutionType === SolutionType['search']; - - const unavailableInListingSolutionType = - 'listing' in definition && - definition['listing'] === false && - solutionType === SolutionType['listing']; - - const unavailableInStandaloneSolutionType = - solutionType === SolutionType['standalone'] && 'standalone' in definition - ? definition['standalone'] === false - : false; - - const unavailabeInRecs = - // TODO: use this disjunction pattern for all other conditions - (solutionType === SolutionType['recommendation'] && - !('recommendation' in definition)) || - ('recommendation' in definition && - definition['recommendation'] === false && - solutionType === SolutionType['recommendation']); + const unavailableInSolutionType = (type: SolutionType) => + (!(type in definition) && solutionType === SolutionType[type]) || + (type in definition && + definition[type as keyof typeof definition] === false && + solutionType === SolutionType[type]); if ( - unavailableInSearchSolutionType || - unavailableInListingSolutionType || - unavailableInStandaloneSolutionType || - unavailabeInRecs + unavailableInSolutionType(SolutionType.search) || + unavailableInSolutionType(SolutionType.listing) || + unavailableInSolutionType(SolutionType.standalone) || + unavailableInSolutionType(SolutionType.recommendation) ) { return null; } diff --git a/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts b/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts index 2d58a08c4ba..e0bb3646a9f 100644 --- a/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts +++ b/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts @@ -115,7 +115,7 @@ function filterRecommendationControllers< TEngine extends CoreEngine | CoreEngineNext, TControllerDefinitions extends ControllerDefinitionsMap, >( - controllers: Record, // TODO: or InferControllersMapFromDefinition + controllers: Record, controllerDefinitions: TControllerDefinitions, logger: Logger ) { diff --git a/packages/headless/src/controllers/commerce/core/breadcrumb-manager/headless-core-breadcrumb-manager.ssr.ts b/packages/headless/src/controllers/commerce/core/breadcrumb-manager/headless-core-breadcrumb-manager.ssr.ts index 6557cfc3c12..8416e622351 100644 --- a/packages/headless/src/controllers/commerce/core/breadcrumb-manager/headless-core-breadcrumb-manager.ssr.ts +++ b/packages/headless/src/controllers/commerce/core/breadcrumb-manager/headless-core-breadcrumb-manager.ssr.ts @@ -25,6 +25,8 @@ export function defineBreadcrumbManager< >(options?: TOptions) { ensureAtLeastOneSolutionType(options); return { + listing: true, + search: true, ...options, build: (engine, solutionType) => solutionType === SolutionType.listing diff --git a/packages/headless/src/controllers/commerce/core/facets/generator/headless-commerce-facet-generator.ssr.ts b/packages/headless/src/controllers/commerce/core/facets/generator/headless-commerce-facet-generator.ssr.ts index d008d0039b8..d68d9c8b07a 100644 --- a/packages/headless/src/controllers/commerce/core/facets/generator/headless-commerce-facet-generator.ssr.ts +++ b/packages/headless/src/controllers/commerce/core/facets/generator/headless-commerce-facet-generator.ssr.ts @@ -113,6 +113,8 @@ export function defineFacetGenerator< >(options?: TOptions) { ensureAtLeastOneSolutionType(options); return { + listing: true, + search: true, ...options, build: (engine, solutionType) => buildFacetGenerator(engine, {props: {solutionType: solutionType!}}), diff --git a/packages/headless/src/controllers/commerce/core/pagination/headless-core-commerce-pagination.ssr.ts b/packages/headless/src/controllers/commerce/core/pagination/headless-core-commerce-pagination.ssr.ts index 10e29dd8a9d..0fe04c67d38 100644 --- a/packages/headless/src/controllers/commerce/core/pagination/headless-core-commerce-pagination.ssr.ts +++ b/packages/headless/src/controllers/commerce/core/pagination/headless-core-commerce-pagination.ssr.ts @@ -28,6 +28,8 @@ export function definePagination< >(props?: PaginationProps & TOptions) { ensureAtLeastOneSolutionType(props); return { + listing: true, + search: true, ...props, build: (engine, solutionType) => solutionType === SolutionType.listing diff --git a/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts b/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts index 2a4917db7d0..fa85bfd07f0 100644 --- a/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts +++ b/packages/headless/src/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.ts @@ -46,6 +46,8 @@ export function defineParameterManager< >(options?: TOptions) { ensureAtLeastOneSolutionType(options); return { + listing: true, + search: true, ...options, buildWithProps: (engine, props, solutionType) => { if (solutionType === SolutionType.listing) { diff --git a/packages/headless/src/controllers/commerce/core/sort/headless-core-commerce-sort.ssr.ts b/packages/headless/src/controllers/commerce/core/sort/headless-core-commerce-sort.ssr.ts index 24743cd3ef9..126497a8580 100644 --- a/packages/headless/src/controllers/commerce/core/sort/headless-core-commerce-sort.ssr.ts +++ b/packages/headless/src/controllers/commerce/core/sort/headless-core-commerce-sort.ssr.ts @@ -24,6 +24,8 @@ export function defineSort< >(props?: SortProps & TOptions) { ensureAtLeastOneSolutionType(props); return { + listing: true, + search: true, ...props, build: (engine, solutionType) => solutionType === SolutionType.listing diff --git a/packages/headless/src/controllers/commerce/core/sub-controller/headless-sub-controller.ssr.ts b/packages/headless/src/controllers/commerce/core/sub-controller/headless-sub-controller.ssr.ts index 066a3f38690..356d4caf5cf 100644 --- a/packages/headless/src/controllers/commerce/core/sub-controller/headless-sub-controller.ssr.ts +++ b/packages/headless/src/controllers/commerce/core/sub-controller/headless-sub-controller.ssr.ts @@ -17,6 +17,8 @@ export function defineQuerySummary< >(options?: TOptions) { ensureAtLeastOneSolutionType(options); return { + listing: true, + search: true, ...options, build: (engine, solutionType) => solutionType === SolutionType.listing diff --git a/packages/headless/src/controllers/commerce/core/summary/headless-core-summary.ssr.ts b/packages/headless/src/controllers/commerce/core/summary/headless-core-summary.ssr.ts index d8f410d8d4c..85e788de36a 100644 --- a/packages/headless/src/controllers/commerce/core/summary/headless-core-summary.ssr.ts +++ b/packages/headless/src/controllers/commerce/core/summary/headless-core-summary.ssr.ts @@ -32,6 +32,8 @@ export function defineSummary< >(options?: TOptions) { ensureAtLeastOneSolutionType(options); return { + listing: true, + search: true, ...options, build: (engine, solutionType) => solutionType === SolutionType.listing diff --git a/packages/headless/src/controllers/commerce/product-list/headless-product-list.ssr.ts b/packages/headless/src/controllers/commerce/product-list/headless-product-list.ssr.ts index df540b83c34..d8c559f0137 100644 --- a/packages/headless/src/controllers/commerce/product-list/headless-product-list.ssr.ts +++ b/packages/headless/src/controllers/commerce/product-list/headless-product-list.ssr.ts @@ -29,6 +29,8 @@ export function defineProductList< >(options?: TOptions) { ensureAtLeastOneSolutionType(options); return { + listing: true, + search: true, ...options, build: (engine, solutionType) => solutionType === SolutionType.listing From 87d07920f1edfe4c7dd9afe8023adedfef90945d Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Mon, 18 Nov 2024 08:33:00 +0100 Subject: [PATCH 23/30] create commerce build result type --- .../commerce-ssr-engine/types/core-engine.ts | 6 ++-- .../types/fetch-static-state.ts | 2 +- .../types/from-build-result.ts | 32 +++++++++++++++++++ .../types/hydrate-static-state.ts | 2 +- .../src/app/ssr-engine/types/build.ts | 12 ------- .../app/ssr-engine/types/from-build-result.ts | 10 ------ 6 files changed, 36 insertions(+), 28 deletions(-) create mode 100644 packages/headless/src/app/commerce-ssr-engine/types/from-build-result.ts diff --git a/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts b/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts index 4b5730db093..5c8d107c6f3 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts @@ -4,10 +4,7 @@ import {CommerceEngineDefinition} from '../../commerce-engine/commerce-engine.ss import {EngineConfiguration} from '../../engine-configuration.js'; import {CoreEngine, CoreEngineNext} from '../../engine.js'; import {NavigatorContextProvider} from '../../navigatorContextProvider.js'; -import type { - FromBuildResult, - FromBuildResultOptions, -} from '../../ssr-engine/types/from-build-result.js'; +import type {FromBuildResultOptions} from '../../ssr-engine/types/from-build-result.js'; import {SSRCommerceEngine} from '../factories/build-factory.js'; import {Build, BuildOptions} from './build.js'; import { @@ -21,6 +18,7 @@ import { FetchStaticState, FetchStaticStateOptions, } from './fetch-static-state.js'; +import {FromBuildResult} from './from-build-result.js'; import { HydrateStaticState, HydrateStaticStateOptions, diff --git a/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts b/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts index 2469152c61c..71bafbc5cd6 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts @@ -13,7 +13,7 @@ import type { ControllerStaticStateMap, OptionsTuple, } from '../../ssr-engine/types/common.js'; -import type {FromBuildResult} from '../../ssr-engine/types/from-build-result.js'; +import {FromBuildResult} from './from-build-result.js'; export type FetchStaticStateOptions = {}; diff --git a/packages/headless/src/app/commerce-ssr-engine/types/from-build-result.ts b/packages/headless/src/app/commerce-ssr-engine/types/from-build-result.ts new file mode 100644 index 00000000000..e9c5933b9cf --- /dev/null +++ b/packages/headless/src/app/commerce-ssr-engine/types/from-build-result.ts @@ -0,0 +1,32 @@ +import {CoreEngine, CoreEngineNext} from '../../engine.js'; +import {ControllersMap} from '../../ssr-engine/types/common.js'; +import {EngineDefinitionBuildResult} from './common.js'; + +export interface FromBuildResultOptions< + TEngine extends CoreEngine | CoreEngineNext, + TControllers extends ControllersMap, +> { + /** + * The build result of the engine + */ + buildResult: EngineDefinitionBuildResult; + /** + * An optional array of keys representing the recommendation controllers to refresh. + * If a recommendation key defined in your engine definition is present in this list, the associate recommendation controller + * will query the API. + * + * This is applicable only if the engine is a recommendation engine. + */ + allowedRecommendationKeys?: (keyof TControllers)[]; +} + +export interface FromBuildResult< + TEngine extends CoreEngine | CoreEngineNext, + TControllers extends ControllersMap, + TOptions, + TReturn, +> { + ( + options: FromBuildResultOptions & TOptions + ): Promise; +} diff --git a/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts b/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts index 0896a068a03..d45c07d6af2 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts @@ -7,12 +7,12 @@ import type { HydratedState, OptionsTuple, } from '../../ssr-engine/types/common.js'; -import type {FromBuildResult} from '../../ssr-engine/types/from-build-result.js'; import { ControllerDefinitionsMap, EngineDefinitionControllersPropsOption, SolutionType, } from './common.js'; +import {FromBuildResult} from './from-build-result.js'; export interface HydrateStaticStateOptions { searchActions: TSearchAction[]; diff --git a/packages/headless/src/app/ssr-engine/types/build.ts b/packages/headless/src/app/ssr-engine/types/build.ts index 62b11178821..e7dcabba4dd 100644 --- a/packages/headless/src/app/ssr-engine/types/build.ts +++ b/packages/headless/src/app/ssr-engine/types/build.ts @@ -12,18 +12,6 @@ export interface BuildOptions { extend?: OptionsExtender; } -export interface BuildWithForRecommendations< - TEngine extends CoreEngine | CoreEngineNext, - TControllersMap extends ControllersMap, -> { - /** - * Initializes an engine and controllers from the definition. - */ - ( - controllers: (keyof TControllersMap)[] - ): Promise>; -} - export interface Build< TEngine extends CoreEngine | CoreEngineNext, TEngineOptions, diff --git a/packages/headless/src/app/ssr-engine/types/from-build-result.ts b/packages/headless/src/app/ssr-engine/types/from-build-result.ts index ce380b391de..fab3bd03029 100644 --- a/packages/headless/src/app/ssr-engine/types/from-build-result.ts +++ b/packages/headless/src/app/ssr-engine/types/from-build-result.ts @@ -5,17 +5,7 @@ export interface FromBuildResultOptions< TEngine extends CoreEngine | CoreEngineNext, TControllers extends ControllersMap, > { - /** - * The build result of the engine - */ buildResult: EngineDefinitionBuildResult; - /** - * An optional array of keys representing the recommendation controllers to refresh. - * If a recommendation key defined in your engine definition is present in this list, the associate recommendation controller - * will query the API. - * - * This is applicable only if the engine is a recommendation engine. - */ allowedRecommendationKeys?: (keyof TControllers)[]; } From 16c2b5e0a7984d3a979454c435168e4f82442524 Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Mon, 18 Nov 2024 08:44:28 +0100 Subject: [PATCH 24/30] remove unnecessary error --- .../app/commerce-ssr-engine/factories/static-state-factory.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/headless/src/app/commerce-ssr-engine/factories/static-state-factory.ts b/packages/headless/src/app/commerce-ssr-engine/factories/static-state-factory.ts index b152e585798..fa79338be79 100644 --- a/packages/headless/src/app/commerce-ssr-engine/factories/static-state-factory.ts +++ b/packages/headless/src/app/commerce-ssr-engine/factories/static-state-factory.ts @@ -64,10 +64,6 @@ export const fetchStaticStateFactory: < case SolutionType.search: buildSearch(engine).executeFirstSearch(); break; - case SolutionType.recommendation: - throw new Error( - '[WARNING] Invalid engine definition. For recommendations, use the recommendation engine definition instead' - ); } const searchActions = await Promise.all( From dbffb512ec5c9d8f3b5784efd3f8f110a35fe270 Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Mon, 18 Nov 2024 09:55:45 +0100 Subject: [PATCH 25/30] revert EngineDefinitionControllersPropsOption --- .../app/commerce-ssr-engine/types/build.ts | 12 +--------- .../app/commerce-ssr-engine/types/common.ts | 24 ++----------------- .../commerce-ssr-engine/types/core-engine.ts | 3 --- .../types/fetch-static-state.ts | 12 +--------- .../types/hydrate-static-state.ts | 12 +--------- 5 files changed, 5 insertions(+), 58 deletions(-) diff --git a/packages/headless/src/app/commerce-ssr-engine/types/build.ts b/packages/headless/src/app/commerce-ssr-engine/types/build.ts index 3333d066720..8bf2fe0bd2b 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/build.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/build.ts @@ -1,4 +1,3 @@ -import type {Controller} from '../../../controllers/controller/headless-controller.js'; import type {CoreEngine, CoreEngineNext} from '../../engine.js'; import type { ControllersMap, @@ -8,7 +7,6 @@ import type { OptionsTuple, } from '../../ssr-engine/types/common.js'; import { - ControllerDefinitionsMap, EngineDefinitionControllersPropsOption, SolutionType, } from './common.js'; @@ -22,10 +20,6 @@ export type Build< TEngineOptions, TControllersMap extends ControllersMap, TControllersProps extends ControllersPropsMap, - TControllersDefinitionsMap extends ControllerDefinitionsMap< - TEngine, - Controller - >, TSolutionType extends SolutionType, > = TSolutionType extends SolutionType.recommendation ? { @@ -43,11 +37,7 @@ export type Build< ( ...params: OptionsTuple< BuildOptions & - EngineDefinitionControllersPropsOption< - TControllersDefinitionsMap, - TControllersProps, - TSolutionType - > + EngineDefinitionControllersPropsOption > ): Promise>; }; diff --git a/packages/headless/src/app/commerce-ssr-engine/types/common.ts b/packages/headless/src/app/commerce-ssr-engine/types/common.ts index 087b02b50d2..2c18f95a23d 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/common.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/common.ts @@ -10,14 +10,14 @@ import type { InferControllerPropsMapFromDefinitions, ControllerStaticStateMap, EngineDefinitionBuildResult, + EngineDefinitionControllersPropsOption, HydratedState, OptionsTuple, - ControllersPropsMap, - HasKeys, } from '../../ssr-engine/types/common.js'; export type { EngineDefinitionBuildResult, + EngineDefinitionControllersPropsOption, HydratedState, OptionsTuple, InferControllerStaticStateFromController, @@ -149,26 +149,6 @@ export type InferControllerStaticStateMapFromDefinitionsWithSolutionType< >; }; -export type EngineDefinitionControllersPropsOption< - TControllers extends ControllerDefinitionsMap< - CoreEngine | CoreEngineNext, - Controller - >, - TControllersPropsMap extends ControllersPropsMap, - TSolutionType extends SolutionType, -> = { - [K in keyof TControllers as HasKey< - TControllers[K], - TSolutionType - > extends never - ? never - : K]: HasKeys extends false - ? {} - : { - controllers: TControllersPropsMap; - }; -}; - export interface ControllerDefinitionOption { /** * Whether the controller will be used in a product listing context. diff --git a/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts b/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts index 5c8d107c6f3..89038e891f1 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts @@ -65,7 +65,6 @@ export interface EngineDefinition< TSolutionType >, InferControllerPropsMapFromDefinitions, - TControllers, TSolutionType >; /** @@ -76,7 +75,6 @@ export interface EngineDefinition< InferControllersMapFromDefinition, UnknownAction, InferControllerPropsMapFromDefinitions, - TControllers, TSolutionType >; /** @@ -87,7 +85,6 @@ export interface EngineDefinition< TEngineOptions, InferControllersMapFromDefinition, InferControllerPropsMapFromDefinitions, - TControllers, TSolutionType >; diff --git a/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts b/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts index 71bafbc5cd6..16bc1aa0155 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts @@ -1,8 +1,6 @@ import type {UnknownAction} from '@reduxjs/toolkit'; -import type {Controller} from '../../../controllers/controller/headless-controller.js'; import {SolutionType} from '../../commerce-ssr-engine/types/common.js'; import type { - ControllerDefinitionsMap, EngineDefinitionControllersPropsOption, EngineStaticState, } from '../../commerce-ssr-engine/types/common.js'; @@ -23,10 +21,6 @@ export type FetchStaticState< TSearchAction extends UnknownAction, TControllersStaticState extends ControllerStaticStateMap, TControllersProps extends ControllersPropsMap, - TControllersDefinitionsMap extends ControllerDefinitionsMap< - TEngine, - Controller - >, TSolutionType extends SolutionType, > = TSolutionType extends SolutionType.recommendation ? { @@ -55,11 +49,7 @@ export type FetchStaticState< ( ...params: OptionsTuple< FetchStaticStateOptions & - EngineDefinitionControllersPropsOption< - TControllersDefinitionsMap, - TControllersProps, - TSolutionType - > + EngineDefinitionControllersPropsOption > ): Promise>; diff --git a/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts b/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts index d45c07d6af2..92797528f9f 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts @@ -1,5 +1,4 @@ import type {UnknownAction} from '@reduxjs/toolkit'; -import type {Controller} from '../../../controllers/controller/headless-controller.js'; import type {CoreEngine, CoreEngineNext} from '../../engine.js'; import type { ControllersMap, @@ -8,7 +7,6 @@ import type { OptionsTuple, } from '../../ssr-engine/types/common.js'; import { - ControllerDefinitionsMap, EngineDefinitionControllersPropsOption, SolutionType, } from './common.js'; @@ -23,10 +21,6 @@ export type HydrateStaticState< TControllers extends ControllersMap, TSearchAction extends UnknownAction, TControllersProps extends ControllersPropsMap, - TControllersDefinitionsMap extends ControllerDefinitionsMap< - TEngine, - Controller - >, TSolutionType extends SolutionType, > = TSolutionType extends SolutionType.recommendation ? { @@ -55,11 +49,7 @@ export type HydrateStaticState< ( ...params: OptionsTuple< HydrateStaticStateOptions & - EngineDefinitionControllersPropsOption< - TControllersDefinitionsMap, - TControllersProps, - TSolutionType - > + EngineDefinitionControllersPropsOption > ): Promise>; From 961c28e1260ee18da25e9ffbee3abbfb13d9bd94 Mon Sep 17 00:00:00 2001 From: ylakhdar Date: Mon, 18 Nov 2024 10:36:47 +0100 Subject: [PATCH 26/30] update UT --- .../headless-react/src/ssr-commerce/commerce-engine.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/headless-react/src/ssr-commerce/commerce-engine.test.tsx b/packages/headless-react/src/ssr-commerce/commerce-engine.test.tsx index e9b5c2fb56f..0d32a09560a 100644 --- a/packages/headless-react/src/ssr-commerce/commerce-engine.test.tsx +++ b/packages/headless-react/src/ssr-commerce/commerce-engine.test.tsx @@ -49,6 +49,7 @@ describe('Headless react SSR utils', () => { listingEngineDefinition, searchEngineDefinition, standaloneEngineDefinition, + recommendationEngineDefinition, ...rest } = defineCommerceEngine({configuration: sampleConfig}); const { From c58b3ae29c7152ab79cf9b22df2b43360673a3f1 Mon Sep 17 00:00:00 2001 From: Alex Prudhomme <78121423+alexprudhomme@users.noreply.github.com> Date: Mon, 18 Nov 2024 10:58:28 -0500 Subject: [PATCH 27/30] Add recs to sample --- .../app/(listing)/[category]/page.tsx | 21 ++++++- .../headless-ssr-commerce/app/cart/page.tsx | 16 ++++- .../headless-ssr-commerce/app/search/page.tsx | 7 --- .../components/pages/product-page.tsx | 4 -- .../components/providers/listing-provider.tsx | 1 - .../providers/recommendation-provider.tsx | 60 +++++++++++++++++++ .../popular-bought.tsx} | 14 +++-- .../recommendations/popular-viewed.tsx | 40 +++++++++++++ .../lib/commerce-engine-config.ts | 4 +- .../lib/commerce-engine.ts | 12 +++- 10 files changed, 155 insertions(+), 24 deletions(-) create mode 100644 packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx rename packages/samples/headless-ssr-commerce/components/{recommendation-list.tsx => recommendations/popular-bought.tsx} (67%) create mode 100644 packages/samples/headless-ssr-commerce/components/recommendations/popular-viewed.tsx diff --git a/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx b/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx index 740a6869fa6..ec0c9bae99e 100644 --- a/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx +++ b/packages/samples/headless-ssr-commerce/app/(listing)/[category]/page.tsx @@ -6,11 +6,16 @@ import FacetGenerator from '@/components/facets/facet-generator'; import Pagination from '@/components/pagination'; import ProductList from '@/components/product-list'; import ListingProvider from '@/components/providers/listing-provider'; -import Recommendations from '@/components/recommendation-list'; +import RecommendationProvider from '@/components/providers/recommendation-provider'; +import PopularBought from '@/components/recommendations/popular-bought'; +import PopularViewed from '@/components/recommendations/popular-viewed'; import Sort from '@/components/sort'; import StandaloneSearchBox from '@/components/standalone-search-box'; import Summary from '@/components/summary'; -import {listingEngineDefinition} from '@/lib/commerce-engine'; +import { + listingEngineDefinition, + recommendationEngineDefinition, +} from '@/lib/commerce-engine'; import {NextJsNavigatorContext} from '@/lib/navigatorContextProvider'; import {defaultContext} from '@/utils/context'; import {headers} from 'next/headers'; @@ -54,6 +59,10 @@ export default async function Listing({params}: {params: {category: string}}) { }, }); + const recsStaticState = await recommendationEngineDefinition.fetchStaticState( + ['popularBought', 'popularViewed'] + ); + return (
- + + + +
diff --git a/packages/samples/headless-ssr-commerce/app/cart/page.tsx b/packages/samples/headless-ssr-commerce/app/cart/page.tsx index 9713f817bb4..bc994c0db1e 100644 --- a/packages/samples/headless-ssr-commerce/app/cart/page.tsx +++ b/packages/samples/headless-ssr-commerce/app/cart/page.tsx @@ -1,8 +1,13 @@ import * as externalCartAPI from '@/actions/external-cart-api'; import Cart from '@/components/cart'; import ContextDropdown from '@/components/context-dropdown'; +import RecommendationProvider from '@/components/providers/recommendation-provider'; import SearchProvider from '@/components/providers/search-provider'; -import {searchEngineDefinition} from '@/lib/commerce-engine'; +import PopularBought from '@/components/recommendations/popular-bought'; +import { + recommendationEngineDefinition, + searchEngineDefinition, +} from '@/lib/commerce-engine'; import {NextJsNavigatorContext} from '@/lib/navigatorContextProvider'; import {defaultContext} from '@/utils/context'; import {headers} from 'next/headers'; @@ -30,6 +35,9 @@ export default async function Search() { }, }); + const recsStaticState = await recommendationEngineDefinition.fetchStaticState( + ['popularBought'] + ); return ( + + + ); diff --git a/packages/samples/headless-ssr-commerce/app/search/page.tsx b/packages/samples/headless-ssr-commerce/app/search/page.tsx index 6458b16d347..c3917a7bce0 100644 --- a/packages/samples/headless-ssr-commerce/app/search/page.tsx +++ b/packages/samples/headless-ssr-commerce/app/search/page.tsx @@ -4,7 +4,6 @@ import ContextDropdown from '@/components/context-dropdown'; import FacetGenerator from '@/components/facets/facet-generator'; import ProductList from '@/components/product-list'; import SearchProvider from '@/components/providers/search-provider'; -import Recommendations from '@/components/recommendation-list'; import SearchBox from '@/components/search-box'; import ShowMore from '@/components/show-more'; import Summary from '@/components/summary'; @@ -60,12 +59,6 @@ export default async function Search() { > */} - -
- {/* popularBoughtRecs */} - {/* TODO: KIT-3503: need to revisit the way recommendations are added*/} - -
); diff --git a/packages/samples/headless-ssr-commerce/components/pages/product-page.tsx b/packages/samples/headless-ssr-commerce/components/pages/product-page.tsx index 7dd0e40d8a7..79232674ef2 100644 --- a/packages/samples/headless-ssr-commerce/components/pages/product-page.tsx +++ b/packages/samples/headless-ssr-commerce/components/pages/product-page.tsx @@ -8,7 +8,6 @@ import { import {NavigatorContext} from '@coveo/headless-react/ssr-commerce'; import {useSearchParams} from 'next/navigation'; import {useEffect, useState} from 'react'; -import Recommendations from '../recommendation-list'; interface IProductPageProps { staticState: StandaloneStaticState; @@ -46,8 +45,6 @@ export default function ProductPage(props: IProductPageProps) { }) .then(({engine, controllers}) => { setHydratedState({engine, controllers}); - - // TODO: use recommendationEngineDefinition for engines controllers }); }, [staticState]); @@ -63,7 +60,6 @@ export default function ProductPage(props: IProductPageProps) { {name} ({productId}) - ${price}


- ); } diff --git a/packages/samples/headless-ssr-commerce/components/providers/listing-provider.tsx b/packages/samples/headless-ssr-commerce/components/providers/listing-provider.tsx index 46e777db338..6b253a54c16 100644 --- a/packages/samples/headless-ssr-commerce/components/providers/listing-provider.tsx +++ b/packages/samples/headless-ssr-commerce/components/providers/listing-provider.tsx @@ -38,7 +38,6 @@ export default function ListingProvider({ }) .then(({engine, controllers}) => { setHydratedState({engine, controllers}); - // TODO: use recommendationEngineDefinition for recommendation controllers }); }, [staticState]); diff --git a/packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx b/packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx new file mode 100644 index 00000000000..ef4c0cd07ce --- /dev/null +++ b/packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx @@ -0,0 +1,60 @@ +'use client'; + +import { + recommendationEngineDefinition, + RecommendationHydratedState, + RecommendationStaticState, +} from '@/lib/commerce-engine'; +import {NavigatorContext} from '@coveo/headless-react/ssr-commerce'; +import {PropsWithChildren, useEffect, useState} from 'react'; + +interface RecommendationProviderProps { + staticState: RecommendationStaticState; + navigatorContext: NavigatorContext; +} + +export default function RecommendationProvider({ + staticState, + navigatorContext, + children, +}: PropsWithChildren) { + const [hydratedState, setHydratedState] = useState< + RecommendationHydratedState | undefined + >(undefined); + + // Setting the navigator context provider also in client-side before hydrating the application + recommendationEngineDefinition.setNavigatorContextProvider( + () => navigatorContext + ); + + useEffect(() => { + recommendationEngineDefinition + .hydrateStaticState({ + searchActions: staticState.searchActions, + }) + .then(({engine, controllers}) => { + setHydratedState({engine, controllers}); + }); + }, [staticState]); + + if (hydratedState) { + return ( + + <>{children} + + ); + } else { + return ( + + {/* // TODO: Add KIT-3701: Type 'React.ReactNode' is not assignable to type 'import(".../node_modules/@types/react/index").ReactNode'. + Type 'bigint' is not assignable to type 'ReactNode'.*/} + <>{children} + + ); + } +} diff --git a/packages/samples/headless-ssr-commerce/components/recommendation-list.tsx b/packages/samples/headless-ssr-commerce/components/recommendations/popular-bought.tsx similarity index 67% rename from packages/samples/headless-ssr-commerce/components/recommendation-list.tsx rename to packages/samples/headless-ssr-commerce/components/recommendations/popular-bought.tsx index 5a62113537d..427fceef362 100644 --- a/packages/samples/headless-ssr-commerce/components/recommendation-list.tsx +++ b/packages/samples/headless-ssr-commerce/components/recommendations/popular-bought.tsx @@ -1,12 +1,12 @@ 'use client'; -import {usePopularBoughtRecs} from '@/lib/commerce-engine'; +import {usePopularBought} from '@/lib/commerce-engine'; import {Product} from '@coveo/headless-react/ssr-commerce'; +import Image from 'next/image'; import {useRouter} from 'next/navigation'; -export default function Recommendations() { - // TODO: KIT-3503: refresh recs server side - const {state, methods} = usePopularBoughtRecs(); +export default function PopularBought() { + const {state, methods} = usePopularBought(); const router = useRouter(); @@ -25,6 +25,12 @@ export default function Recommendations() {
  • ))} diff --git a/packages/samples/headless-ssr-commerce/components/recommendations/popular-viewed.tsx b/packages/samples/headless-ssr-commerce/components/recommendations/popular-viewed.tsx new file mode 100644 index 00000000000..cbd7cdc7129 --- /dev/null +++ b/packages/samples/headless-ssr-commerce/components/recommendations/popular-viewed.tsx @@ -0,0 +1,40 @@ +'use client'; + +import {usePopularViewed} from '@/lib/commerce-engine'; +import {Product} from '@coveo/headless-react/ssr-commerce'; +import Image from 'next/image'; +import {useRouter} from 'next/navigation'; + +export default function PopularViewed() { + const {state, methods} = usePopularViewed(); + + const router = useRouter(); + + const onProductClick = (product: Product) => { + methods?.interactiveProduct({options: {product}}).select(); + router.push( + `/products/${product.ec_product_id}?name=${product.ec_name}&price=${product.ec_price}` + ); + }; + + return ( + <> +
      +

      {state.headline}

      + {state.products.map((product) => ( +
    • + +
    • + ))} +
    + + ); +} diff --git a/packages/samples/headless-ssr-commerce/lib/commerce-engine-config.ts b/packages/samples/headless-ssr-commerce/lib/commerce-engine-config.ts index e4545e20d9f..feb0703f666 100644 --- a/packages/samples/headless-ssr-commerce/lib/commerce-engine-config.ts +++ b/packages/samples/headless-ssr-commerce/lib/commerce-engine-config.ts @@ -37,12 +37,12 @@ export default { controllers: { summary: defineSummary(), productList: defineProductList(), - popularViewedRecs: defineRecommendations({ + popularViewed: defineRecommendations({ options: { slotId: 'd73afbd2-8521-4ee6-a9b8-31f064721e73', }, }), - popularBoughtRecs: defineRecommendations({ + popularBought: defineRecommendations({ options: { slotId: 'af4fb7ba-6641-4b67-9cf9-be67e9f30174', }, diff --git a/packages/samples/headless-ssr-commerce/lib/commerce-engine.ts b/packages/samples/headless-ssr-commerce/lib/commerce-engine.ts index 91ea7d56473..ad6db361dff 100644 --- a/packages/samples/headless-ssr-commerce/lib/commerce-engine.ts +++ b/packages/samples/headless-ssr-commerce/lib/commerce-engine.ts @@ -10,6 +10,7 @@ export const engineDefinition = defineCommerceEngine(engineConfig); export const { listingEngineDefinition, searchEngineDefinition, + recommendationEngineDefinition, standaloneEngineDefinition, useEngine, } = engineDefinition; @@ -22,8 +23,8 @@ export const { useInstantProducts, useNotifyTrigger, usePagination, - usePopularBoughtRecs, - usePopularViewedRecs, + usePopularBought, + usePopularViewed, useProductView, useQueryTrigger, useRecentQueriesList, @@ -48,6 +49,13 @@ export type SearchHydratedState = InferHydratedState< typeof searchEngineDefinition >; +export type RecommendationStaticState = InferStaticState< + typeof recommendationEngineDefinition +>; +export type RecommendationHydratedState = InferHydratedState< + typeof recommendationEngineDefinition +>; + export type StandaloneStaticState = InferStaticState< typeof standaloneEngineDefinition >; From b8e6e16059606b6931eb74669c1a529303f44113 Mon Sep 17 00:00:00 2001 From: Alex Prudhomme <78121423+alexprudhomme@users.noreply.github.com> Date: Mon, 18 Nov 2024 11:22:17 -0500 Subject: [PATCH 28/30] no core engine --- .../commerce-ssr-engine/factories/build-factory.ts | 2 +- .../recommendation-static-state-factory.ts | 6 ++---- .../src/app/commerce-ssr-engine/types/build.ts | 11 +++++++---- .../app/commerce-ssr-engine/types/core-engine.ts | 3 --- .../types/fetch-static-state.ts | 4 ---- .../commerce-ssr-engine/types/from-build-result.ts | 14 ++++---------- .../types/hydrate-static-state.ts | 13 +++++-------- 7 files changed, 19 insertions(+), 34 deletions(-) diff --git a/packages/headless/src/app/commerce-ssr-engine/factories/build-factory.ts b/packages/headless/src/app/commerce-ssr-engine/factories/build-factory.ts index f9bc6535353..b084d2d8545 100644 --- a/packages/headless/src/app/commerce-ssr-engine/factories/build-factory.ts +++ b/packages/headless/src/app/commerce-ssr-engine/factories/build-factory.ts @@ -34,7 +34,7 @@ export interface SSRCommerceEngine extends CommerceEngine { } export type CommerceEngineDefinitionOptions< - TControllers extends ControllerDefinitionsMap, + TControllers extends ControllerDefinitionsMap, > = EngineDefinitionOptions; function isListingFetchCompletedAction(action: unknown): action is Action { diff --git a/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts b/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts index e0bb3646a9f..6299c3d8cb8 100644 --- a/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts +++ b/packages/headless/src/app/commerce-ssr-engine/factories/recommendation-static-state-factory.ts @@ -3,7 +3,6 @@ import {Logger} from 'pino'; import {Recommendations} from '../../../controllers/commerce/recommendations/headless-recommendations.js'; import {RecommendationsDefinitionMeta} from '../../../controllers/commerce/recommendations/headless-recommendations.ssr.js'; import {Controller} from '../../../controllers/controller/headless-controller.js'; -import {CoreEngine, CoreEngineNext} from '../../engine.js'; import {buildLogger} from '../../logger.js'; import {composeFunction} from '../../ssr-engine/common.js'; import {createStaticState} from '../common.js'; @@ -112,8 +111,7 @@ export function fetchRecommendationStaticStateFactory< } function filterRecommendationControllers< - TEngine extends CoreEngine | CoreEngineNext, - TControllerDefinitions extends ControllerDefinitionsMap, + TControllerDefinitions extends ControllerDefinitionsMap, >( controllers: Record, controllerDefinitions: TControllerDefinitions, @@ -122,7 +120,7 @@ function filterRecommendationControllers< const slotIdSet = new Set(); const isRecommendationDefinition = < - C extends ControllerDefinition, + C extends ControllerDefinition, >( controllerDefinition: C ): controllerDefinition is C & RecommendationsDefinitionMeta => { diff --git a/packages/headless/src/app/commerce-ssr-engine/types/build.ts b/packages/headless/src/app/commerce-ssr-engine/types/build.ts index 8bf2fe0bd2b..ddbe2a29265 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/build.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/build.ts @@ -1,4 +1,3 @@ -import type {CoreEngine, CoreEngineNext} from '../../engine.js'; import type { ControllersMap, ControllersPropsMap, @@ -6,6 +5,7 @@ import type { OptionsExtender, OptionsTuple, } from '../../ssr-engine/types/common.js'; +import {SSRCommerceEngine} from '../factories/build-factory.js'; import { EngineDefinitionControllersPropsOption, SolutionType, @@ -16,7 +16,6 @@ export interface BuildOptions { } export type Build< - TEngine extends CoreEngine | CoreEngineNext, TEngineOptions, TControllersMap extends ControllersMap, TControllersProps extends ControllersPropsMap, @@ -28,7 +27,9 @@ export type Build< */ ( controllers: (keyof TControllersMap)[] - ): Promise>; + ): Promise< + EngineDefinitionBuildResult + >; } : { /** @@ -39,5 +40,7 @@ export type Build< BuildOptions & EngineDefinitionControllersPropsOption > - ): Promise>; + ): Promise< + EngineDefinitionBuildResult + >; }; diff --git a/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts b/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts index 45db41f5269..2d7083e8e4a 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/core-engine.ts @@ -52,7 +52,6 @@ export interface EngineDefinition< * Fetches the static state on the server side using your engine definition. */ fetchStaticState: FetchStaticState< - SSRCommerceEngine, InferControllersMapFromDefinition, UnknownAction, InferControllerStaticStateMapFromDefinitionsWithSolutionType< @@ -66,7 +65,6 @@ export interface EngineDefinition< * Fetches the hydrated state on the client side using your engine definition and the static state. */ hydrateStaticState: HydrateStaticState< - SSRCommerceEngine, InferControllersMapFromDefinition, UnknownAction, InferControllerPropsMapFromDefinitions, @@ -76,7 +74,6 @@ export interface EngineDefinition< * Builds an engine and its controllers from an engine definition. */ build: Build< - SSRCommerceEngine, TEngineOptions, InferControllersMapFromDefinition, InferControllerPropsMapFromDefinitions, diff --git a/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts b/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts index 16bc1aa0155..ec73f32afd9 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/fetch-static-state.ts @@ -4,7 +4,6 @@ import type { EngineDefinitionControllersPropsOption, EngineStaticState, } from '../../commerce-ssr-engine/types/common.js'; -import type {CoreEngine, CoreEngineNext} from '../../engine.js'; import type { ControllersMap, ControllersPropsMap, @@ -16,7 +15,6 @@ import {FromBuildResult} from './from-build-result.js'; export type FetchStaticStateOptions = {}; export type FetchStaticState< - TEngine extends CoreEngine | CoreEngineNext, TControllers extends ControllersMap, TSearchAction extends UnknownAction, TControllersStaticState extends ControllerStaticStateMap, @@ -34,7 +32,6 @@ export type FetchStaticState< ): Promise>; fromBuildResult: FromBuildResult< - TEngine, TControllers, FetchStaticStateOptions, EngineStaticState @@ -54,7 +51,6 @@ export type FetchStaticState< ): Promise>; fromBuildResult: FromBuildResult< - TEngine, TControllers, FetchStaticStateOptions, EngineStaticState diff --git a/packages/headless/src/app/commerce-ssr-engine/types/from-build-result.ts b/packages/headless/src/app/commerce-ssr-engine/types/from-build-result.ts index e9c5933b9cf..b8cdace3642 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/from-build-result.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/from-build-result.ts @@ -1,15 +1,12 @@ -import {CoreEngine, CoreEngineNext} from '../../engine.js'; import {ControllersMap} from '../../ssr-engine/types/common.js'; +import {SSRCommerceEngine} from '../factories/build-factory.js'; import {EngineDefinitionBuildResult} from './common.js'; -export interface FromBuildResultOptions< - TEngine extends CoreEngine | CoreEngineNext, - TControllers extends ControllersMap, -> { +export interface FromBuildResultOptions { /** * The build result of the engine */ - buildResult: EngineDefinitionBuildResult; + buildResult: EngineDefinitionBuildResult; /** * An optional array of keys representing the recommendation controllers to refresh. * If a recommendation key defined in your engine definition is present in this list, the associate recommendation controller @@ -21,12 +18,9 @@ export interface FromBuildResultOptions< } export interface FromBuildResult< - TEngine extends CoreEngine | CoreEngineNext, TControllers extends ControllersMap, TOptions, TReturn, > { - ( - options: FromBuildResultOptions & TOptions - ): Promise; + (options: FromBuildResultOptions & TOptions): Promise; } diff --git a/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts b/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts index 92797528f9f..55196960e04 100644 --- a/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts +++ b/packages/headless/src/app/commerce-ssr-engine/types/hydrate-static-state.ts @@ -1,11 +1,11 @@ import type {UnknownAction} from '@reduxjs/toolkit'; -import type {CoreEngine, CoreEngineNext} from '../../engine.js'; import type { ControllersMap, ControllersPropsMap, HydratedState, OptionsTuple, } from '../../ssr-engine/types/common.js'; +import {SSRCommerceEngine} from '../factories/build-factory.js'; import { EngineDefinitionControllersPropsOption, SolutionType, @@ -17,7 +17,6 @@ export interface HydrateStaticStateOptions { } export type HydrateStaticState< - TEngine extends CoreEngine | CoreEngineNext, TControllers extends ControllersMap, TSearchAction extends UnknownAction, TControllersProps extends ControllersPropsMap, @@ -31,13 +30,12 @@ export type HydrateStaticState< */ ( ...params: OptionsTuple> - ): Promise>; + ): Promise>; fromBuildResult: FromBuildResult< - TEngine, TControllers, HydrateStaticStateOptions, - HydratedState + HydratedState >; } : { @@ -51,12 +49,11 @@ export type HydrateStaticState< HydrateStaticStateOptions & EngineDefinitionControllersPropsOption > - ): Promise>; + ): Promise>; fromBuildResult: FromBuildResult< - TEngine, TControllers, HydrateStaticStateOptions, - HydratedState + HydratedState >; }; From 3f93d5150c1474f1baba8220443787c32f879b1a Mon Sep 17 00:00:00 2001 From: Alex Prudhomme <78121423+alexprudhomme@users.noreply.github.com> Date: Mon, 18 Nov 2024 11:39:30 -0500 Subject: [PATCH 29/30] add todo --- .../headless/src/app/commerce-engine/commerce-engine.ssr.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts index 5e835bf8f9f..69e82a409f3 100644 --- a/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts +++ b/packages/headless/src/app/commerce-engine/commerce-engine.ssr.ts @@ -110,7 +110,7 @@ export function defineCommerceEngine< TControllerDefinitions, SolutionType.recommendation >, - // TODO: The standaloneEngineDefinition should not be async since no request is sent to the API + // TODO KIT-3738 : The standaloneEngineDefinition should not be async since no request is sent to the API standaloneEngineDefinition: { build: build(SolutionType.standalone), fetchStaticState: fetchStaticState(SolutionType.standalone), From a73f175f0e18161e739056cb114c83c38785acf7 Mon Sep 17 00:00:00 2001 From: Alex Prudhomme <78121423+alexprudhomme@users.noreply.github.com> Date: Fri, 22 Nov 2024 13:59:17 -0500 Subject: [PATCH 30/30] fix build --- .../components/providers/recommendation-provider.tsx | 6 ++---- .../components/providers/standalone-provider.tsx | 10 +++------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx b/packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx index ef4c0cd07ce..6ea9c228236 100644 --- a/packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx +++ b/packages/samples/headless-ssr-commerce/components/providers/recommendation-provider.tsx @@ -43,7 +43,7 @@ export default function RecommendationProvider({ engine={hydratedState.engine} controllers={hydratedState.controllers} > - <>{children} + {children} ); } else { @@ -51,9 +51,7 @@ export default function RecommendationProvider({ - {/* // TODO: Add KIT-3701: Type 'React.ReactNode' is not assignable to type 'import(".../node_modules/@types/react/index").ReactNode'. - Type 'bigint' is not assignable to type 'ReactNode'.*/} - <>{children} + {children} ); } diff --git a/packages/samples/headless-ssr-commerce/components/providers/standalone-provider.tsx b/packages/samples/headless-ssr-commerce/components/providers/standalone-provider.tsx index a32a78d16ed..d7764a08f5a 100644 --- a/packages/samples/headless-ssr-commerce/components/providers/standalone-provider.tsx +++ b/packages/samples/headless-ssr-commerce/components/providers/standalone-provider.tsx @@ -30,7 +30,7 @@ export default function StandaloneProvider({ useEffect(() => { standaloneEngineDefinition .hydrateStaticState({ - searchAction: staticState.searchAction, + searchActions: staticState.searchActions, controllers: { cart: { initialState: {items: staticState.controllers.cart.state.items}, @@ -53,9 +53,7 @@ export default function StandaloneProvider({ engine={hydratedState.engine} controllers={hydratedState.controllers} > - {/* // TODO: KIT-3701: Type 'React.ReactNode' is not assignable to type 'import(".../node_modules/@types/react/index").ReactNode'. - Type 'bigint' is not assignable to type 'ReactNode'.*/} - <>{children} + {children} ); } else { @@ -63,9 +61,7 @@ export default function StandaloneProvider({ - {/* // TODO: KIT-3701: Type 'React.ReactNode' is not assignable to type 'import(".../node_modules/@types/react/index").ReactNode'. - Type 'bigint' is not assignable to type 'ReactNode'.*/} - <>{children} + {children} ); }