@coveo/headless 3.28.4-pre.f345a65b79 → 3.28.4-pre.fe2dbcea63

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.
Files changed (59) hide show
  1. package/dist/cjs/case-assist/headless.cjs +1 -1
  2. package/dist/cjs/commerce/headless.cjs +61 -25
  3. package/dist/cjs/commerce/node.cjs.stats.json +1 -1
  4. package/dist/cjs/headless.cjs +1 -1
  5. package/dist/cjs/insight/headless.cjs +1 -1
  6. package/dist/cjs/recommendation/headless.cjs +1 -1
  7. package/dist/cjs/ssr/headless.cjs +1 -1
  8. package/dist/cjs/ssr-commerce/headless.cjs +61 -25
  9. package/dist/cjs/ssr-commerce/node.cjs.stats.json +1 -1
  10. package/dist/cjs/ssr-commerce-next/headless.cjs +61 -25
  11. package/dist/cjs/ssr-commerce-next/node.cjs.stats.json +1 -1
  12. package/dist/cjs/ssr-next/headless.cjs +1 -1
  13. package/dist/definitions/api/commerce/common/product.d.ts +4 -0
  14. package/dist/definitions/ssr-commerce-next.index.d.ts +1 -1
  15. package/dist/definitions/ssr-next/commerce/controller-utils.d.ts +6 -7
  16. package/dist/definitions/ssr-next/commerce/controllers/context/headless-context.ssr.d.ts +3 -1
  17. package/dist/definitions/ssr-next/commerce/controllers/parameter-manager/headless-core-parameter-manager.ssr.d.ts +2 -1
  18. package/dist/definitions/ssr-next/commerce/engine/commerce-engine.ssr.d.ts +28 -3
  19. package/dist/definitions/ssr-next/commerce/types/controller-inference.d.ts +3 -2
  20. package/dist/definitions/ssr-next/commerce/types/engine.d.ts +0 -8
  21. package/dist/definitions/ssr-next/commerce/types/fetch-static-state.d.ts +22 -1
  22. package/dist/definitions/ssr-next/common/builders/controller-builder.d.ts +17 -0
  23. package/dist/definitions/ssr-next/common/builders/static-controller-builder.d.ts +24 -0
  24. package/dist/definitions/ssr-next/common/controller-utils.d.ts +6 -6
  25. package/dist/definitions/ssr-next/common/navigator-context-utils.d.ts +16 -0
  26. package/dist/definitions/ssr-next/common/navigator-context-utils.spec.d.ts +1 -0
  27. package/dist/definitions/ssr-next/common/types/controllers.d.ts +6 -0
  28. package/dist/definitions/ssr-next/common/types/engine.d.ts +0 -8
  29. package/dist/definitions/ssr-next/common/types/fetch-static-state.d.ts +4 -1
  30. package/dist/definitions/ssr-next/common/types/inference.d.ts +3 -2
  31. package/dist/definitions/ssr-next/search/engine/search-engine.ssr.d.ts +18 -20
  32. package/dist/definitions/test/mock-controller.d.ts +9 -0
  33. package/dist/definitions/test/mock-ssr-controller-definitions.d.ts +38 -0
  34. package/dist/esm/controllers/commerce/core/interactive-product/headless-core-interactive-product.js +2 -1
  35. package/dist/esm/features/commerce/instant-products/instant-products-slice.js +6 -3
  36. package/dist/esm/features/commerce/product-listing/product-listing-slice.js +7 -4
  37. package/dist/esm/features/commerce/recommendations/recommendations-slice.js +7 -4
  38. package/dist/esm/features/commerce/search/search-slice.js +7 -4
  39. package/dist/esm/ssr-next/commerce/controller-utils.js +6 -18
  40. package/dist/esm/ssr-next/commerce/controllers/cart/headless-cart.ssr.js +3 -5
  41. package/dist/esm/ssr-next/commerce/controllers/context/headless-context.ssr.js +1 -1
  42. package/dist/esm/ssr-next/commerce/engine/commerce-engine.ssr.js +32 -12
  43. package/dist/esm/ssr-next/commerce/factories/static-state-factory.js +3 -10
  44. package/dist/esm/ssr-next/common/builders/controller-builder.js +38 -0
  45. package/dist/esm/ssr-next/common/builders/static-controller-builder.js +46 -0
  46. package/dist/esm/ssr-next/common/controller-utils.js +7 -13
  47. package/dist/esm/ssr-next/common/navigator-context-utils.js +27 -0
  48. package/dist/esm/ssr-next/search/engine/search-engine.ssr.js +34 -40
  49. package/dist/esm/test/mock-controller.js +13 -0
  50. package/dist/esm/test/mock-ssr-controller-definitions.js +43 -0
  51. package/dist/esm/utils/version.js +1 -1
  52. package/dist/quantic/case-assist/headless.js +1 -1
  53. package/dist/quantic/commerce/browser.cjs.stats.json +1 -1
  54. package/dist/quantic/commerce/headless.js +7 -7
  55. package/dist/quantic/commerce/headless.js.map +3 -3
  56. package/dist/quantic/headless.js +1 -1
  57. package/dist/quantic/insight/headless.js +1 -1
  58. package/dist/quantic/recommendation/headless.js +1 -1
  59. package/package.json +1 -1
@@ -31,7 +31,7 @@ export const recommendationsReducer = createReducer(getRecommendationsInitialSta
31
31
  return;
32
32
  }
33
33
  const paginationOffset = getPaginationOffset(action.payload);
34
- recommendations.products = response.products.map((product, index) => preprocessProduct(product, paginationOffset + index + 1));
34
+ recommendations.products = response.products.map((product, index) => preprocessProduct(product, paginationOffset + index + 1, response.responseId));
35
35
  })
36
36
  .addCase(fetchMoreRecommendations.fulfilled, (state, action) => {
37
37
  if (!action.payload) {
@@ -45,7 +45,7 @@ export const recommendationsReducer = createReducer(getRecommendationsInitialSta
45
45
  return;
46
46
  }
47
47
  const paginationOffset = getPaginationOffset(action.payload);
48
- recommendations.products = recommendations.products.concat(response.products.map((product, index) => preprocessProduct(product, paginationOffset + index + 1)));
48
+ recommendations.products = recommendations.products.concat(response.products.map((product, index) => preprocessProduct(product, paginationOffset + index + 1, response.responseId)));
49
49
  })
50
50
  .addCase(fetchRecommendations.pending, (state, action) => {
51
51
  handlePending(state, action.meta.arg.slotId);
@@ -67,6 +67,7 @@ export const recommendationsReducer = createReducer(getRecommendationsInitialSta
67
67
  if (currentParentIndex === -1 || childToPromote === undefined) {
68
68
  return;
69
69
  }
70
+ const responseId = products[currentParentIndex].responseId;
70
71
  const position = products[currentParentIndex].position;
71
72
  const { children, totalNumberOfChildren } = products[currentParentIndex];
72
73
  const newParent = {
@@ -74,6 +75,7 @@ export const recommendationsReducer = createReducer(getRecommendationsInitialSta
74
75
  children,
75
76
  totalNumberOfChildren,
76
77
  position,
78
+ responseId,
77
79
  };
78
80
  products.splice(currentParentIndex, 1, newParent);
79
81
  })
@@ -119,15 +121,16 @@ function getPaginationOffset(actionPayload) {
119
121
  const pagination = actionPayload.response.pagination;
120
122
  return pagination.page * pagination.perPage;
121
123
  }
122
- function preprocessProduct(product, position) {
124
+ function preprocessProduct(product, position, responseId) {
123
125
  const isParentAlreadyInChildren = product.children.some((child) => child.permanentid === product.permanentid);
124
126
  if (product.children.length === 0 || isParentAlreadyInChildren) {
125
- return { ...product, position };
127
+ return { ...product, position, responseId };
126
128
  }
127
129
  const { children, totalNumberOfChildren: _totalNumberOfChildren, ...restOfProduct } = product;
128
130
  return {
129
131
  ...product,
130
132
  children: [restOfProduct, ...children],
131
133
  position,
134
+ responseId,
132
135
  };
133
136
  }
@@ -14,7 +14,7 @@ export const commerceSearchReducer = createReducer(getCommerceSearchInitialState
14
14
  .addCase(executeSearch.fulfilled, (state, action) => {
15
15
  const paginationOffset = getPaginationOffset(action.payload);
16
16
  handleFulfilled(state, action.payload.response, action.payload.queryExecuted);
17
- state.products = action.payload.response.products.map((product, index) => preprocessProduct(product, paginationOffset + index + 1));
17
+ state.products = action.payload.response.products.map((product, index) => preprocessProduct(product, paginationOffset + index + 1, action.payload.response.responseId));
18
18
  })
19
19
  .addCase(fetchMoreProducts.fulfilled, (state, action) => {
20
20
  if (!action.payload) {
@@ -22,7 +22,7 @@ export const commerceSearchReducer = createReducer(getCommerceSearchInitialState
22
22
  }
23
23
  const paginationOffset = getPaginationOffset(action.payload);
24
24
  handleFulfilled(state, action.payload.response, action.payload.queryExecuted);
25
- state.products = state.products.concat(action.payload.response.products.map((product, index) => preprocessProduct(product, paginationOffset + index + 1)));
25
+ state.products = state.products.concat(action.payload.response.products.map((product, index) => preprocessProduct(product, paginationOffset + index + 1, action.payload?.response.responseId)));
26
26
  })
27
27
  .addCase(executeSearch.pending, (state, action) => {
28
28
  handlePending(state, action.meta.requestId);
@@ -40,6 +40,7 @@ export const commerceSearchReducer = createReducer(getCommerceSearchInitialState
40
40
  if (currentParentIndex === -1 || childToPromote === undefined) {
41
41
  return;
42
42
  }
43
+ const responseId = products[currentParentIndex].responseId;
43
44
  const position = products[currentParentIndex].position;
44
45
  const { children, totalNumberOfChildren } = products[currentParentIndex];
45
46
  const newParent = {
@@ -47,6 +48,7 @@ export const commerceSearchReducer = createReducer(getCommerceSearchInitialState
47
48
  children,
48
49
  totalNumberOfChildren,
49
50
  position,
51
+ responseId,
50
52
  };
51
53
  products.splice(currentParentIndex, 1, newParent);
52
54
  })
@@ -76,15 +78,16 @@ function getPaginationOffset(payload) {
76
78
  const pagination = payload.response.pagination;
77
79
  return pagination.page * pagination.perPage;
78
80
  }
79
- function preprocessProduct(product, position) {
81
+ function preprocessProduct(product, position, responseId) {
80
82
  const isParentAlreadyInChildren = product.children.some((child) => child.permanentid === product.permanentid);
81
83
  if (product.children.length === 0 || isParentAlreadyInChildren) {
82
- return { ...product, position };
84
+ return { ...product, position, responseId };
83
85
  }
84
86
  const { children, totalNumberOfChildren: _totalNumberOfChildren, ...restOfProduct } = product;
85
87
  return {
86
88
  ...product,
87
89
  children: [restOfProduct, ...children],
88
90
  position,
91
+ responseId,
89
92
  };
90
93
  }
@@ -1,22 +1,13 @@
1
1
  import { clone, filterObject, mapObject } from '../../utils/utils.js';
2
+ import { ControllerBuilder } from '../common/builders/controller-builder.js';
3
+ import { createStaticControllerBuilder } from '../common/builders/static-controller-builder.js';
2
4
  import { InvalidControllerDefinition } from '../common/errors.js';
3
- function hasKindProperty(controller) {
4
- return '_kind' in controller;
5
- }
6
5
  export function createStaticState({ searchActions, controllers, }) {
7
6
  return {
8
- controllers: mapObject(controllers, (controller) => ({
9
- state: clone(controller.state),
10
- ...(hasKindProperty(controller) && { _kind: controller._kind }),
11
- })),
7
+ controllers: mapObject(controllers, (controller) => createStaticControllerBuilder(controller).build()),
12
8
  searchActions: searchActions.map((action) => clone(action)),
13
9
  };
14
10
  }
15
- function buildControllerFromDefinition({ definition, engine, solutionType, props, }) {
16
- return ('build' in definition
17
- ? definition.build(engine, solutionType)
18
- : definition.buildWithProps(engine, props, solutionType));
19
- }
20
11
  export function buildControllerDefinitions({ definitionsMap, engine, solutionType, propsMap, }) {
21
12
  const controllerMap = mapObject(definitionsMap, (definition, key) => {
22
13
  const unavailableInSolutionType = () => !(solutionType in definition) ||
@@ -26,12 +17,9 @@ export function buildControllerDefinitions({ definitionsMap, engine, solutionTyp
26
17
  if (unavailableInSolutionType()) {
27
18
  return null;
28
19
  }
29
- return buildControllerFromDefinition({
30
- definition,
31
- engine,
32
- solutionType,
33
- props,
34
- });
20
+ return new ControllerBuilder(definition, engine, props)
21
+ .withAdditionalArgs([solutionType])
22
+ .build();
35
23
  });
36
24
  return filterObject(controllerMap, (value) => value !== null);
37
25
  }
@@ -1,5 +1,4 @@
1
1
  import { buildCart, } from '../../../../controllers/commerce/context/cart/headless-cart.js';
2
- import { MissingControllerProps } from '../../../common/errors.js';
3
2
  import { createControllerWithKind, Kind } from '../../types/kind.js';
4
3
  /**
5
4
  * Defines a `Cart` controller instance.
@@ -14,10 +13,9 @@ export function defineCart() {
14
13
  standalone: true,
15
14
  recommendation: true,
16
15
  buildWithProps: (engine, props) => {
17
- if (props === undefined) {
18
- throw new MissingControllerProps(Kind.Cart);
19
- }
20
- const controller = buildCart(engine, { initialState: props.initialState });
16
+ const controller = buildCart(engine, {
17
+ initialState: props ? props.initialState : {},
18
+ });
21
19
  return createControllerWithKind(controller, Kind.Cart);
22
20
  },
23
21
  };
@@ -17,7 +17,7 @@ export function defineContext() {
17
17
  if (props === undefined) {
18
18
  throw new MissingControllerProps(Kind.Context);
19
19
  }
20
- const controller = buildContext(engine, { options: props });
20
+ const controller = buildContext(engine, { options: props.initialState });
21
21
  return createControllerWithKind(controller, Kind.Context);
22
22
  },
23
23
  };
@@ -18,29 +18,49 @@ import { SolutionType } from '../types/controller-constants.js';
18
18
  *
19
19
  * @example
20
20
  * ```ts
21
- * const engineDefinition = defineCommerceEngine(engineConfig);
22
- * type SearchStaticState = InferStaticState<typeof engineDefinition>;
23
- * type SearchHydratedState = InferHydratedState<typeof engineDefinition>;
21
+ * const { listingEngineDefinition } = defineCommerceEngine(engineConfig);
22
+ *
23
+ * // Pass navigator context directly to fetchStaticState
24
+ * const staticState = await listingEngineDefinition.fetchStaticState({
25
+ * navigatorContext: {
26
+ * forwardedFor: req.ip,
27
+ * referrer: req.headers.referer || null,
28
+ * userAgent: req.headers['user-agent'] || null,
29
+ * clientId: 'unique-session-id'
30
+ * }
31
+ * });
32
+ *
33
+ * // Framework examples:
34
+ * // Express.js
35
+ * app.get('/api/listing', async (req, res) => {
36
+ * const state = await listingEngineDefinition.fetchStaticState({
37
+ * navigatorContext: {
38
+ * forwardedFor: req.ip,
39
+ * referrer: req.headers.referer || null,
40
+ * userAgent: req.headers['user-agent'] || null,
41
+ * clientId: req.sessionID
42
+ * }
43
+ * });
44
+ * res.json(state);
45
+ * });
46
+ *
47
+ * type SearchStaticState = InferStaticState<typeof listingEngineDefinition>;
48
+ * type SearchHydratedState = InferHydratedState<typeof listingEngineDefinition>;
24
49
  * ```
25
50
  *
26
51
  * @group Engine
27
52
  */
28
53
  export function defineCommerceEngine(options) {
29
54
  const { controllers: controllerDefinitions, ...engineOptions } = options;
30
- const getOptions = () => engineOptions;
31
- const setNavigatorContextProvider = (navigatorContextProvider) => {
32
- engineOptions.navigatorContextProvider = navigatorContextProvider;
33
- };
34
55
  const getAccessToken = () => engineOptions.configuration.accessToken;
35
56
  const setAccessToken = (accessToken) => {
36
57
  engineOptions.configuration.accessToken = accessToken;
37
58
  };
38
- const fetchStaticState = fetchStaticStateFactory(controllerDefinitions, getOptions());
39
- const hydrateStaticState = hydratedStaticStateFactory(controllerDefinitions, getOptions());
40
- const fetchRecommendationStaticState = fetchRecommendationStaticStateFactory(controllerDefinitions, getOptions());
41
- const hydrateRecommendationStaticState = hydratedRecommendationStaticStateFactory(controllerDefinitions, getOptions());
59
+ const fetchStaticState = fetchStaticStateFactory(controllerDefinitions, engineOptions);
60
+ const hydrateStaticState = hydratedStaticStateFactory(controllerDefinitions, engineOptions);
61
+ const fetchRecommendationStaticState = fetchRecommendationStaticStateFactory(controllerDefinitions, engineOptions);
62
+ const hydrateRecommendationStaticState = hydratedRecommendationStaticStateFactory(controllerDefinitions, engineOptions);
42
63
  const commonMethods = {
43
- setNavigatorContextProvider,
44
64
  getAccessToken,
45
65
  setAccessToken,
46
66
  };
@@ -1,21 +1,14 @@
1
1
  import { buildProductListing } from '../../../controllers/commerce/product-listing/headless-product-listing.js';
2
2
  import { buildSearch } from '../../../controllers/commerce/search/headless-search.js';
3
- import { augmentPreprocessRequestWithForwardedFor } from '../../common/augment-preprocess-request.js';
3
+ import { processNavigatorContext } from '../../common/navigator-context-utils.js';
4
4
  import { createStaticState } from '../controller-utils.js';
5
5
  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 { engineOptions } = processNavigatorContext(params, options);
10
+ const solutionTypeBuild = await buildFactory(controllerDefinitions, engineOptions)(solutionType);
12
11
  const { engine, controllers } = await solutionTypeBuild(...params);
13
- options.configuration.preprocessRequest =
14
- augmentPreprocessRequestWithForwardedFor({
15
- preprocessRequest: options.configuration.preprocessRequest,
16
- navigatorContextProvider: options.navigatorContextProvider,
17
- loggerOptions: options.loggerOptions,
18
- });
19
12
  switch (solutionType) {
20
13
  case SolutionType.listing:
21
14
  buildProductListing(engine).executeFirstRequest();
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Builder class for creating controllers from definitions
3
+ */
4
+ export class ControllerBuilder {
5
+ _definition;
6
+ _engine;
7
+ _props;
8
+ _additionalArgs = [];
9
+ constructor(definition, engine, props) {
10
+ this._definition = definition;
11
+ this._engine = engine;
12
+ this._props = props;
13
+ }
14
+ withAdditionalArgs(additionalArgs = []) {
15
+ this._additionalArgs = additionalArgs;
16
+ return this;
17
+ }
18
+ build() {
19
+ if ('build' in this._definition &&
20
+ typeof this._definition.build === 'function') {
21
+ return this.buildWithoutProps();
22
+ }
23
+ if ('buildWithProps' in this._definition &&
24
+ typeof this._definition.buildWithProps === 'function') {
25
+ return this.buildWithProps();
26
+ }
27
+ throw new Error('Controller definition must have a build or buildWithProps method.');
28
+ }
29
+ buildWithoutProps() {
30
+ const buildFn = this._definition.build;
31
+ return buildFn(this._engine, ...this._additionalArgs);
32
+ }
33
+ buildWithProps() {
34
+ const buildWithPropsFn = this._definition.buildWithProps;
35
+ const controller = buildWithPropsFn(this._engine, this._props, ...this._additionalArgs);
36
+ return { ...controller, initialState: controller.state };
37
+ }
38
+ }
@@ -0,0 +1,46 @@
1
+ import { clone } from '../../../utils/utils.js';
2
+ /**
3
+ * Builder class for creating static controller
4
+ */
5
+ class StaticControllerBuilder {
6
+ _state;
7
+ _kind;
8
+ _initialState;
9
+ withState(state) {
10
+ this._state = clone(state);
11
+ return this;
12
+ }
13
+ withKind(kind) {
14
+ this._kind = kind;
15
+ return this;
16
+ }
17
+ withInitialState(initialState) {
18
+ this._initialState = clone(initialState);
19
+ return this;
20
+ }
21
+ /**
22
+ * Builds and returns the static controller state object
23
+ */
24
+ build() {
25
+ const result = {
26
+ state: this._state,
27
+ };
28
+ if (this._kind) {
29
+ result._kind = this._kind;
30
+ }
31
+ if (this._initialState) {
32
+ result.initialState = this._initialState;
33
+ }
34
+ return result;
35
+ }
36
+ }
37
+ export function createStaticControllerBuilder(controller) {
38
+ const builder = new StaticControllerBuilder().withState(controller.state);
39
+ if ('_kind' in controller) {
40
+ builder.withKind(controller._kind);
41
+ }
42
+ if ('initialState' in controller) {
43
+ builder.withInitialState(controller.initialState);
44
+ }
45
+ return builder;
46
+ }
@@ -1,21 +1,15 @@
1
1
  import { clone, mapObject } from '../../utils/utils.js';
2
- function buildControllerFromDefinition({ definition, engine, props, }) {
3
- return ('build' in definition
4
- ? definition.build(engine)
5
- : definition.buildWithProps(engine, props));
6
- }
2
+ import { ControllerBuilder } from './builders/controller-builder.js';
3
+ import { createStaticControllerBuilder } from './builders/static-controller-builder.js';
7
4
  export function buildControllerDefinitions({ definitionsMap, engine, propsMap, }) {
8
- return mapObject(definitionsMap, (definition, key) => buildControllerFromDefinition({
9
- definition,
10
- engine,
11
- props: propsMap?.[key],
12
- }));
5
+ return mapObject(definitionsMap, (definition, key) => {
6
+ const props = propsMap?.[key];
7
+ return new ControllerBuilder(definition, engine, props).build();
8
+ });
13
9
  }
14
10
  export function createStaticState({ searchAction, controllers, }) {
15
11
  return {
16
- controllers: mapObject(controllers, (controller) => ({
17
- state: clone(controller.state),
18
- })),
12
+ controllers: mapObject(controllers, (controller) => createStaticControllerBuilder(controller).build()),
19
13
  searchAction: clone(searchAction),
20
14
  };
21
15
  }
@@ -0,0 +1,27 @@
1
+ import { augmentPreprocessRequestWithForwardedFor } from './augment-preprocess-request.js';
2
+ /**
3
+ * Processes fetchStaticState parameters and returns enhanced engine options with navigator context.
4
+ * This is the single function that handles all navigator context logic for both commerce and search engines.
5
+ */
6
+ export function processNavigatorContext(params, baseOptions) {
7
+ const [callOptions] = params;
8
+ // If no navigator context, return original options
9
+ if (!callOptions?.navigatorContext) {
10
+ return { engineOptions: baseOptions, callOptions };
11
+ }
12
+ // Create provider function and enhance options
13
+ const navigatorContextProvider = () => callOptions.navigatorContext;
14
+ const engineOptions = {
15
+ ...baseOptions,
16
+ navigatorContextProvider,
17
+ configuration: {
18
+ ...baseOptions.configuration,
19
+ preprocessRequest: augmentPreprocessRequestWithForwardedFor({
20
+ preprocessRequest: baseOptions.configuration?.preprocessRequest,
21
+ navigatorContextProvider,
22
+ loggerOptions: baseOptions.loggerOptions,
23
+ }),
24
+ },
25
+ };
26
+ return { engineOptions, callOptions };
27
+ }
@@ -1,8 +1,7 @@
1
- import { buildLogger } from '../../../app/logger.js';
2
1
  import { buildSearchEngine, } from '../../../app/search-engine/search-engine.js';
3
2
  import { createWaitForActionMiddleware } from '../../../utils/utils.js';
4
- import { augmentPreprocessRequestWithForwardedFor } from '../../common/augment-preprocess-request.js';
5
3
  import { buildControllerDefinitions, createStaticState, } from '../../common/controller-utils.js';
4
+ import { processNavigatorContext } from '../../common/navigator-context-utils.js';
6
5
  function isSearchCompletedAction(action) {
7
6
  return /^search\/executeSearch\/(fulfilled|rejected)$/.test(action.type);
8
7
  }
@@ -23,50 +22,52 @@ function buildSSRSearchEngine(options) {
23
22
  };
24
23
  }
25
24
  /**
26
- * Initializes a Search engine definition in SSR with given controllers definitions and search engine config.
25
+ * Initializes a Search engine definition in SSR with per-call navigator context support.
27
26
  *
28
27
  * @param options - The search engine definition
29
- * @returns Three utility functions to fetch the initial state of the engine in SSR, hydrate the state in CSR,
30
- * and a build function that can be used for edge cases requiring more control.
28
+ * @returns Engine definition with support for per-call navigator context.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * const searchEngine = defineSearchEngine(config);
33
+ *
34
+ * // Pass navigator context directly to fetchStaticState
35
+ * const staticState = await searchEngine.fetchStaticState({
36
+ * navigatorContext: {
37
+ * forwardedFor: req.ip,
38
+ * referrer: req.headers.referer || null,
39
+ * userAgent: req.headers['user-agent'] || null,
40
+ * clientId: 'abc123'
41
+ * }
42
+ * });
43
+ * ```
31
44
  *
32
45
  * @group Engine
33
46
  */
34
47
  export function defineSearchEngine(options) {
35
48
  const { controllers: controllerDefinitions, ...engineOptions } = options;
36
- const getOptions = () => {
37
- return engineOptions;
38
- };
39
- const setNavigatorContextProvider = (navigatorContextProvider) => {
40
- engineOptions.navigatorContextProvider = navigatorContextProvider;
41
- };
42
- const build = async (...[buildOptions]) => {
43
- const logger = buildLogger(options.loggerOptions);
44
- if (!getOptions().navigatorContextProvider) {
45
- logger.warn('[WARNING] Missing navigator context in server-side code. Make sure to set it with `setNavigatorContextProvider` before calling fetchStaticState()');
46
- }
47
- const engine = buildSSRSearchEngine(buildOptions?.extend
48
- ? await buildOptions.extend(getOptions())
49
- : getOptions());
49
+ const hydrateStaticState = async (...params) => {
50
+ const engine = buildSSRSearchEngine(engineOptions);
50
51
  const controllers = buildControllerDefinitions({
51
52
  definitionsMap: (controllerDefinitions ?? {}),
52
53
  engine,
53
- propsMap: (buildOptions && 'controllers' in buildOptions
54
- ? buildOptions.controllers
55
- : {}),
54
+ propsMap: {},
56
55
  });
57
- return {
58
- engine,
59
- controllers,
60
- };
56
+ const [staticState] = params;
57
+ engine.dispatch(staticState.searchAction);
58
+ await engine.waitForSearchCompletedAction();
59
+ return { engine, controllers };
61
60
  };
62
61
  const fetchStaticState = async (...params) => {
63
- const { engine, controllers } = await build(...params);
64
- options.configuration.preprocessRequest =
65
- augmentPreprocessRequestWithForwardedFor({
66
- preprocessRequest: options.configuration.preprocessRequest,
67
- navigatorContextProvider: options.navigatorContextProvider,
68
- loggerOptions: options.loggerOptions,
69
- });
62
+ const { engineOptions, callOptions } = processNavigatorContext(params, options);
63
+ const engine = buildSSRSearchEngine(engineOptions);
64
+ const controllers = buildControllerDefinitions({
65
+ definitionsMap: (controllerDefinitions ?? {}),
66
+ engine,
67
+ propsMap: (callOptions && 'controllers' in callOptions
68
+ ? callOptions.controllers
69
+ : {}),
70
+ });
70
71
  engine.executeFirstSearch();
71
72
  const staticState = createStaticState({
72
73
  searchAction: await engine.waitForSearchCompletedAction(),
@@ -74,15 +75,8 @@ export function defineSearchEngine(options) {
74
75
  });
75
76
  return staticState;
76
77
  };
77
- const hydrateStaticState = async (...params) => {
78
- const { engine, controllers } = await build(...params);
79
- engine.dispatch(params[0].searchAction);
80
- await engine.waitForSearchCompletedAction();
81
- return { engine, controllers };
82
- };
83
78
  return {
84
79
  fetchStaticState,
85
80
  hydrateStaticState,
86
- setNavigatorContextProvider,
87
81
  };
88
82
  }
@@ -0,0 +1,13 @@
1
+ import { vi } from 'vitest';
2
+ export function buildMockController() {
3
+ return {
4
+ subscribe: vi.fn(),
5
+ state: {},
6
+ };
7
+ }
8
+ export function buildMockControllerWithInitialState(_engine, props) {
9
+ return {
10
+ state: props.initialState,
11
+ subscribe: vi.fn(),
12
+ };
13
+ }
@@ -0,0 +1,43 @@
1
+ import { vi } from 'vitest';
2
+ import { buildMockController, buildMockControllerWithInitialState, } from './mock-controller.js';
3
+ export function defineMockController() {
4
+ return {
5
+ build: vi.fn((_engine) => {
6
+ return buildMockController();
7
+ }),
8
+ };
9
+ }
10
+ export function defineMockControllerWithProps() {
11
+ return {
12
+ buildWithProps: vi.fn((engine, props) => {
13
+ return buildMockControllerWithInitialState(engine, {
14
+ initialState: props?.initialState,
15
+ });
16
+ }),
17
+ };
18
+ }
19
+ export function defineMockCommerceController(options) {
20
+ return {
21
+ build: vi.fn((_engine) => {
22
+ return buildMockController();
23
+ }),
24
+ listing: options?.listing ?? true,
25
+ search: options?.search ?? true,
26
+ standalone: options?.standalone ?? true,
27
+ recommendation: options?.recommendation ?? true,
28
+ };
29
+ }
30
+ export function defineMockCommerceControllerWithProps(options) {
31
+ return {
32
+ buildWithProps: vi.fn((engine, props) => ({
33
+ _kind: 'some-kind',
34
+ ...buildMockControllerWithInitialState(engine, {
35
+ initialState: props?.initialState,
36
+ }),
37
+ })),
38
+ listing: options?.listing ?? true,
39
+ search: options?.search ?? true,
40
+ standalone: options?.standalone ?? true,
41
+ recommendation: options?.recommendation ?? true,
42
+ };
43
+ }
@@ -1,2 +1,2 @@
1
- export const VERSION = "3.28.4-pre.f345a65b79" || 'Test version';
1
+ export const VERSION = "3.28.4-pre.fe2dbcea63" || 'Test version';
2
2
  export const COVEO_FRAMEWORK = ['@coveo/atomic', '@coveo/quantic'];