@coveo/quantic 3.44.2 → 3.44.3

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/README.md CHANGED
@@ -66,6 +66,15 @@ To access the links to the created communities, check the generated `.env` file
66
66
 
67
67
  Or you can run the individual commands below.
68
68
 
69
+ ### Playground Components and Solution Examples
70
+
71
+ Quantic has two separate folders for example components, each serving a different purpose, use the following rules when adding an example component:
72
+
73
+ - **`force-app/playgroundComponents`** contains the components used to build the Quantic Examples community playground and to support Playwright end-to-end tests. Add a component here when it demonstrates an individual Quantic component, exposes its configurable properties for experimentation, or provides test coverage for the component.
74
+ - **`force-app/solutionExamples`** contains real-world reference implementations that demonstrate how to combine Quantic components to solve a broader Salesforce use case. Examples include `exampleInsightPanel`, `exampleRelevantGenerativeAnswering`, `examplePageViewTracker` and more. Add a component here when it demonstrates an integration or reusable solution pattern rather than an individual component playground.
75
+
76
+ Both folders are deployed by `pnpm run deploy:examples`.
77
+
69
78
  #### Create a Default Scratch Org
70
79
 
71
80
  - Make sure you are in the `packages/quantic` root folder.
@@ -130,19 +139,19 @@ pnpm run deploy:examples MyCustomOrg
130
139
  To run Playwright tests, run:
131
140
 
132
141
  ```bash
133
- pnpm run e2e:playwright
142
+ pnpm run e2e:pre-provisioned
134
143
  ```
135
144
 
136
145
  To run Playwright tests only for the scratch org where LWS is enabled, run:
137
146
 
138
147
  ```bash
139
- pnpm run e2e:playwright:lws-enabled
148
+ pnpm run e2e:pre-provisioned:lws-enabled
140
149
  ```
141
150
 
142
151
  To run Playwright tests only for the scratch org where LWS is disabled, run:
143
152
 
144
153
  ```bash
145
- pnpm run e2e:playwright:lws-disabled
154
+ pnpm run e2e:pre-provisioned:lws-disabled
146
155
  ```
147
156
 
148
157
  ### Run LWC unit tests for Quantic Components
@@ -21,7 +21,7 @@ export default class QuanticCitation extends NavigationMixin(LightningElement) {
21
21
  /**
22
22
  * The citation item information.
23
23
  * @api
24
- * @type {{title: string, index: number, text: string, uri: string, clickUri: string, fields: object}}
24
+ * @type {{title: string, index: number, text: string, uri: string, clickUri: string, fields: object, filetype: string}}
25
25
  */
26
26
  @api citation;
27
27
  /**
@@ -78,7 +78,7 @@ export default class QuanticCitation extends NavigationMixin(LightningElement) {
78
78
  hideTimer = null;
79
79
 
80
80
  connectedCallback() {
81
- const fileType = this.citation?.fields?.filetype;
81
+ const fileType = this.citation.filetype ?? this.citation.fields?.filetype;
82
82
  this.isHrefWithTextFragment =
83
83
  !this.disableCitationAnchoring &&
84
84
  supportedFileTypesForTextFragment.includes(fileType) &&
@@ -52,8 +52,8 @@ const headlessBundles = {
52
52
 
53
53
  /**
54
54
  * Loads the Bueno library dependency.
55
- * @param element The Lightning element to use to load dependencies.
56
- * @returns {Promise}
55
+ * @param element The Lightning element used to load the dependency.
56
+ * @returns {Promise<void>} A promise that resolves when Bueno is available.
57
57
  */
58
58
  const getBueno = (element) => {
59
59
  if (window.Bueno) {
@@ -63,11 +63,20 @@ const getBueno = (element) => {
63
63
  };
64
64
 
65
65
  /**
66
- * Initiates dependency loading promises.
67
- * @param element The Lightning element to use to load dependencies.
68
- * @returns {Promise<AnyHeadless>}
66
+ * Loads the dependencies for an engine use case and returns its Headless bundle.
67
+ * @param element The Lightning element used to load dependencies.
68
+ * @param {string} [headlessUseCase=HeadlessBundleNames.search] The Headless use case whose bundle should be loaded.
69
+ * @returns {Promise<AnyHeadless>} A promise that resolves to the loaded Headless bundle.
70
+ * @throws {Error} If a dependency cannot be loaded.
69
71
  */
70
72
  const loadDependencies = async (element, headlessUseCase) => {
73
+ if (
74
+ headlessUseCase &&
75
+ !Object.prototype.hasOwnProperty.call(headlessBundles, headlessUseCase)
76
+ ) {
77
+ throw new Error(`Unsupported Headless use case: ${headlessUseCase}`);
78
+ }
79
+
71
80
  const bundleInfo = headlessUseCase
72
81
  ? headlessBundles[headlessUseCase]
73
82
  : headlessBundles.search;
@@ -83,17 +92,23 @@ const loadDependencies = async (element, headlessUseCase) => {
83
92
  };
84
93
 
85
94
  /**
86
- * Sets callback to execute when all components are registered with the specified engine.
87
- * @param {Function} callback
88
- * @param {string} engineId
95
+ * Registers a callback to run after the Headless engine has been initialized.
96
+ * @param {(engine: AnyEngine) => void} callback The callback to invoke with the initialized engine
97
+ * @throws {Error} If no engine is registered for the specified engine ID.
89
98
  */
90
99
  const setInitializedCallback = (callback, engineId) => {
91
- window.coveoHeadless[engineId].initializedCallback = callback;
100
+ const engine = window.coveoHeadless?.[engineId];
101
+ if (!engine) {
102
+ throw new Error(
103
+ `Engine has not been registered for engine ID: ${engineId}`
104
+ );
105
+ }
106
+ engine.initializedCallback = callback;
92
107
  };
93
108
 
94
109
  /**
95
110
  * Cancels the delayed search query.
96
- * @param {string} engineId The id of the engine.
111
+ * @param {string} engineId The ID of the engine.
97
112
  */
98
113
  const cancelInitializedCallback = (engineId) => {
99
114
  if (debouncers[engineId]) {
@@ -103,20 +118,34 @@ const cancelInitializedCallback = (engineId) => {
103
118
  };
104
119
 
105
120
  /**
106
- * Dispatches search request.
107
- * @param {string} engineId The id of the engine.
121
+ * Executes the initialization callback with the engine once the debounce delay expires.
122
+ * @param {string} engineId The ID of the engine.
123
+ * @returns {Promise<void>}
108
124
  */
109
125
  const executeInitializedCallback = async (engineId) => {
110
- if (window.coveoHeadless[engineId]) {
111
- window.coveoHeadless[engineId].initializedCallback(
112
- await window.coveoHeadless[engineId].enginePromise
126
+ const engine = window.coveoHeadless?.[engineId];
127
+ if (!engine?.initializedCallback) {
128
+ return;
129
+ }
130
+
131
+ try {
132
+ await engine.initializedCallback(await engine.enginePromise);
133
+ } catch (error) {
134
+ console.error(
135
+ `Fatal error: unable to execute the initialization callback for engine ID: ${engineId}`,
136
+ error
137
+ );
138
+ throw new Error(
139
+ `Fatal error: unable to execute the initialization callback for engine ID: ${engineId}`,
140
+ // @ts-ignore Error constructor cause options are supported at runtime.
141
+ {cause: error}
113
142
  );
114
143
  }
115
144
  };
116
145
 
117
146
  /**
118
- * Starts the debounced initial search request.
119
- * @param {string} engineId The id of the engine.
147
+ * Schedules the engine's initialization callback after a short debounce period.
148
+ * @param {string} engineId The ID of the engine.
120
149
  */
121
150
  const debounceInitializedCallback = (engineId) => {
122
151
  if (!debouncers[engineId]) {
@@ -129,28 +158,30 @@ const debounceInitializedCallback = (engineId) => {
129
158
  };
130
159
 
131
160
  /**
132
- * Returns true if registered components are initialized, false otherwise.
133
- * @param {string} engineId The id of the engine.
161
+ * Checks whether every component registered for an engine has initialized.
162
+ * @param {string} engineId The ID of the engine.
163
+ * @returns {boolean}
134
164
  */
135
165
  const areAllComponentsInitialized = (engineId) =>
136
- !window.coveoHeadless[engineId].components.find(
137
- (component) => component.initialized === false
138
- );
166
+ window.coveoHeadless?.[engineId]?.components?.every(
167
+ (component) => component.initialized === true
168
+ ) ?? false;
139
169
 
140
170
  /**
141
- * Returns the registered component object if it exists.
142
- * @param element The Lightning Element component with which to load dependencies.
143
- * @param {string} engineId The id of the engine.
171
+ * Finds a component registered for an engine.
172
+ * @param element The component to find.
173
+ * @param {string} engineId The ID of the engine.
174
+ * @returns {Object | undefined} The registered component, if found.
144
175
  */
145
176
  const getRegisteredComponent = (element, engineId) =>
146
- window.coveoHeadless[engineId].components.find(
177
+ window.coveoHeadless?.[engineId]?.components?.find(
147
178
  (component) => component.element === element
148
179
  );
149
180
 
150
181
  /**
151
182
  * Instantiates the coveoHeadless window object and the engine attribute for the provided ID.
152
- * @param element The Lightning element to use to load dependencies.
153
- * @param {string} engineId The id of the engine.
183
+ * @param element The Lightning element associated with the engine.
184
+ * @param {string} engineId The ID of the engine.
154
185
  */
155
186
  const instantiateWindowEngineObject = (element, engineId) => {
156
187
  const newWindowEngineObject = {
@@ -170,50 +201,67 @@ const instantiateWindowEngineObject = (element, engineId) => {
170
201
  };
171
202
 
172
203
  /**
173
- * Loads dependencies and returns an initialized Headless engine.
174
- * @param {string} engineId The id of the engine.
204
+ * Creates and returns the Headless engine for an engine ID.
205
+ * @param {string} engineId The ID of the engine.
206
+ * @returns {Promise<AnyHeadless>} A promise that resolves to the initialized Headless engine.
207
+ * @throws {Error} If the engine state or options is invalid, or engine construction fails.
175
208
  */
176
209
  async function initEngine(engineId) {
177
210
  try {
178
- if (window.coveoHeadless[engineId].bindings.engine) {
211
+ const engineState = window.coveoHeadless?.[engineId];
212
+ if (!engineState) {
213
+ throw new Error(
214
+ `Engine has not been registered for engine ID: ${engineId}`
215
+ );
216
+ }
217
+ if (engineState.bindings.engine) {
179
218
  throw new Error(`Engine already instantiated for engine ID: ${engineId}`);
180
219
  }
181
- if (!window.coveoHeadless[engineId].options) {
220
+ if (!engineState.options) {
182
221
  throw new Error('Engine options have not been set.');
183
222
  }
184
223
 
185
- const options = await window.coveoHeadless[engineId].options.promise;
186
- return window.coveoHeadless[engineId].engineConstructor(options);
224
+ const options = await engineState.options.promise;
225
+ return engineState.engineConstructor(options);
187
226
  } catch (error) {
188
- throw new Error(
189
- 'Fatal error: unable to initialize Coveo Headless: ' + error
190
- );
227
+ // @ts-ignore Error constructor cause options are supported at runtime.
228
+ throw new Error('Fatal error: unable to initialize Coveo Headless', {
229
+ cause: error,
230
+ });
191
231
  }
192
232
  }
193
233
 
194
234
  /**
195
- * Initialize coveoHeadless Store object
235
+ * Initializes the Quantic store for an engine when one is not already available.
196
236
  * @param {string} engineId The ID of the engine.
237
+ * @throws {Error} If the engine state cannot be accessed or the store cannot be initialized.
197
238
  */
198
239
  const initQuanticStore = (engineId) => {
199
240
  try {
200
- if (!window.coveoHeadless[engineId].bindings.store) {
201
- window.coveoHeadless[engineId].bindings.store = Store.initialize();
241
+ const engineState = window.coveoHeadless?.[engineId];
242
+ if (!engineState) {
243
+ throw new Error(
244
+ `Engine has not been registered for engine ID: ${engineId}`
245
+ );
246
+ }
247
+ if (!engineState.bindings.store) {
248
+ engineState.bindings.store = Store.initialize();
202
249
  }
203
250
  } catch (error) {
204
- throw new Error(
205
- 'Fatal error: unable to initialize Quantic store: ' + error
206
- );
251
+ // @ts-ignore Error constructor cause options are supported at runtime.
252
+ throw new Error('Fatal error: unable to initialize Quantic store', {
253
+ cause: error,
254
+ });
207
255
  }
208
256
  };
209
257
 
210
258
  /**
211
259
  * Sets the options passed to engine constructor for given engine ID.
212
260
  * @param options The Headless options for the specified engine ID.
213
- * @param {(options: unknown) => unknown} engineConstructor Th engine constructor.
214
- * @param {string} engineId The id of the engine.
261
+ * @param {(options: unknown) => unknown} engineConstructor The Headless engine constructor.
262
+ * @param {string} engineId The ID of the engine.
215
263
  * @param element The Lightning element to use to load dependencies.
216
- * @param headlessBundle The headless bundle associated to the engine.
264
+ * @param headlessBundle The Headless bundle associated with the engine.
217
265
  */
218
266
  function setEngineOptions(
219
267
  options,
@@ -239,7 +287,7 @@ function setEngineOptions(
239
287
  /**
240
288
  * Registers a component for future initialization.
241
289
  * @param element The Lightning element to use to load dependencies.
242
- * @param {string} engineId The id of the engine.
290
+ * @param {string} engineId The ID of the engine.
243
291
  */
244
292
  function registerComponentForInit(element, engineId) {
245
293
  cancelInitializedCallback(engineId);
@@ -257,7 +305,8 @@ function registerComponentForInit(element, engineId) {
257
305
  /**
258
306
  * Sets registered component to initialized.
259
307
  * @param element The Lightning element to use to load dependencies.
260
- * @param {string} engineId The id of the engine.
308
+ * @param {string} engineId The ID of the engine.
309
+ * @throws {Error} If the component was not registered for the engine.
261
310
  */
262
311
  function setComponentInitialized(element, engineId) {
263
312
  const component = window.coveoHeadless?.[engineId]
@@ -280,43 +329,62 @@ function setComponentInitialized(element, engineId) {
280
329
 
281
330
  /**
282
331
  * Returns headless engine promise.
283
- * @param {string} engineId The id of the engine.
332
+ * @param {string} engineId The ID of the engine.
333
+ * @returns {Promise<AnyHeadless>} A promise that resolves to the Headless engine.
334
+ * @throws {Error} If engine initialization fails.
284
335
  */
285
336
  function getHeadlessEnginePromise(engineId) {
286
- if (!window.coveoHeadless[engineId].enginePromise) {
287
- window.coveoHeadless[engineId].enginePromise = initEngine(engineId).then(
288
- (engine) => {
289
- window.coveoHeadless[engineId].bindings.engine = engine;
290
- return engine;
291
- }
337
+ const engineState = window.coveoHeadless?.[engineId];
338
+ if (!engineState) {
339
+ throw new Error(
340
+ `Engine has not been registered for engine ID: ${engineId}`
292
341
  );
293
342
  }
294
- return window.coveoHeadless[engineId].enginePromise;
343
+ if (!engineState.enginePromise) {
344
+ engineState.enginePromise = initEngine(engineId).then((engine) => {
345
+ engineState.bindings.engine = engine;
346
+ return engine;
347
+ });
348
+ }
349
+ return engineState.enginePromise;
295
350
  }
296
351
 
297
352
  /**
298
353
  * Returns bindings object for specified engineId.
299
- * @param {string} engineId The id of the engine.
354
+ * @param {string} engineId The ID of the engine.
355
+ * @returns {Object | undefined} The engine bindings, if registered.
300
356
  */
301
357
  function getHeadlessBindings(engineId) {
302
358
  return window.coveoHeadless?.[engineId]?.bindings;
303
359
  }
304
360
 
305
361
  /**
306
- * Returns store object for specified engineId.
307
- * @param {string} engineId The engine ID.
362
+ * Returns the Quantic store associated with an engine.
363
+ * @param {string} engineId The ID of the engine.
364
+ * @returns {Object | undefined} The store, if initialized.
308
365
  */
309
366
  function getQuanticStore(engineId) {
310
367
  return window.coveoHeadless?.[engineId]?.bindings?.store;
311
368
  }
369
+
312
370
  /**
313
371
  * Initializes a component with Coveo Headless.
314
- * @param element The LightningElement component to initialize.
315
- * @param {string} engineId The id of the engine.
316
- * @param {Function} initialize The component's initialization function.
372
+ * Failures while initializing the Quantic store, Headless engine, or component
373
+ * callback are logged and reported through the component error UI.
374
+ * @param element The component to initialize.
375
+ * @param {string} engineId The ID of the engine.
376
+ * @param {(engine: AnyEngine) => void} initialize The component initialization callback function.
377
+ * @returns {Promise<void>} A promise that resolves after initialization handling completes.
378
+ * @throws {Error} If the component was not registered before initialization.
317
379
  */
318
380
  async function initializeWithHeadless(element, engineId, initialize) {
319
- if (getRegisteredComponent(element, engineId).initialized) {
381
+ const component = getRegisteredComponent(element, engineId);
382
+ if (!component) {
383
+ throw new Error(
384
+ 'Fatal Error: Component was not registered before initialization'
385
+ );
386
+ }
387
+ if (component.initialized) {
320
388
  return;
321
389
  }
322
390
  try {
@@ -345,8 +413,8 @@ async function initializeWithHeadless(element, engineId, initialize) {
345
413
  }
346
414
 
347
415
  /**
348
- * Removed the headless engine instance from the window object.
349
- * @param {string} engineId
416
+ * Removes the headless engine instance from the window object.
417
+ * @param {string} engineId The ID of the engine to remove.
350
418
  */
351
419
  function destroyEngine(engineId) {
352
420
  if (window.coveoHeadless?.[engineId]) {
@@ -355,10 +423,10 @@ function destroyEngine(engineId) {
355
423
  }
356
424
 
357
425
  /**
358
- * Register a facet in the store.
359
- * @param {string} engineId The engine ID.
360
- * @param {string} facetType
361
- * @param {{ label: string; facetId: string; format?: function, element?: HTMLElement, metadata?: object}} data
426
+ * Registers facet data in the engine's store.
427
+ * @param {string} engineId The ID of the engine.
428
+ * @param {string} facetType The store facet collection to update.
429
+ * @param {{label: string, facetId: string, format?: Function, element?: HTMLElement, metadata?: object}} data The facet data to register.
362
430
  */
363
431
  function registerToStore(engineId, facetType, data) {
364
432
  const store = getQuanticStore(engineId);
@@ -370,9 +438,9 @@ function registerToStore(engineId, facetType, data) {
370
438
  }
371
439
 
372
440
  /**
373
- * Register the sort options in the store.
374
- * @param {string} engineId The engine ID.
375
- * @param {Array<{label: string; value: string; criterion: SortCriterion;}>} data
441
+ * Registers sort options in the engine's store.
442
+ * @param {string} engineId The ID of the engine.
443
+ * @param {Array<{label: string, value: string, criterion: SortCriterion}>} data The sort options to register.
376
444
  */
377
445
  function registerSortOptionsToStore(engineId, data) {
378
446
  const store = getQuanticStore(engineId);
@@ -384,9 +452,10 @@ function registerSortOptionsToStore(engineId, data) {
384
452
  }
385
453
 
386
454
  /**
387
- * Get facet data from store.
388
- * @param {string} engineId The engine ID.
389
- * @param {string} facetType
455
+ * Gets facet data from the engine's store.
456
+ * @param {string} engineId The ID of the engine.
457
+ * @param {string} facetType The store facet collection to read.
458
+ * @returns {Object | undefined} The facet data, if available.
390
459
  */
391
460
  function getFromStore(engineId, facetType) {
392
461
  const store = getQuanticStore(engineId);
@@ -399,8 +468,9 @@ function getFromStore(engineId, facetType) {
399
468
  }
400
469
 
401
470
  /**
402
- * Get all facet data from store.
403
- * @param {string} engineId The engine ID.
471
+ * Gets all facet data from the engine's store.
472
+ * @param {string} engineId The ID of the engine.
473
+ * @returns {Object} All available facet data grouped by facet type.
404
474
  */
405
475
  function getAllFacetsFromStore(engineId) {
406
476
  return Object.values(Store.facetTypes).reduce(
@@ -413,8 +483,9 @@ function getAllFacetsFromStore(engineId) {
413
483
  }
414
484
 
415
485
  /**
416
- * Gets all sort options data from store.
417
- * @param {string} engineId The engine ID.
486
+ * Gets all sort option data from the engine's store.
487
+ * @param {string} engineId The ID of the engine.
488
+ * @returns {Array<{label: string, value: string, criterion: SortCriterion}> | undefined} The sort options, if available.
418
489
  */
419
490
  function getAllSortOptionsFromStore(engineId) {
420
491
  const store = getQuanticStore(engineId);
@@ -427,19 +498,19 @@ function getAllSortOptionsFromStore(engineId) {
427
498
  }
428
499
 
429
500
  /**
430
- * Get the headless bundle associated to the specified engine.
431
- * @param {string} engineId The engine ID.
432
- * @returns The headless bundle associated to the specified engine.
501
+ * Gets the Headless bundle associated with an engine.
502
+ * @param {string} engineId The ID of the engine.
503
+ * @returns {AnyHeadless} The configured Headless bundle, or the default search bundle.
433
504
  */
434
505
  function getHeadlessBundle(engineId) {
435
- return window.coveoHeadless[engineId]?.bundle ?? CoveoHeadless;
506
+ return window.coveoHeadless?.[engineId]?.bundle ?? CoveoHeadless;
436
507
  }
437
508
 
438
509
  /**
439
- * Gets whether the specified engine is using the expected bundle.
440
- * @param {string} engineId - The engine ID.
441
- * @param {string} expectedBundleName - The expected headless bundle name.
442
- * @returns
510
+ * Checks whether an engine uses the expected Headless bundle.
511
+ * @param {string} engineId The ID of the engine.
512
+ * @param {string} expectedBundleName The name of the expected Headless bundle.
513
+ * @returns {boolean} Whether the engine uses the expected bundle.
443
514
  */
444
515
  function isHeadlessBundle(engineId, expectedBundleName) {
445
516
  let expectedBundle;