@coveo/headless 3.29.0-pre.032a5300a8 → 3.29.0-pre.075377ce6d
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/case-assist/headless.cjs +1 -1
- package/dist/cjs/commerce/headless.cjs +1 -1
- package/dist/cjs/headless.cjs +1 -1
- package/dist/cjs/insight/headless.cjs +1 -1
- package/dist/cjs/recommendation/headless.cjs +1 -1
- package/dist/cjs/ssr/headless.cjs +1 -1
- package/dist/cjs/ssr-commerce/headless.cjs +1 -1
- package/dist/cjs/ssr-commerce/node.cjs.stats.json +1 -1
- package/dist/cjs/ssr-commerce-next/headless.cjs +1 -1
- package/dist/cjs/ssr-commerce-next/node.cjs.stats.json +1 -1
- package/dist/cjs/ssr-next/headless.cjs +1 -1
- package/dist/definitions/ssr/commerce/types/fetch-static-state.d.ts +4 -0
- package/dist/definitions/ssr/common/types/build.d.ts +3 -0
- package/dist/definitions/ssr/common/types/engine.d.ts +3 -0
- package/dist/definitions/ssr-commerce-next.index.d.ts +3 -3
- package/dist/definitions/ssr-next/commerce/controllers/recommendations/headless-recommendations.ssr.d.ts +8 -4
- package/dist/definitions/ssr-next/commerce/factories/build-factory.d.ts +4 -3
- package/dist/definitions/ssr-next/commerce/factories/hydrated-state-factory.d.ts +2 -1
- package/dist/definitions/ssr-next/commerce/factories/recommendation-hydrated-state-factory.d.ts +1 -1
- package/dist/definitions/ssr-next/commerce/factories/recommendation-static-state-factory.d.ts +2 -1
- package/dist/definitions/ssr-next/commerce/factories/static-state-factory.d.ts +2 -1
- package/dist/definitions/ssr-next/commerce/types/build.d.ts +73 -7
- package/dist/definitions/ssr-next/commerce/types/controller-definitions.d.ts +35 -112
- package/dist/definitions/ssr-next/commerce/types/controller-inference.d.ts +3 -3
- package/dist/definitions/ssr-next/commerce/types/engine.d.ts +15 -8
- package/dist/definitions/ssr-next/commerce/types/fetch-static-state.d.ts +3 -3
- package/dist/definitions/ssr-next/commerce/types/hydrate-static-state.d.ts +2 -1
- package/dist/definitions/ssr-next/commerce/types/utilities.d.ts +6 -0
- package/dist/definitions/ssr-next/commerce/utils/controller-wiring.d.ts +35 -0
- package/dist/definitions/ssr-next/commerce/utils/engine-wiring.d.ts +5 -0
- package/dist/definitions/ssr-next/commerce/utils/recommendation-filter.d.ts +14 -1
- package/dist/definitions/ssr-next/commerce/validation/controller-validation.d.ts +1 -0
- package/dist/definitions/ssr-next/common/types/fetch-static-state.d.ts +1 -3
- package/dist/definitions/ssr-next/common/types/utilities.d.ts +0 -3
- package/dist/definitions/test/mock-ssr-controller-definitions.d.ts +12 -0
- package/dist/esm/ssr/commerce/types/kind.js +1 -0
- package/dist/esm/ssr-next/commerce/controllers/recommendations/headless-recommendations.ssr.js +1 -1
- package/dist/esm/ssr-next/commerce/engine/commerce-engine.ssr.js +15 -4
- package/dist/esm/ssr-next/commerce/factories/build-factory.js +13 -18
- package/dist/esm/ssr-next/commerce/factories/hydrated-state-factory.js +1 -3
- package/dist/esm/ssr-next/commerce/factories/recommendation-static-state-factory.js +6 -10
- package/dist/esm/ssr-next/commerce/factories/static-state-factory.js +5 -4
- package/dist/esm/ssr-next/commerce/types/utilities.js +1 -0
- package/dist/esm/ssr-next/commerce/utils/controller-wiring.js +153 -0
- package/dist/esm/ssr-next/commerce/utils/engine-wiring.js +10 -0
- package/dist/esm/ssr-next/commerce/utils/recommendation-filter.js +27 -8
- package/dist/esm/ssr-next/commerce/validation/controller-validation.js +7 -0
- package/dist/esm/test/mock-ssr-controller-definitions.js +15 -0
- package/dist/esm/utils/version.js +1 -1
- package/dist/quantic/case-assist/headless.js +1 -1
- package/dist/quantic/commerce/headless.js +1 -1
- package/dist/quantic/headless.js +1 -1
- package/dist/quantic/insight/headless.js +1 -1
- package/dist/quantic/recommendation/headless.js +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import type { Controller } from '../../../controllers/controller/headless-controller.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { RecommendationsDefinitionMeta } from '../controllers/recommendations/headless-recommendations.ssr.js';
|
|
3
|
+
import type { ControllerDefinition, ControllerDefinitionsMap } from '../types/controller-definitions.js';
|
|
4
|
+
/**
|
|
5
|
+
* Type guard to check if a controller definition is a recommendation controller.
|
|
6
|
+
* A recommendation controller must have both the recommendation flag and a slotId.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isRecommendationDefinition<C extends ControllerDefinition<Controller>>(controllerDefinition: C): controllerDefinition is C & RecommendationsDefinitionMeta;
|
|
9
|
+
/**
|
|
10
|
+
* Extracts recommendation controller definitions from a controller definitions map.
|
|
11
|
+
*
|
|
12
|
+
* @param controllerDefinitions - Map of controller definitions
|
|
13
|
+
* @returns Object containing only the recommendation controller definitions
|
|
14
|
+
*/
|
|
15
|
+
export declare function getRecommendationDefinitions(controllerDefinitions: ControllerDefinitionsMap<Controller>): Record<string, ControllerDefinition<Controller> & RecommendationsDefinitionMeta>;
|
|
3
16
|
export declare function filterRecommendationControllers<TControllerDefinitions extends ControllerDefinitionsMap<Controller>>(controllers: Record<string, Controller>, controllerDefinitions: TControllerDefinitions): {
|
|
4
17
|
/**
|
|
5
18
|
* Go through all the controllers passed in argument and only refresh recommendation controllers.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const validateControllerNames: (controllers: Record<string, unknown>) => void;
|
|
@@ -2,11 +2,9 @@ import type { UnknownAction } from '@reduxjs/toolkit';
|
|
|
2
2
|
import type { ControllerStaticStateMap, ControllersPropsMap } from './controllers.js';
|
|
3
3
|
import type { EngineDefinitionControllersPropsOption, EngineStaticState } from './engine.js';
|
|
4
4
|
import type { OptionsTuple } from './utilities.js';
|
|
5
|
-
type FetchStaticStateOptions = {};
|
|
6
5
|
/**
|
|
7
6
|
* Executes only the initial search for a given configuration, then returns a resumable snapshot of engine state along with the state of the controllers.
|
|
8
7
|
*
|
|
9
8
|
* Useful for static generation and server-side rendering.
|
|
10
9
|
*/
|
|
11
|
-
export type FetchStaticState<TSearchAction extends UnknownAction, TControllersStaticState extends ControllerStaticStateMap, TControllersProps extends ControllersPropsMap> = (...params: OptionsTuple<
|
|
12
|
-
export {};
|
|
10
|
+
export type FetchStaticState<TSearchAction extends UnknownAction, TControllersStaticState extends ControllerStaticStateMap, TControllersProps extends ControllersPropsMap> = (...params: OptionsTuple<EngineDefinitionControllersPropsOption<TControllersProps>>) => Promise<EngineStaticState<TSearchAction, TControllersStaticState>>;
|
|
@@ -3,9 +3,6 @@ export type HasKeys<TObject> = TObject extends {} ? keyof TObject extends never
|
|
|
3
3
|
export type HasRequiredKeys<TObject> = TObject extends {} ? {
|
|
4
4
|
[K in keyof TObject]-?: {} extends Pick<TObject, K> ? never : K;
|
|
5
5
|
}[keyof TObject] extends never ? false : true : boolean;
|
|
6
|
-
export type HasOptionalKeys<TObject> = TObject extends {} ? {
|
|
7
|
-
[K in keyof TObject]-?: {} extends Pick<TObject, K> ? K : never;
|
|
8
|
-
}[keyof TObject] extends never ? false : true : boolean;
|
|
9
6
|
type ExtractRequiredOptions<TOptions> = {
|
|
10
7
|
[TKey in keyof TOptions as Pick<TOptions, TKey> extends Required<Pick<TOptions, TKey>> ? TKey : never]: TOptions[TKey];
|
|
11
8
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SearchEngine } from '../app/search-engine/search-engine.js';
|
|
2
2
|
import type { Controller } from '../controllers/controller/headless-controller.js';
|
|
3
3
|
import type { Kind } from '../ssr/commerce/types/kind.js';
|
|
4
|
+
import { recommendationInternalOptionKey } from '../ssr-next/commerce/types/controller-constants.js';
|
|
4
5
|
import type { ControllerDefinitionWithoutProps, ControllerDefinitionWithProps } from '../ssr-next/common/types/controllers.js';
|
|
5
6
|
interface MockController {
|
|
6
7
|
initialState?: Record<string, unknown>;
|
|
@@ -35,4 +36,15 @@ export declare function defineMockCommerceControllerWithProps(options?: Solution
|
|
|
35
36
|
standalone: boolean;
|
|
36
37
|
recommendation: boolean;
|
|
37
38
|
};
|
|
39
|
+
export declare function defineMockRecommendationDefinition(slotId: string): {
|
|
40
|
+
recommendation: boolean;
|
|
41
|
+
[recommendationInternalOptionKey]: {
|
|
42
|
+
slotId: string;
|
|
43
|
+
};
|
|
44
|
+
buildWithProps: import("vitest").Mock<(engine: any, props: any) => {
|
|
45
|
+
state: {};
|
|
46
|
+
subscribe(listener: () => void): import("redux").Unsubscribe;
|
|
47
|
+
_kind: Kind;
|
|
48
|
+
}>;
|
|
49
|
+
};
|
|
38
50
|
export {};
|
|
@@ -5,6 +5,7 @@ export var Kind;
|
|
|
5
5
|
Kind["ParameterManager"] = "PARAMETER_MANAGER";
|
|
6
6
|
Kind["Recommendations"] = "RECOMMENDATIONS";
|
|
7
7
|
})(Kind || (Kind = {}));
|
|
8
|
+
// TODO: KIT-4742: There is no point for this now that the wiring is happening in headless
|
|
8
9
|
export function createControllerWithKind(controller, kind) {
|
|
9
10
|
const copy = Object.defineProperties({}, Object.getOwnPropertyDescriptors(controller));
|
|
10
11
|
Object.defineProperty(copy, '_kind', {
|
package/dist/esm/ssr-next/commerce/controllers/recommendations/headless-recommendations.ssr.js
CHANGED
|
@@ -17,7 +17,7 @@ export function defineRecommendations(props) {
|
|
|
17
17
|
buildWithProps: (engine, options) => {
|
|
18
18
|
const staticOptions = props.options;
|
|
19
19
|
const controller = buildRecommendations(engine, {
|
|
20
|
-
options: { ...staticOptions, ...options },
|
|
20
|
+
options: { ...staticOptions, ...options.initialState },
|
|
21
21
|
});
|
|
22
22
|
return createControllerWithKind(controller, Kind.Recommendations);
|
|
23
23
|
},
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Utility functions to be used for Commerce Server Side Rendering.
|
|
3
3
|
*/
|
|
4
|
+
import { defineCart } from '../controllers/cart/headless-cart.ssr.js';
|
|
5
|
+
import { defineContext } from '../controllers/context/headless-context.ssr.js';
|
|
6
|
+
import { defineParameterManager } from '../controllers/parameter-manager/headless-core-parameter-manager.ssr.js';
|
|
4
7
|
import { hydratedStaticStateFactory } from '../factories/hydrated-state-factory.js';
|
|
5
8
|
import { hydratedRecommendationStaticStateFactory } from '../factories/recommendation-hydrated-state-factory.js';
|
|
6
9
|
import { fetchRecommendationStaticStateFactory } from '../factories/recommendation-static-state-factory.js';
|
|
7
10
|
import { fetchStaticStateFactory } from '../factories/static-state-factory.js';
|
|
8
11
|
import { SolutionType } from '../types/controller-constants.js';
|
|
12
|
+
import { validateControllerNames } from '../validation/controller-validation.js';
|
|
9
13
|
/**
|
|
10
14
|
* Initializes a Commerce engine definition in SSR with given controllers definitions and commerce engine config.
|
|
11
15
|
* @param options - The commerce engine definition
|
|
@@ -35,10 +39,17 @@ export function defineCommerceEngine(options) {
|
|
|
35
39
|
const setAccessToken = (accessToken) => {
|
|
36
40
|
engineOptions.configuration.accessToken = accessToken;
|
|
37
41
|
};
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
controllerDefinitions && validateControllerNames(controllerDefinitions);
|
|
43
|
+
const augmentedControllerDefinition = {
|
|
44
|
+
...controllerDefinitions,
|
|
45
|
+
parameterManager: defineParameterManager(),
|
|
46
|
+
context: defineContext(),
|
|
47
|
+
cart: defineCart(),
|
|
48
|
+
};
|
|
49
|
+
const fetchStaticState = fetchStaticStateFactory(augmentedControllerDefinition, getOptions());
|
|
50
|
+
const hydrateStaticState = hydratedStaticStateFactory(augmentedControllerDefinition, getOptions());
|
|
51
|
+
const fetchRecommendationStaticState = fetchRecommendationStaticStateFactory(augmentedControllerDefinition, getOptions());
|
|
52
|
+
const hydrateRecommendationStaticState = hydratedRecommendationStaticStateFactory(augmentedControllerDefinition, getOptions());
|
|
42
53
|
const commonMethods = {
|
|
43
54
|
setNavigatorContextProvider,
|
|
44
55
|
getAccessToken,
|
|
@@ -4,6 +4,8 @@ import { stateKey } from '../../../app/state-key.js';
|
|
|
4
4
|
import { createWaitForActionMiddleware, createWaitForActionMiddlewareForRecommendation, } from '../../../utils/utils.js';
|
|
5
5
|
import { buildControllerDefinitions } from '../controller-utils.js';
|
|
6
6
|
import { SolutionType } from '../types/controller-constants.js';
|
|
7
|
+
import { wireControllerParams } from '../utils/controller-wiring.js';
|
|
8
|
+
import { extendEngineConfiguration } from '../utils/engine-wiring.js';
|
|
7
9
|
function isListingFetchCompletedAction(action) {
|
|
8
10
|
return /^commerce\/productListing\/fetch\/(fulfilled|rejected)$/.test(action.type);
|
|
9
11
|
}
|
|
@@ -55,33 +57,26 @@ function buildSSRCommerceEngine(solutionType, options, recommendationCount) {
|
|
|
55
57
|
},
|
|
56
58
|
};
|
|
57
59
|
}
|
|
58
|
-
function fetchActiveRecommendationControllers(controllerProps, solutionType) {
|
|
59
|
-
return solutionType === SolutionType.recommendation
|
|
60
|
-
? Object.values(controllerProps).filter((controller) => controller &&
|
|
61
|
-
typeof controller === 'object' &&
|
|
62
|
-
'enabled' in controller &&
|
|
63
|
-
controller.enabled).length
|
|
64
|
-
: 0;
|
|
65
|
-
}
|
|
66
60
|
export const buildFactory = (controllerDefinitions, options) => (solutionType) => async (...[buildOptions]) => {
|
|
61
|
+
const controllerProps = wireControllerParams(solutionType, controllerDefinitions, buildOptions); // TODO: KIT-4754: remove non-null assertion operator
|
|
67
62
|
const logger = buildLogger(options.loggerOptions);
|
|
68
63
|
if (!options.navigatorContextProvider) {
|
|
69
64
|
logger.warn('[WARNING] Missing navigator context in server-side code. Make sure to set it with `setNavigatorContextProvider` before calling fetchStaticState()');
|
|
70
65
|
}
|
|
71
|
-
const
|
|
72
|
-
? buildOptions
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
: options,
|
|
66
|
+
const enabledRecommendationControllers = buildOptions && 'recommendations' in buildOptions // TODO: KIT-4754: remove non-null assertion
|
|
67
|
+
? buildOptions
|
|
68
|
+
?.recommendations.length
|
|
69
|
+
: 0;
|
|
70
|
+
const engineOptions = {
|
|
71
|
+
...options,
|
|
72
|
+
configuration: extendEngineConfiguration(options.configuration, buildOptions), // TODO: KIT-4754: remove non-null assertion operator
|
|
73
|
+
};
|
|
74
|
+
const engine = buildSSRCommerceEngine(solutionType, engineOptions, enabledRecommendationControllers);
|
|
78
75
|
const controllers = buildControllerDefinitions({
|
|
79
76
|
definitionsMap: (controllerDefinitions ?? {}),
|
|
80
77
|
engine,
|
|
81
78
|
solutionType,
|
|
82
|
-
propsMap:
|
|
83
|
-
? buildOptions.controllers
|
|
84
|
-
: {}),
|
|
79
|
+
propsMap: controllerProps,
|
|
85
80
|
});
|
|
86
81
|
return {
|
|
87
82
|
engine,
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { buildFactory, } from './build-factory.js';
|
|
2
2
|
export function hydratedStaticStateFactory(controllerDefinitions, options) {
|
|
3
3
|
return (solutionType) => async (...params) => {
|
|
4
|
-
const solutionTypeBuild = await buildFactory(controllerDefinitions,
|
|
5
|
-
...options,
|
|
6
|
-
})(solutionType);
|
|
4
|
+
const solutionTypeBuild = await buildFactory(controllerDefinitions, options)(solutionType);
|
|
7
5
|
const { engine, controllers } = await solutionTypeBuild(...params);
|
|
8
6
|
params[0].searchActions.forEach((action) => {
|
|
9
7
|
engine.dispatch(action);
|
|
@@ -1,26 +1,22 @@
|
|
|
1
|
-
import { filterObject } from '../../../utils/utils.js';
|
|
2
1
|
import { createStaticState } from '../controller-utils.js';
|
|
3
2
|
import { SolutionType } from '../types/controller-constants.js';
|
|
4
3
|
import { filterRecommendationControllers } from '../utils/recommendation-filter.js';
|
|
5
4
|
import { buildFactory, } from './build-factory.js';
|
|
6
5
|
export function fetchRecommendationStaticStateFactory(controllerDefinitions, options) {
|
|
7
|
-
const getAllowedRecommendationKeys = (props) => {
|
|
8
|
-
if (props && 'controllers' in props) {
|
|
9
|
-
const enabledRecommendationControllers = filterObject(props.controllers, (value) => Boolean(value.enabled));
|
|
10
|
-
return Object.keys(enabledRecommendationControllers);
|
|
11
|
-
}
|
|
12
|
-
return [];
|
|
13
|
-
};
|
|
14
6
|
return async (...params) => {
|
|
15
7
|
const [props] = params;
|
|
16
|
-
const allowedRecommendationKeys =
|
|
8
|
+
const allowedRecommendationKeys = props.recommendations;
|
|
17
9
|
const solutionTypeBuild = await buildFactory(controllerDefinitions, options)(SolutionType.recommendation);
|
|
18
10
|
const { engine, controllers } = (await solutionTypeBuild(...params));
|
|
19
11
|
filterRecommendationControllers(controllers, controllerDefinitions ?? {}).refresh(allowedRecommendationKeys);
|
|
20
12
|
const searchActions = await Promise.all(engine.waitForRequestCompletedAction());
|
|
21
|
-
|
|
13
|
+
const staticState = createStaticState({
|
|
22
14
|
searchActions,
|
|
23
15
|
controllers,
|
|
24
16
|
});
|
|
17
|
+
return {
|
|
18
|
+
...params[0], // TODO: KIT-4754: remove index access after no longer relying on OptionTuple type
|
|
19
|
+
...staticState,
|
|
20
|
+
};
|
|
25
21
|
};
|
|
26
22
|
}
|
|
@@ -6,9 +6,7 @@ import { SolutionType } from '../types/controller-constants.js';
|
|
|
6
6
|
import { buildFactory, } from './build-factory.js';
|
|
7
7
|
export function fetchStaticStateFactory(controllerDefinitions, options) {
|
|
8
8
|
return (solutionType) => async (...params) => {
|
|
9
|
-
const solutionTypeBuild = await buildFactory(controllerDefinitions,
|
|
10
|
-
...options,
|
|
11
|
-
})(solutionType);
|
|
9
|
+
const solutionTypeBuild = await buildFactory(controllerDefinitions, options)(solutionType);
|
|
12
10
|
const { engine, controllers } = await solutionTypeBuild(...params);
|
|
13
11
|
options.configuration.preprocessRequest =
|
|
14
12
|
augmentPreprocessRequestWithForwardedFor({
|
|
@@ -29,6 +27,9 @@ export function fetchStaticStateFactory(controllerDefinitions, options) {
|
|
|
29
27
|
searchActions,
|
|
30
28
|
controllers,
|
|
31
29
|
});
|
|
32
|
-
return
|
|
30
|
+
return {
|
|
31
|
+
...params[0], // TODO: KIT-4754: remove index access after no longer relying on OptionTuple type
|
|
32
|
+
...staticState,
|
|
33
|
+
};
|
|
33
34
|
};
|
|
34
35
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { ArrayValue, RecordValue, Schema, StringValue } from '@coveo/bueno';
|
|
2
|
+
import { contextDefinition } from '../../../features/commerce/context/context-validation.js';
|
|
3
|
+
import { parametersDefinition } from '../../../features/commerce/parameters/parameters-schema.js';
|
|
4
|
+
import { nonEmptyString, requiredEmptyAllowedString } from '../../../utils/validate-payload.js';
|
|
5
|
+
import { SolutionType } from '../types/controller-constants.js';
|
|
6
|
+
import { getRecommendationDefinitions, isRecommendationDefinition, } from './recommendation-filter.js';
|
|
7
|
+
const requiredDefinition = {
|
|
8
|
+
context: new RecordValue({
|
|
9
|
+
options: { required: true },
|
|
10
|
+
values: contextDefinition,
|
|
11
|
+
}),
|
|
12
|
+
};
|
|
13
|
+
const listingDefinition = {
|
|
14
|
+
...requiredDefinition,
|
|
15
|
+
searchParams: new RecordValue({
|
|
16
|
+
values: parametersDefinition,
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
19
|
+
const standaloneDefinition = {
|
|
20
|
+
...requiredDefinition,
|
|
21
|
+
};
|
|
22
|
+
const searchDefinition = {
|
|
23
|
+
...requiredDefinition,
|
|
24
|
+
searchParams: new RecordValue({
|
|
25
|
+
options: { required: true },
|
|
26
|
+
values: { query: requiredEmptyAllowedString, ...parametersDefinition },
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
const recommendationsDefinition = (recommendationName) => ({
|
|
30
|
+
...requiredDefinition,
|
|
31
|
+
recommendations: new ArrayValue({
|
|
32
|
+
each: new StringValue({
|
|
33
|
+
required: true,
|
|
34
|
+
constrainTo: recommendationName,
|
|
35
|
+
emptyAllowed: false,
|
|
36
|
+
}),
|
|
37
|
+
required: true,
|
|
38
|
+
}),
|
|
39
|
+
productId: nonEmptyString,
|
|
40
|
+
});
|
|
41
|
+
export const listingDefinitionSchema = new Schema(listingDefinition);
|
|
42
|
+
export const searchDefinitionSchema = new Schema(searchDefinition);
|
|
43
|
+
export const standaloneDefinitionSchema = new Schema(standaloneDefinition);
|
|
44
|
+
export const recommendationsDefinitionSchema = (recommendationName) => new Schema(recommendationsDefinition(recommendationName));
|
|
45
|
+
/**
|
|
46
|
+
* Validates the build configuration based on the solution type.
|
|
47
|
+
*/
|
|
48
|
+
function validateBuildConfig(solutionType, controllerDefinitions, buildConfig) {
|
|
49
|
+
const validationMap = {
|
|
50
|
+
[SolutionType.listing]: listingDefinitionSchema,
|
|
51
|
+
[SolutionType.search]: searchDefinitionSchema,
|
|
52
|
+
[SolutionType.standalone]: standaloneDefinitionSchema,
|
|
53
|
+
[SolutionType.recommendation]: recommendationsDefinitionSchema(Object.keys(getRecommendationDefinitions(controllerDefinitions))),
|
|
54
|
+
};
|
|
55
|
+
const schema = validationMap[solutionType];
|
|
56
|
+
schema.validate(buildConfig);
|
|
57
|
+
}
|
|
58
|
+
function createControllerWirer(buildConfig, controllerDefinitions, controllerProps) {
|
|
59
|
+
return {
|
|
60
|
+
wireParameterManager: () => {
|
|
61
|
+
if (!controllerDefinitions?.parameterManager)
|
|
62
|
+
return;
|
|
63
|
+
const { searchParams } = buildConfig;
|
|
64
|
+
const { query, ...rest } = searchParams || {};
|
|
65
|
+
const parameters = {
|
|
66
|
+
...(query && { q: query }),
|
|
67
|
+
...(rest && typeof rest === 'object' ? rest : {}),
|
|
68
|
+
};
|
|
69
|
+
controllerProps.parameterManager = {
|
|
70
|
+
initialState: { parameters },
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
wireContext: () => {
|
|
74
|
+
if (!controllerDefinitions?.context)
|
|
75
|
+
return;
|
|
76
|
+
const { context } = buildConfig;
|
|
77
|
+
controllerProps.context = {
|
|
78
|
+
initialState: {
|
|
79
|
+
...context,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
wireCart: () => {
|
|
84
|
+
if (!controllerDefinitions?.cart || !buildConfig.cart)
|
|
85
|
+
return;
|
|
86
|
+
controllerProps.cart = {
|
|
87
|
+
initialState: buildConfig.cart,
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
wireRecommendations: () => {
|
|
91
|
+
if (!('recommendations' in buildConfig)) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
for (const recController in controllerDefinitions) {
|
|
95
|
+
if (isRecommendationDefinition(controllerDefinitions[recController])) {
|
|
96
|
+
controllerProps[recController] = {
|
|
97
|
+
initialState: {
|
|
98
|
+
...('productId' in buildConfig && {
|
|
99
|
+
productId: buildConfig.productId,
|
|
100
|
+
}),
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function wireCommonControllers(wirer) {
|
|
109
|
+
wirer.wireCart();
|
|
110
|
+
wirer.wireContext();
|
|
111
|
+
}
|
|
112
|
+
function wireSolutionSpecificControllers(solutionType, wirer) {
|
|
113
|
+
switch (solutionType) {
|
|
114
|
+
case SolutionType.search: {
|
|
115
|
+
wirer.wireParameterManager();
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case SolutionType.listing: {
|
|
119
|
+
wirer.wireParameterManager();
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
case SolutionType.recommendation: {
|
|
123
|
+
wirer.wireRecommendations();
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case SolutionType.standalone:
|
|
127
|
+
// No additional wiring needed for standalone
|
|
128
|
+
break;
|
|
129
|
+
default: {
|
|
130
|
+
// Exhaustive check - TypeScript will error if we miss a case
|
|
131
|
+
const _exhaustiveCheck = solutionType;
|
|
132
|
+
throw new Error(`Unsupported solution type: ${_exhaustiveCheck}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Converts simple user configuration (URL, language, currency, query) into the
|
|
138
|
+
* nested structure required by the internal {@link Build} method. Automatically generates
|
|
139
|
+
* appropriate controllers (context, cart, parameterManager) transparently.
|
|
140
|
+
*
|
|
141
|
+
* @param solutionType - The type of solution (search, listing, recommendation, standalone)
|
|
142
|
+
* @param controllerDefinitions - Map of controller definitions to be initialized
|
|
143
|
+
* @param buildConfig - Simple configuration object with simplified controller properties
|
|
144
|
+
* @returns Formatted and validated controller properties map defined for the controller in the definition
|
|
145
|
+
*/
|
|
146
|
+
export function wireControllerParams(solutionType, controllerDefinitions, buildConfig) {
|
|
147
|
+
validateBuildConfig(solutionType, controllerDefinitions, buildConfig);
|
|
148
|
+
const controllerProps = buildConfig.controllers ?? {};
|
|
149
|
+
const wirer = createControllerWirer(buildConfig, controllerDefinitions, controllerProps);
|
|
150
|
+
wireCommonControllers(wirer);
|
|
151
|
+
wireSolutionSpecificControllers(solutionType, wirer);
|
|
152
|
+
return controllerProps;
|
|
153
|
+
}
|
|
@@ -1,15 +1,34 @@
|
|
|
1
1
|
import { MultipleRecommendationError } from '../../common/errors.js';
|
|
2
2
|
import { recommendationInternalOptionKey } from '../types/controller-constants.js';
|
|
3
|
+
/**
|
|
4
|
+
* Type guard to check if a controller definition is a recommendation controller.
|
|
5
|
+
* A recommendation controller must have both the recommendation flag and a slotId.
|
|
6
|
+
*/
|
|
7
|
+
export function isRecommendationDefinition(controllerDefinition) {
|
|
8
|
+
const hasRecommendationFlag = 'recommendation' in controllerDefinition &&
|
|
9
|
+
controllerDefinition.recommendation === true;
|
|
10
|
+
const hasValidSlotId = recommendationInternalOptionKey in controllerDefinition &&
|
|
11
|
+
'slotId' in
|
|
12
|
+
controllerDefinition[recommendationInternalOptionKey];
|
|
13
|
+
return hasRecommendationFlag && hasValidSlotId;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Extracts recommendation controller definitions from a controller definitions map.
|
|
17
|
+
*
|
|
18
|
+
* @param controllerDefinitions - Map of controller definitions
|
|
19
|
+
* @returns Object containing only the recommendation controller definitions
|
|
20
|
+
*/
|
|
21
|
+
export function getRecommendationDefinitions(controllerDefinitions) {
|
|
22
|
+
const recommendationMap = {};
|
|
23
|
+
for (const [name, definition] of Object.entries(controllerDefinitions)) {
|
|
24
|
+
if (isRecommendationDefinition(definition)) {
|
|
25
|
+
recommendationMap[name] = definition;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return recommendationMap;
|
|
29
|
+
}
|
|
3
30
|
export function filterRecommendationControllers(controllers, controllerDefinitions) {
|
|
4
31
|
const slotIdSet = new Set();
|
|
5
|
-
const isRecommendationDefinition = (controllerDefinition) => {
|
|
6
|
-
const isControllerRecommendation = 'recommendation' in controllerDefinition &&
|
|
7
|
-
controllerDefinition.recommendation === true;
|
|
8
|
-
const hasSlotId = recommendationInternalOptionKey in controllerDefinition &&
|
|
9
|
-
'slotId' in
|
|
10
|
-
controllerDefinition[recommendationInternalOptionKey];
|
|
11
|
-
return isControllerRecommendation && hasSlotId;
|
|
12
|
-
};
|
|
13
32
|
const ensureSingleRecommendationPerSlot = (slotId) => {
|
|
14
33
|
throw new MultipleRecommendationError(slotId);
|
|
15
34
|
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const validateControllerNames = (controllers) => {
|
|
2
|
+
const reservedNames = ['context', 'cart', 'parameterManager'];
|
|
3
|
+
const invalidNames = Object.keys(controllers).filter((name) => reservedNames.includes(name));
|
|
4
|
+
if (invalidNames.length > 0) {
|
|
5
|
+
throw new Error(`Reserved controller names found: ${invalidNames.join(', ')}. Please use different controller names than ${reservedNames.join(', ')}.`);
|
|
6
|
+
}
|
|
7
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { vi } from 'vitest';
|
|
2
|
+
import { recommendationInternalOptionKey } from '../ssr-next/commerce/types/controller-constants.js';
|
|
2
3
|
import { buildMockController, buildMockControllerWithInitialState, } from './mock-controller.js';
|
|
3
4
|
export function defineMockController() {
|
|
4
5
|
return {
|
|
@@ -41,3 +42,17 @@ export function defineMockCommerceControllerWithProps(options) {
|
|
|
41
42
|
recommendation: options?.recommendation ?? true,
|
|
42
43
|
};
|
|
43
44
|
}
|
|
45
|
+
export function defineMockRecommendationDefinition(slotId) {
|
|
46
|
+
return {
|
|
47
|
+
recommendation: true,
|
|
48
|
+
[recommendationInternalOptionKey]: {
|
|
49
|
+
slotId,
|
|
50
|
+
},
|
|
51
|
+
buildWithProps: vi.fn((engine, props) => ({
|
|
52
|
+
_kind: 'some-kind',
|
|
53
|
+
...buildMockControllerWithInitialState(engine, {
|
|
54
|
+
initialState: props?.initialState,
|
|
55
|
+
}),
|
|
56
|
+
})),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = "3.29.0-pre.
|
|
1
|
+
export const VERSION = "3.29.0-pre.075377ce6d" || 'Test version';
|
|
2
2
|
export const COVEO_FRAMEWORK = ['@coveo/atomic', '@coveo/quantic'];
|