@askdialog/dialog-sdk 2.3.0 → 2.4.0

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 (47) hide show
  1. package/README.md +63 -5
  2. package/dist/Dialog.d.ts +14 -16
  3. package/dist/Dialog.d.ts.map +1 -1
  4. package/dist/Dialog.js +29 -23
  5. package/dist/DialogSearchError.d.ts +19 -0
  6. package/dist/DialogSearchError.d.ts.map +1 -0
  7. package/dist/DialogSearchError.js +17 -0
  8. package/dist/__tests__/dialogAddToCart.spec.d.ts +2 -0
  9. package/dist/__tests__/dialogAddToCart.spec.d.ts.map +1 -0
  10. package/dist/__tests__/dialogAddToCart.spec.js +59 -0
  11. package/dist/__tests__/dialogCallbacks.spec.d.ts +2 -0
  12. package/dist/__tests__/dialogCallbacks.spec.d.ts.map +1 -0
  13. package/dist/__tests__/dialogCallbacks.spec.js +40 -0
  14. package/dist/__tests__/publicTypes.spec.d.ts +2 -0
  15. package/dist/__tests__/publicTypes.spec.d.ts.map +1 -0
  16. package/dist/__tests__/publicTypes.spec.js +21 -0
  17. package/dist/__tests__/searchService.spec.d.ts +2 -0
  18. package/dist/__tests__/searchService.spec.d.ts.map +1 -0
  19. package/dist/__tests__/searchService.spec.js +111 -0
  20. package/dist/config/config.development.d.ts +1 -0
  21. package/dist/config/config.development.d.ts.map +1 -1
  22. package/dist/config/config.development.js +2 -0
  23. package/dist/config/config.production.d.ts +1 -0
  24. package/dist/config/config.production.d.ts.map +1 -1
  25. package/dist/config/config.production.js +2 -0
  26. package/dist/config/index.d.ts +1 -0
  27. package/dist/config/index.d.ts.map +1 -1
  28. package/dist/config/index.js +2 -0
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +1 -0
  32. package/dist/services/search.d.ts +7 -0
  33. package/dist/services/search.d.ts.map +1 -0
  34. package/dist/services/search.js +37 -0
  35. package/dist/types/assistantEvent.d.ts +8 -0
  36. package/dist/types/assistantEvent.d.ts.map +1 -1
  37. package/dist/types/constructor.d.ts +11 -10
  38. package/dist/types/constructor.d.ts.map +1 -1
  39. package/dist/types/events.d.ts +15 -1
  40. package/dist/types/events.d.ts.map +1 -1
  41. package/dist/types/index.d.ts +1 -0
  42. package/dist/types/index.d.ts.map +1 -1
  43. package/dist/types/index.js +1 -0
  44. package/dist/types/search.d.ts +50 -0
  45. package/dist/types/search.d.ts.map +1 -0
  46. package/dist/types/search.js +4 -0
  47. package/package.json +5 -1
package/README.md CHANGED
@@ -21,11 +21,11 @@ Before using the Dialog SDK, you need:
21
21
  ### Installation
22
22
 
23
23
  ```bash
24
- npm install @dialog/dialog-sdk
24
+ npm install @askdialog/dialog-sdk
25
25
  # or
26
- pnpm add @dialog/dialog-sdk
26
+ pnpm add @askdialog/dialog-sdk
27
27
  # or
28
- yarn add @dialog/dialog-sdk
28
+ yarn add @askdialog/dialog-sdk
29
29
  ```
30
30
 
31
31
  You can also use our CDN link if you’re not using a package manager.
@@ -47,7 +47,7 @@ const client = new window.DialogSDK.Dialog({
47
47
  ### Instantiate the client
48
48
 
49
49
  ```typescript
50
- import { Dialog } from '@dialog/dialog-sdk';
50
+ import { Dialog } from '@askdialog/dialog-sdk';
51
51
 
52
52
  const client = new Dialog({
53
53
  apiKey: 'YOUR_API_KEY', // required
@@ -87,7 +87,9 @@ The optional `countryCode` (ISO 3166 alpha-2, e.g. `'FR'`, `'US'`) sets the regi
87
87
  The addToCart function is triggered when a user clicks the AddToCart button.
88
88
  The getProduct function is used to display product information in the assistant.
89
89
 
90
- When the client is instantiated, it will automatically insert into the DOM the Dialog Assistant script, so you can interact with the assistant using `sendProductMessage` or `sendGenericMessage`.
90
+ `callbacks` is **optional**: a search-only integration can construct the client with just `apiKey` and `locale`. Only `getProduct()` and `addToCart()` require their callback they throw an explicit configuration error when it is absent; every other feature works without callbacks.
91
+
92
+ When the client is instantiated, it will automatically insert into the DOM the Dialog Assistant script, so you can interact with the assistant using `sendProductMessage` or `sendGenericMessage`. This assistant runtime always loads — it owns the shopper identity, consent handling and analytics bridge — while the heavy assistant UI stays lazy-loaded and is not fetched eagerly.
91
93
 
92
94
  ### Getters
93
95
 
@@ -163,6 +165,62 @@ Example of expected result:
163
165
  ```
164
166
 
165
167
 
168
+ - Search products
169
+
170
+ `client.search()` performs a typed product search through Dialog's public API, with no framework and no commerce callbacks required.
171
+
172
+ ```typescript
173
+ import { Dialog, DialogSearchError } from '@askdialog/dialog-sdk';
174
+ import type { SearchResponse } from '@askdialog/dialog-sdk';
175
+
176
+ const client = new Dialog({ apiKey: 'YOUR_API_KEY', locale: 'fr' });
177
+
178
+ const response: SearchResponse = await client.search({
179
+ query: 'shampoo',
180
+ page: 0, // optional, zero-indexed (default 0)
181
+ hitsPerPage: 20, // optional, 1-100 (default 20)
182
+ });
183
+ // response.hits[n].product: { id, title?, url?, imageUrl?, priceRange?, inStock? }
184
+ ```
185
+
186
+ With the IIFE bundle the results are plain runtime JSON (same shape, no types):
187
+
188
+ ```html
189
+ <script src="https://d2m6yt8rnm4dos.cloudfront.net/dialog-sdk.X.Y.Z.min.js"></script>
190
+ <script>
191
+ const client = new window.DialogSDK.Dialog({ apiKey: 'YOUR_API_KEY', locale: 'fr' });
192
+ client.search({ query: 'shampoo' }).then((response) => console.log(response.hits));
193
+ </script>
194
+ ```
195
+
196
+ A non-2xx answer rejects with `DialogSearchError` — stable `name`, HTTP `status`, optional machine-readable `code` (e.g. `SEARCH_INDEX_NOT_FOUND`) and `message`. Aborting rejects with the native `AbortError`, and network failures keep their native errors.
197
+
198
+ The SDK is stateless: no debounce, no cache, no automatic cancellation of previous searches. For search-as-you-type, own those in the integration:
199
+
200
+ ```typescript
201
+ let controller: AbortController | undefined;
202
+ let latestRequestId = 0;
203
+ let debounceTimer: number | undefined;
204
+
205
+ const onInput = (query: string) => {
206
+ window.clearTimeout(debounceTimer);
207
+ debounceTimer = window.setTimeout(async () => {
208
+ controller?.abort(); // cancel the in-flight request
209
+ controller = new AbortController();
210
+ const requestId = ++latestRequestId;
211
+ try {
212
+ const response = await client.search({ query }, { signal: controller.signal });
213
+ if (requestId !== latestRequestId) return; // stale response, ignore
214
+ render(response.hits);
215
+ } catch (error) {
216
+ if (error instanceof DOMException && error.name === 'AbortError') return;
217
+ if (error instanceof DialogSearchError) renderError(error.message);
218
+ else throw error;
219
+ }
220
+ }, 200);
221
+ };
222
+ ```
223
+
166
224
  - Handler for fetch product
167
225
 
168
226
  The `getProduct` callback is called by the assistant to display product information. You must return an object matching the `SimplifiedProduct` interface.
package/dist/Dialog.d.ts CHANGED
@@ -2,16 +2,17 @@ import { DialogConstructor } from "./types/constructor";
2
2
  import { Theme } from "./types/theme";
3
3
  import { DetailedLocaleInfo } from "./utils/localization";
4
4
  import { Suggestion } from "./types/suggestion";
5
- import { GenericQuestionPayload, LegacyCheckoutParams, OpenAssistantPayload, ProductQuestionPayload, SubmitCheckoutParams } from "./types/events";
5
+ import { AddToCartInput, GenericQuestionPayload, LegacyCheckoutParams, OpenAssistantPayload, ProductQuestionPayload, SubmitCheckoutParams } from "./types/events";
6
6
  import { SimplifiedProduct } from "./types/product";
7
7
  import { EventsHandler } from "./EventsHandler";
8
+ import { SearchOptions, SearchRequest, SearchResponse } from "./types/search";
8
9
  import { AssistantEvent } from "./types/assistantEvent";
9
10
  export declare class Dialog {
10
11
  static readonly VERSION: string;
11
12
  private _apiKey;
12
13
  private _locale;
13
14
  private _countryCode?;
14
- private _callbacks;
15
+ private _callbacks?;
15
16
  private _theme;
16
17
  private _userId;
17
18
  private _eventsHandler;
@@ -24,6 +25,14 @@ export declare class Dialog {
24
25
  getLocalizationInformations(): DetailedLocaleInfo | null;
25
26
  private _createOrRetrieveUserId;
26
27
  getSuggestions(productId: string): Promise<Suggestion>;
28
+ /**
29
+ * Typed product search through the Nest public endpoint. Stateless: one
30
+ * request per call, cancellation belongs to the caller via
31
+ * `options.signal` (previous searches are never cancelled automatically).
32
+ * Rejects with `DialogSearchError` on a non-2xx answer, and with the
33
+ * native AbortError / network error otherwise.
34
+ */
35
+ search(request: SearchRequest, options?: SearchOptions): Promise<SearchResponse>;
27
36
  openAssistant(params: OpenAssistantPayload): void;
28
37
  closeAssistant(): void;
29
38
  sendProductMessage(params: ProductQuestionPayload): void;
@@ -31,20 +40,9 @@ export declare class Dialog {
31
40
  onAssistantEvent(listener: (event: AssistantEvent) => void): void;
32
41
  dispatchAssistantEvent(event: AssistantEvent): void;
33
42
  getProduct(productId: string, variantId?: string): Promise<SimplifiedProduct>;
34
- addToCart({ productId, quantity, currency, variantId, price, }: {
35
- productId: string;
36
- quantity: number;
37
- price?: string;
38
- currency?: string;
39
- variantId?: string;
40
- }): Promise<void>;
41
- registerAddToCartEvent({ productId, quantity, currency, variantId, price, }: {
42
- productId: string;
43
- quantity: number;
44
- price?: string;
45
- currency?: string;
46
- variantId?: string;
47
- }): void;
43
+ addToCart(input: AddToCartInput): Promise<void>;
44
+ private _getCallbacksOrThrow;
45
+ registerAddToCartEvent(input: AddToCartInput): void;
48
46
  registerSubmitCheckoutEvent(params: SubmitCheckoutParams | LegacyCheckoutParams): void;
49
47
  private _loadAssistant;
50
48
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Dialog.d.ts","sourceRoot":"","sources":["../src/Dialog.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EACL,kBAAkB,EAEnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAEL,sBAAsB,EACtB,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,EACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,qBAAa,MAAM;IACjB,gBAAuB,OAAO,SAAuB;IAErD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAC,CAAS;IAE9B,OAAO,CAAC,UAAU,CAGhB;IACF,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,cAAc,CAAgB;gBAE1B,EACV,MAAM,EACN,MAAM,EACN,WAAW,EACX,SAAS,EACT,KAAK,EACL,MAAM,GACP,EAAE,iBAAiB;IAYpB,IAAW,MAAM,IAAI,MAAM,CAE1B;IACD,IAAW,KAAK,IAAI,KAAK,CAExB;IACD,IAAW,MAAM,IAAI,MAAM,CAE1B;IACD,IAAW,MAAM,IAAI,MAAM,CAE1B;IACD,IAAW,aAAa,IAAI,aAAa,CAExC;IAEM,2BAA2B,IAAI,kBAAkB,GAAG,IAAI;IAI/D,OAAO,CAAC,uBAAuB;IAkBlB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAK5D,aAAa,CAAC,MAAM,EAAE,oBAAoB,GAAG,IAAI;IAKjD,cAAc,IAAI,IAAI;IAItB,kBAAkB,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI;IAIxD,kBAAkB,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI;IAOxD,gBAAgB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,IAAI;IAIjE,sBAAsB,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAInD,UAAU,CACf,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,iBAAiB,CAAC;IAIhB,SAAS,CAAC,EACrB,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,KAAK,GACN,EAAE;QACD,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBV,sBAAsB,CAAC,EAC5B,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,KAAK,GACN,EAAE;QACD,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,IAAI;IAkBD,2BAA2B,CAChC,MAAM,EAAE,oBAAoB,GAAG,oBAAoB,GAClD,IAAI;IA0BP,OAAO,CAAC,cAAc;CA4BvB"}
1
+ {"version":3,"file":"Dialog.d.ts","sourceRoot":"","sources":["../src/Dialog.ts"],"names":[],"mappings":"AAIA,OAAO,EAAmB,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACzE,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EACL,kBAAkB,EAEnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EACL,cAAc,EAEd,sBAAsB,EACtB,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,EACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAE9E,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,qBAAa,MAAM;IACjB,gBAAuB,OAAO,SAAuB;IAErD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAC,CAAS;IAE9B,OAAO,CAAC,UAAU,CAAC,CAAkB;IACrC,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,cAAc,CAAgB;gBAE1B,EACV,MAAM,EACN,MAAM,EACN,WAAW,EACX,SAAS,EACT,KAAK,EACL,MAAM,GACP,EAAE,iBAAiB;IAYpB,IAAW,MAAM,IAAI,MAAM,CAE1B;IACD,IAAW,KAAK,IAAI,KAAK,CAExB;IACD,IAAW,MAAM,IAAI,MAAM,CAE1B;IACD,IAAW,MAAM,IAAI,MAAM,CAE1B;IACD,IAAW,aAAa,IAAI,aAAa,CAExC;IAEM,2BAA2B,IAAI,kBAAkB,GAAG,IAAI;IAI/D,OAAO,CAAC,uBAAuB;IAkBlB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAInE;;;;;;OAMG;IACI,MAAM,CACX,OAAO,EAAE,aAAa,EACtB,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,cAAc,CAAC;IAKnB,aAAa,CAAC,MAAM,EAAE,oBAAoB,GAAG,IAAI;IAKjD,cAAc,IAAI,IAAI;IAItB,kBAAkB,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI;IAIxD,kBAAkB,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI;IAOxD,gBAAgB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,IAAI;IAIjE,sBAAsB,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAInD,UAAU,CACf,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,iBAAiB,CAAC;IAUhB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAS5D,OAAO,CAAC,oBAAoB;IAUrB,sBAAsB,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAcnD,2BAA2B,CAChC,MAAM,EAAE,oBAAoB,GAAG,oBAAoB,GAClD,IAAI;IA0BP,OAAO,CAAC,cAAc;CA4BvB"}
package/dist/Dialog.js CHANGED
@@ -7,6 +7,7 @@ import { ANONYMOUS_CUSTOMER_ID, CUSTOMER_ID } from "./constants/user";
7
7
  import { DialogEvents, } from "./types/events";
8
8
  import { EventsHandler } from "./EventsHandler";
9
9
  import { loadSuggestions } from "./services/suggestions";
10
+ import { searchProducts } from "./services/search";
10
11
  import { config } from "./config";
11
12
  import { exposeSdkOnWindowDialog } from "./windowAudit";
12
13
  export class Dialog {
@@ -63,6 +64,16 @@ export class Dialog {
63
64
  async getSuggestions(productId) {
64
65
  return loadSuggestions(this._apiKey, this._locale, productId);
65
66
  }
67
+ /**
68
+ * Typed product search through the Nest public endpoint. Stateless: one
69
+ * request per call, cancellation belongs to the caller via
70
+ * `options.signal` (previous searches are never cancelled automatically).
71
+ * Rejects with `DialogSearchError` on a non-2xx answer, and with the
72
+ * native AbortError / network error otherwise.
73
+ */
74
+ search(request, options) {
75
+ return searchProducts(this._apiKey, request, options);
76
+ }
66
77
  // TODO: Not yet implemented on assistant
67
78
  openAssistant(params) {
68
79
  this._eventsHandler.emitExternalEvent(DialogEvents.OPEN_ASSISTANT, params);
@@ -84,33 +95,28 @@ export class Dialog {
84
95
  this._eventsHandler.emitAssistantEvent(event.type, event.payload);
85
96
  }
86
97
  getProduct(productId, variantId) {
87
- return this._callbacks.getProduct(productId, variantId);
88
- }
89
- async addToCart({ productId, quantity, currency, variantId, price, }) {
90
- await this._callbacks.addToCart({
91
- productId,
92
- variantId,
93
- quantity,
94
- currency,
95
- price,
96
- });
97
- this.registerAddToCartEvent({
98
- productId,
99
- variantId,
100
- quantity,
101
- currency,
102
- price,
103
- });
98
+ return this._getCallbacksOrThrow("getProduct").getProduct(productId, variantId);
99
+ }
100
+ // The full input (including the optional enriched product fields) is
101
+ // forwarded to the merchant callback and to the tracking event, so
102
+ // integrations can consume the added-product data wherever they hook in.
103
+ async addToCart(input) {
104
+ await this._getCallbacksOrThrow("addToCart").addToCart(input);
105
+ this.registerAddToCartEvent(input);
104
106
  return;
105
107
  }
106
- registerAddToCartEvent({ productId, quantity, currency, variantId, price, }) {
108
+ // Callbacks are optional at construction; the two commerce methods assert
109
+ // theirs at call time with an integration-facing configuration error.
110
+ _getCallbacksOrThrow(name) {
111
+ if (this._callbacks?.[name] === undefined) {
112
+ throw new Error(`Dialog: \`callbacks.${name}\` was not provided to the constructor; ${name}() is unavailable on this instance.`);
113
+ }
114
+ return this._callbacks;
115
+ }
116
+ registerAddToCartEvent(input) {
107
117
  this._eventsHandler.emitExternalEvent(DialogEvents.TRACK_ADD_TO_CART, {
108
118
  userId: this._userId,
109
- productId,
110
- variantId,
111
- quantity,
112
- price,
113
- currency,
119
+ ...input,
114
120
  });
115
121
  }
116
122
  // Order-level: call ONCE per completed order with the order total.
@@ -0,0 +1,19 @@
1
+ interface DialogSearchErrorParams {
2
+ status: number;
3
+ code?: string;
4
+ message: string;
5
+ }
6
+ /**
7
+ * Non-2xx answer from the search endpoint. `status` is the HTTP status,
8
+ * `code` the stable machine-readable error code the API carries when the
9
+ * body is JSON (e.g. "SEARCH_INDEX_NOT_FOUND"), `message` the API message
10
+ * or the HTTP statusText when the body is not JSON. Aborts and network
11
+ * failures are NOT wrapped: they reject with their native errors.
12
+ */
13
+ export declare class DialogSearchError extends Error {
14
+ readonly status: number;
15
+ readonly code?: string;
16
+ constructor({ status, code, message }: DialogSearchErrorParams);
17
+ }
18
+ export {};
19
+ //# sourceMappingURL=DialogSearchError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DialogSearchError.d.ts","sourceRoot":"","sources":["../src/DialogSearchError.ts"],"names":[],"mappings":"AAAA,UAAU,uBAAuB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,SAAgB,IAAI,CAAC,EAAE,MAAM,CAAC;gBAElB,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,uBAAuB;CAM/D"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Non-2xx answer from the search endpoint. `status` is the HTTP status,
3
+ * `code` the stable machine-readable error code the API carries when the
4
+ * body is JSON (e.g. "SEARCH_INDEX_NOT_FOUND"), `message` the API message
5
+ * or the HTTP statusText when the body is not JSON. Aborts and network
6
+ * failures are NOT wrapped: they reject with their native errors.
7
+ */
8
+ export class DialogSearchError extends Error {
9
+ status;
10
+ code;
11
+ constructor({ status, code, message }) {
12
+ super(message);
13
+ this.name = "DialogSearchError";
14
+ this.status = status;
15
+ this.code = code;
16
+ }
17
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dialogAddToCart.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialogAddToCart.spec.d.ts","sourceRoot":"","sources":["../../src/__tests__/dialogAddToCart.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,59 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import { Dialog } from "../Dialog";
3
+ import { EventsHandler } from "../EventsHandler";
4
+ import { DialogEvents } from "../types/events";
5
+ // The full Dialog constructor loads the assistant into the DOM; these tests
6
+ // only exercise the add-to-cart pass-through, so the instance is built from
7
+ // the prototype with just the fields addToCart/registerAddToCartEvent read.
8
+ const buildDialog = (addToCartCallback) => {
9
+ const eventsHandler = new EventsHandler("fr", "user-1");
10
+ const emitExternalEvent = vi.fn();
11
+ eventsHandler.emitExternalEvent =
12
+ emitExternalEvent;
13
+ const dialog = Object.create(Dialog.prototype);
14
+ Object.assign(dialog, {
15
+ _callbacks: { addToCart: addToCartCallback, getProduct: vi.fn() },
16
+ _eventsHandler: eventsHandler,
17
+ _userId: "user-1",
18
+ });
19
+ return { dialog, emitExternalEvent };
20
+ };
21
+ const enrichedInput = {
22
+ productId: "gid://shopify/Product/42",
23
+ variantId: "gid://shopify/ProductVariant/4242",
24
+ quantity: 2,
25
+ price: "19.90",
26
+ currency: "EUR",
27
+ productTitle: "Suggested product",
28
+ variantTitle: "50ml",
29
+ productUrl: "https://shop.example/products/suggested",
30
+ pageProductId: "gid://shopify/Product/1",
31
+ pageVariantId: "gid://shopify/ProductVariant/11",
32
+ };
33
+ afterEach(() => {
34
+ vi.clearAllMocks();
35
+ });
36
+ describe("Dialog.addToCart", () => {
37
+ it("forwards the full enriched input to the merchant callback", async () => {
38
+ const addToCartCallback = vi.fn().mockResolvedValue(undefined);
39
+ const { dialog } = buildDialog(addToCartCallback);
40
+ await dialog.addToCart(enrichedInput);
41
+ expect(addToCartCallback).toHaveBeenCalledWith(enrichedInput);
42
+ });
43
+ it("emits TRACK_ADD_TO_CART with the enriched fields and the userId", async () => {
44
+ const { dialog, emitExternalEvent } = buildDialog(vi.fn().mockResolvedValue(undefined));
45
+ await dialog.addToCart(enrichedInput);
46
+ expect(emitExternalEvent).toHaveBeenCalledWith(DialogEvents.TRACK_ADD_TO_CART, { userId: "user-1", ...enrichedInput });
47
+ });
48
+ it("keeps working with the legacy minimal input", async () => {
49
+ const addToCartCallback = vi.fn().mockResolvedValue(undefined);
50
+ const { dialog, emitExternalEvent } = buildDialog(addToCartCallback);
51
+ const legacyInput = {
52
+ productId: "gid://shopify/Product/42",
53
+ quantity: 1,
54
+ };
55
+ await dialog.addToCart(legacyInput);
56
+ expect(addToCartCallback).toHaveBeenCalledWith(legacyInput);
57
+ expect(emitExternalEvent).toHaveBeenCalledWith(DialogEvents.TRACK_ADD_TO_CART, { userId: "user-1", ...legacyInput });
58
+ });
59
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dialogCallbacks.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialogCallbacks.spec.d.ts","sourceRoot":"","sources":["../../src/__tests__/dialogCallbacks.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,40 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import { Dialog } from "../Dialog";
3
+ // The full Dialog constructor loads the assistant into the DOM; these tests
4
+ // only exercise the callback plumbing, so the instance is built from the
5
+ // prototype with just the fields the tested methods read.
6
+ const buildDialog = (callbacks) => {
7
+ const dialog = Object.create(Dialog.prototype);
8
+ Object.assign(dialog, { _callbacks: callbacks });
9
+ return dialog;
10
+ };
11
+ afterEach(() => {
12
+ vi.clearAllMocks();
13
+ });
14
+ describe("Dialog without commerce callbacks", () => {
15
+ it("throws an explicit configuration error from getProduct()", async () => {
16
+ const dialog = buildDialog(undefined);
17
+ expect(() => dialog.getProduct("product-1")).toThrowError(/callbacks\.getProduct/);
18
+ });
19
+ it("throws an explicit configuration error from addToCart()", async () => {
20
+ const dialog = buildDialog(undefined);
21
+ await expect(dialog.addToCart({
22
+ productId: "product-1",
23
+ variantId: "variant-1",
24
+ quantity: 1,
25
+ })).rejects.toThrowError(/callbacks\.addToCart/);
26
+ });
27
+ it("throws when only the other callback was provided", () => {
28
+ const dialog = buildDialog({ addToCart: vi.fn() });
29
+ expect(() => dialog.getProduct("product-1")).toThrowError(/callbacks\.getProduct/);
30
+ });
31
+ });
32
+ describe("Dialog with commerce callbacks (historical construction)", () => {
33
+ it("keeps forwarding getProduct() to the merchant callback", async () => {
34
+ const product = { id: "product-1" };
35
+ const getProduct = vi.fn().mockResolvedValue(product);
36
+ const dialog = buildDialog({ getProduct });
37
+ await expect(dialog.getProduct("product-1", "variant-1")).resolves.toBe(product);
38
+ expect(getProduct).toHaveBeenCalledWith("product-1", "variant-1");
39
+ });
40
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=publicTypes.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publicTypes.spec.d.ts","sourceRoot":"","sources":["../../src/__tests__/publicTypes.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,21 @@
1
+ // Compile-time contract of the public API: these assertions are checked by
2
+ // `tsc` (test-type), the runtime test only anchors the file in the suite.
3
+ import { describe, expect, expectTypeOf, it } from "vitest";
4
+ import { DialogSearchError, } from "../index";
5
+ describe("public API types", () => {
6
+ it("accepts construction with and without commerce callbacks", () => {
7
+ expectTypeOf().toMatchTypeOf();
8
+ expectTypeOf().toMatchTypeOf();
9
+ // Invalid callback values must be rejected.
10
+ expectTypeOf().not.toMatchTypeOf();
11
+ expectTypeOf().parameters.toMatchTypeOf();
12
+ expectTypeOf().returns.resolves.toEqualTypeOf();
13
+ expectTypeOf().toEqualTypeOf();
14
+ expectTypeOf().toEqualTypeOf();
15
+ expectTypeOf().toMatchTypeOf();
16
+ const error = new DialogSearchError({ status: 404, message: "not found" });
17
+ expectTypeOf(error.status).toEqualTypeOf();
18
+ expectTypeOf(error.code).toEqualTypeOf();
19
+ expect(error.name).toBe("DialogSearchError");
20
+ });
21
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=searchService.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"searchService.spec.d.ts","sourceRoot":"","sources":["../../src/__tests__/searchService.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,111 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { DialogSearchError } from "../DialogSearchError";
3
+ import { searchProducts } from "../services/search";
4
+ const API_KEY = "pk_test_abcdef";
5
+ const emptyResponse = {
6
+ queryId: "0198c3f2-0000-7000-8000-000000000000",
7
+ hits: [],
8
+ nbHits: 0,
9
+ page: 0,
10
+ nbPages: 0,
11
+ hitsPerPage: 20,
12
+ processingTimeMs: 3,
13
+ query: "running shoes",
14
+ };
15
+ const jsonResponse = (body, status = 200) => new Response(JSON.stringify(body), {
16
+ status,
17
+ headers: { "Content-Type": "application/json" },
18
+ });
19
+ const fetchMock = vi.fn();
20
+ beforeEach(() => {
21
+ vi.stubGlobal("fetch", fetchMock);
22
+ });
23
+ afterEach(() => {
24
+ vi.unstubAllGlobals();
25
+ vi.clearAllMocks();
26
+ });
27
+ describe("searchProducts", () => {
28
+ it("POSTs the exact request to /public/search with only the api key header", async () => {
29
+ fetchMock.mockResolvedValue(jsonResponse(emptyResponse));
30
+ await searchProducts(API_KEY, {
31
+ query: "running shoes",
32
+ page: 2,
33
+ hitsPerPage: 50,
34
+ });
35
+ expect(fetchMock).toHaveBeenCalledTimes(1);
36
+ const [url, init] = fetchMock.mock.calls[0];
37
+ expect(url).toMatch(/\/public\/search$/);
38
+ expect(init.method).toBe("POST");
39
+ expect(init.headers).toEqual({
40
+ "Content-Type": "application/json",
41
+ "x-dialog-api-key": API_KEY,
42
+ });
43
+ expect(JSON.parse(init.body)).toEqual({
44
+ query: "running shoes",
45
+ page: 2,
46
+ hitsPerPage: 50,
47
+ });
48
+ });
49
+ it("omits pagination fields the caller did not provide", async () => {
50
+ fetchMock.mockResolvedValue(jsonResponse(emptyResponse));
51
+ await searchProducts(API_KEY, { query: "running shoes" });
52
+ const [, init] = fetchMock.mock.calls[0];
53
+ expect(JSON.parse(init.body)).toEqual({
54
+ query: "running shoes",
55
+ });
56
+ });
57
+ it("resolves the typed response, including empty results", async () => {
58
+ fetchMock.mockResolvedValue(jsonResponse(emptyResponse));
59
+ await expect(searchProducts(API_KEY, { query: "running shoes" })).resolves.toEqual(emptyResponse);
60
+ });
61
+ it.each([
62
+ [401, "INVALID_WIDGET_API_KEY", "Invalid widget API key"],
63
+ [
64
+ 422,
65
+ "VALIDATION_ERROR",
66
+ "query must contain at least 2 visible characters",
67
+ ],
68
+ [429, "THROTTLED", "Too many requests"],
69
+ [503, "STOREFRONT_SEARCH_UNAVAILABLE", "Storefront search is unavailable"],
70
+ ])("rejects a %i answer with a DialogSearchError carrying status, code and message", async (status, code, message) => {
71
+ fetchMock.mockResolvedValue(jsonResponse({ statusCode: status, error: code, message }, status));
72
+ const error = await searchProducts(API_KEY, {
73
+ query: "running shoes",
74
+ }).catch((caught) => caught);
75
+ expect(error).toBeInstanceOf(DialogSearchError);
76
+ expect(error).toMatchObject({
77
+ name: "DialogSearchError",
78
+ status,
79
+ code,
80
+ message,
81
+ });
82
+ });
83
+ it("falls back to statusText when the error body is not JSON", async () => {
84
+ fetchMock.mockResolvedValue(new Response("<html>Bad Gateway</html>", {
85
+ status: 502,
86
+ statusText: "Bad Gateway",
87
+ }));
88
+ const error = await searchProducts(API_KEY, {
89
+ query: "running shoes",
90
+ }).catch((caught) => caught);
91
+ expect(error).toBeInstanceOf(DialogSearchError);
92
+ expect(error).toMatchObject({
93
+ status: 502,
94
+ code: undefined,
95
+ message: "Bad Gateway",
96
+ });
97
+ });
98
+ it("propagates network failures untouched", async () => {
99
+ const networkError = new TypeError("Failed to fetch");
100
+ fetchMock.mockRejectedValue(networkError);
101
+ await expect(searchProducts(API_KEY, { query: "running shoes" })).rejects.toBe(networkError);
102
+ });
103
+ it("forwards the AbortSignal and preserves the native AbortError", async () => {
104
+ const abortError = new DOMException("The operation was aborted.", "AbortError");
105
+ fetchMock.mockRejectedValue(abortError);
106
+ const controller = new AbortController();
107
+ await expect(searchProducts(API_KEY, { query: "running shoes" }, { signal: controller.signal })).rejects.toBe(abortError);
108
+ const [, init] = fetchMock.mock.calls[0];
109
+ expect(init.signal).toBe(controller.signal);
110
+ });
111
+ });
@@ -1,5 +1,6 @@
1
1
  export declare const config: {
2
2
  baseApiUrl: string;
3
+ monolithApiUrl: string;
3
4
  assistantUrl: string;
4
5
  };
5
6
  export type Config = typeof config;
@@ -1 +1 @@
1
- {"version":3,"file":"config.development.d.ts","sourceRoot":"","sources":["../../src/config/config.development.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM;;;CAGlB,CAAC;AACF,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC"}
1
+ {"version":3,"file":"config.development.d.ts","sourceRoot":"","sources":["../../src/config/config.development.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM;;;;CAKlB,CAAC;AACF,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC"}
@@ -1,4 +1,6 @@
1
1
  export const config = {
2
2
  baseApiUrl: "https://abkjr5ukvi.execute-api.eu-west-1.amazonaws.com",
3
+ // Nest monolith (staging) — serves POST /public/search.
4
+ monolithApiUrl: "https://fvcphlqyle.execute-api.eu-west-1.amazonaws.com",
3
5
  assistantUrl: "https://d2bycosa71tnxv.cloudfront.net/assets/index.js",
4
6
  };
@@ -1,5 +1,6 @@
1
1
  export declare const config: {
2
2
  baseApiUrl: string;
3
+ monolithApiUrl: string;
3
4
  assistantUrl: string;
4
5
  };
5
6
  export type Config = typeof config;
@@ -1 +1 @@
1
- {"version":3,"file":"config.production.d.ts","sourceRoot":"","sources":["../../src/config/config.production.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM;;;CAGlB,CAAC;AACF,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC"}
1
+ {"version":3,"file":"config.production.d.ts","sourceRoot":"","sources":["../../src/config/config.production.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM;;;;CAKlB,CAAC;AACF,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC"}
@@ -1,4 +1,6 @@
1
1
  export const config = {
2
2
  baseApiUrl: "https://rtbzcxkmwj.execute-api.eu-west-1.amazonaws.com",
3
+ // Nest monolith (production) — serves POST /public/search.
4
+ monolithApiUrl: "https://n5e8pa4r9a.execute-api.eu-west-1.amazonaws.com",
3
5
  assistantUrl: "https://d2zm7i5bmzo6ze.cloudfront.net/assets/index.js",
4
6
  };
@@ -1,5 +1,6 @@
1
1
  export declare const config: {
2
2
  baseApiUrl: string;
3
+ monolithApiUrl: string;
3
4
  assistantUrl: string;
4
5
  };
5
6
  export type Config = typeof config;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM;;;CAGlB,CAAC;AACF,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM;;;;CAKlB,CAAC;AACF,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC"}
@@ -1,4 +1,6 @@
1
1
  export const config = {
2
2
  baseApiUrl: "https://rtbzcxkmwj.execute-api.eu-west-1.amazonaws.com",
3
+ // Nest monolith (production) — serves POST /public/search.
4
+ monolithApiUrl: "https://n5e8pa4r9a.execute-api.eu-west-1.amazonaws.com",
3
5
  assistantUrl: "https://d2zm7i5bmzo6ze.cloudfront.net/assets/index.js",
4
6
  };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./Dialog";
2
+ export * from "./DialogSearchError";
2
3
  export * from "./EventsHandler";
3
4
  export * from "./types";
4
5
  export * from "./windowAudit";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./Dialog";
2
+ export * from "./DialogSearchError";
2
3
  export * from "./EventsHandler";
3
4
  export * from "./types";
4
5
  export * from "./windowAudit";
@@ -0,0 +1,7 @@
1
+ import { SearchOptions, SearchRequest, SearchResponse } from "../types/search";
2
+ /**
3
+ * One POST per invocation — no debounce, cache, retry or request state; the
4
+ * caller owns cancellation through `options.signal`.
5
+ */
6
+ export declare const searchProducts: (apiKey: string, request: SearchRequest, options?: SearchOptions) => Promise<SearchResponse>;
7
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/services/search.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAuB/E;;;GAGG;AACH,eAAO,MAAM,cAAc,GACzB,QAAQ,MAAM,EACd,SAAS,aAAa,EACtB,UAAU,aAAa,KACtB,OAAO,CAAC,cAAc,CAgBxB,CAAC"}
@@ -0,0 +1,37 @@
1
+ import { config } from "../config";
2
+ import { DialogSearchError } from "../DialogSearchError";
3
+ const SEARCH_PATH = "/public/search";
4
+ const API_KEY_HEADER = "x-dialog-api-key";
5
+ const toSearchError = async (response) => {
6
+ let body;
7
+ try {
8
+ body = (await response.json());
9
+ }
10
+ catch {
11
+ body = undefined;
12
+ }
13
+ const code = typeof body?.error === "string" ? body.error : undefined;
14
+ const message = typeof body?.message === "string" && body.message.length > 0
15
+ ? body.message
16
+ : response.statusText;
17
+ return new DialogSearchError({ status: response.status, code, message });
18
+ };
19
+ /**
20
+ * One POST per invocation — no debounce, cache, retry or request state; the
21
+ * caller owns cancellation through `options.signal`.
22
+ */
23
+ export const searchProducts = async (apiKey, request, options) => {
24
+ const response = await fetch(`${config.monolithApiUrl}${SEARCH_PATH}`, {
25
+ method: "POST",
26
+ headers: {
27
+ "Content-Type": "application/json",
28
+ [API_KEY_HEADER]: apiKey,
29
+ },
30
+ body: JSON.stringify(request),
31
+ signal: options?.signal,
32
+ });
33
+ if (!response.ok) {
34
+ throw await toSearchError(response);
35
+ }
36
+ return (await response.json());
37
+ };
@@ -18,6 +18,14 @@ export interface GenericAssistantEventPayload {
18
18
  userId?: string;
19
19
  productId?: string;
20
20
  variantId?: string;
21
+ quantity?: number;
22
+ price?: string;
23
+ currency?: string;
24
+ productTitle?: string;
25
+ variantTitle?: string;
26
+ productUrl?: string;
27
+ pageProductId?: string;
28
+ pageVariantId?: string;
21
29
  }
22
30
  export type AssistantEventPayload = GenericAssistantEventPayload;
23
31
  export interface AssistantEvent<T = AssistantEventPayload> {
@@ -1 +1 @@
1
- {"version":3,"file":"assistantEvent.d.ts","sourceRoot":"","sources":["../../src/types/assistantEvent.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,yBAAyB,CAAC;AAE7D,oBAAY,eAAe;IACzB,qBAAqB,wBAAwB;IAC7C,qBAAqB,wBAAwB;IAC7C,iBAAiB,oBAAoB;IACrC,4BAA4B,6BAA6B;IACzD,0BAA0B,6BAA6B;IACvD,kBAAkB,oBAAoB;IACtC,2BAA2B,6BAA6B;IACxD,2BAA2B,6BAA6B;CACzD;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,4BAA4B;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,qBAAqB,GAAG,4BAA4B,CAAC;AAEjE,MAAM,WAAW,cAAc,CAAC,CAAC,GAAG,qBAAqB;IACvD,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,CAAC,CAAC;CACZ"}
1
+ {"version":3,"file":"assistantEvent.d.ts","sourceRoot":"","sources":["../../src/types/assistantEvent.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,yBAAyB,CAAC;AAE7D,oBAAY,eAAe;IACzB,qBAAqB,wBAAwB;IAC7C,qBAAqB,wBAAwB;IAC7C,iBAAiB,oBAAoB;IACrC,4BAA4B,6BAA6B;IACzD,0BAA0B,6BAA6B;IACvD,kBAAkB,oBAAoB;IACtC,2BAA2B,6BAA6B;IACxD,2BAA2B,6BAA6B;CACzD;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb;AAKD,MAAM,WAAW,4BAA4B;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,qBAAqB,GAAG,4BAA4B,CAAC;AAEjE,MAAM,WAAW,cAAc,CAAC,CAAC,GAAG,qBAAqB;IACvD,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,CAAC,CAAC;CACZ"}
@@ -1,5 +1,10 @@
1
+ import { AddToCartInput } from "./events";
1
2
  import { SimplifiedProduct } from "./product";
2
3
  import { Theme } from "./theme";
4
+ export interface DialogCallbacks {
5
+ addToCart: (input: AddToCartInput) => Promise<void>;
6
+ getProduct: (productId: string, variantId?: string) => Promise<SimplifiedProduct>;
7
+ }
3
8
  export interface DialogConstructor {
4
9
  apiKey: string;
5
10
  locale: string;
@@ -9,16 +14,12 @@ export interface DialogConstructor {
9
14
  * Omit it to keep deriving the country from the locale (backward compatible).
10
15
  */
11
16
  countryCode?: string;
12
- callbacks: {
13
- addToCart: ({ productId, quantity, price, variantId, currency, }: {
14
- productId: string;
15
- quantity: number;
16
- price?: string;
17
- variantId?: string;
18
- currency?: string;
19
- }) => Promise<void>;
20
- getProduct: (productId: string, variantId?: string) => Promise<SimplifiedProduct>;
21
- };
17
+ /**
18
+ * Commerce callbacks. Optional: a search-only integration can omit them
19
+ * the assistant runtime still loads, and only `getProduct()` / `addToCart()`
20
+ * throw a configuration error when their callback is absent.
21
+ */
22
+ callbacks?: DialogCallbacks;
22
23
  theme?: Partial<Theme>;
23
24
  userId?: string;
24
25
  }
@@ -1 +1 @@
1
- {"version":3,"file":"constructor.d.ts","sourceRoot":"","sources":["../../src/types/constructor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE;QACT,SAAS,EAAE,CAAC,EACV,SAAS,EACT,QAAQ,EACR,KAAK,EACL,SAAS,EACT,QAAQ,GACT,EAAE;YACD,SAAS,EAAE,MAAM,CAAC;YAClB,QAAQ,EAAE,MAAM,CAAC;YACjB,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,SAAS,CAAC,EAAE,MAAM,CAAC;YACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;SACnB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;QACpB,UAAU,EAAE,CACV,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,KACf,OAAO,CAAC,iBAAiB,CAAC,CAAC;KACjC,CAAC;IACF,KAAK,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
1
+ {"version":3,"file":"constructor.d.ts","sourceRoot":"","sources":["../../src/types/constructor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,UAAU,EAAE,CACV,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,KACf,OAAO,CAAC,iBAAiB,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
@@ -34,7 +34,21 @@ export interface DiagnosticPayload {
34
34
  buttonType: DiagnosticButtonType;
35
35
  url: string;
36
36
  }
37
- export interface TrackEventPayload {
37
+ export interface AddToCartProductDetails {
38
+ productTitle?: string;
39
+ variantTitle?: string;
40
+ productUrl?: string;
41
+ pageProductId?: string;
42
+ pageVariantId?: string;
43
+ }
44
+ export interface AddToCartInput extends AddToCartProductDetails {
45
+ productId: string;
46
+ quantity: number;
47
+ price?: string;
48
+ currency?: string;
49
+ variantId?: string;
50
+ }
51
+ export interface TrackEventPayload extends AddToCartProductDetails {
38
52
  userId?: string;
39
53
  productId: string;
40
54
  variantId?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/types/events.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,+BAA+B,CAAC;AAEhE,oBAAY,YAAY;IACtB,cAAc,mBAAmB;IACjC,eAAe,oBAAoB;IACnC,YAAY,qBAAqB;IACjC,qBAAqB,qBAAqB;IAC1C,iBAAiB,sBAAsB;IACvC,qBAAqB,0BAA0B;CAChD;AACD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,WAAW,sBAAuB,SAAQ,sBAAsB;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,MAAM,oBAAoB,GAAG,mBAAmB,GAAG,cAAc,CAAC;AAExE,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,oBAAoB,CAAC;IACjC,GAAG,EAAE,MAAM,CAAC;CACb;AAQD,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAKD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAOD,MAAM,WAAW,0BAA0B;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAC5B;AAGD,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,kBAAkB,GAC1B,sBAAsB,GACtB,sBAAsB,GACtB,iBAAiB,GACjB,oBAAoB,GACpB,iBAAiB,GACjB,0BAA0B,CAAC;AAE/B,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,kBAAkB;IACjD,IAAI,EAAE,YAAY,CAAC;IACnB,OAAO,EAAE,CAAC,CAAC;CACZ"}
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/types/events.ts"],"names":[],"mappings":"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"}
@@ -3,5 +3,6 @@ export * from "./product";
3
3
  export * from "./suggestion";
4
4
  export * from "./theme";
5
5
  export * from "./constructor";
6
+ export * from "./search";
6
7
  export * from "./assistantEvent";
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC"}
@@ -3,4 +3,5 @@ export * from "./product";
3
3
  export * from "./suggestion";
4
4
  export * from "./theme";
5
5
  export * from "./constructor";
6
+ export * from "./search";
6
7
  export * from "./assistantEvent";
@@ -0,0 +1,50 @@
1
+ export interface SearchRequest {
2
+ /** Trimmed server-side; must keep at least two visible characters (code points). */
3
+ query: string;
4
+ /** Zero-indexed results page. Defaults to 0 server-side. */
5
+ page?: number;
6
+ /** Between 1 and 100. Defaults to 20 server-side. */
7
+ hitsPerPage?: number;
8
+ }
9
+ export interface SearchOptions {
10
+ /** Forwarded to fetch untouched: aborting rejects with the native AbortError. */
11
+ signal?: AbortSignal;
12
+ }
13
+ export interface SearchPrice {
14
+ /** Decimal amount as a string, exactly as indexed (e.g. "24.90"). */
15
+ amount: string;
16
+ currencyCode: string;
17
+ }
18
+ export interface SearchPriceRange {
19
+ min: SearchPrice;
20
+ max: SearchPrice;
21
+ }
22
+ /**
23
+ * Storefront-ready projection of a search hit. Every display field is
24
+ * best-effort: a product may carry only its id.
25
+ */
26
+ export interface SearchProduct {
27
+ id: string;
28
+ title?: string;
29
+ url?: string;
30
+ imageUrl?: string;
31
+ priceRange?: SearchPriceRange;
32
+ inStock?: boolean;
33
+ }
34
+ export interface SearchHit {
35
+ id: string;
36
+ score: number;
37
+ product: SearchProduct;
38
+ }
39
+ export interface SearchResponse {
40
+ /** Nest-generated id for search attribution analytics. */
41
+ queryId: string;
42
+ hits: SearchHit[];
43
+ nbHits: number;
44
+ page: number;
45
+ nbPages: number;
46
+ hitsPerPage: number;
47
+ processingTimeMs: number;
48
+ query: string;
49
+ }
50
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/types/search.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,oFAAoF;IACpF,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,iFAAiF;IACjF,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,WAAW,CAAC;IACjB,GAAG,EAAE,WAAW,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,aAAa,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,0DAA0D;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;CACf"}
@@ -0,0 +1,4 @@
1
+ // Mirrors the public Nest storefront-search DTO exactly (POST /public/search,
2
+ // `storefrontSearchRequestSchema` / `storefrontSearchResponseSchema` in
3
+ // @dialog/shared-schemas) — camelCase wire format, no internal Python casing.
4
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askdialog/dialog-sdk",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "private": false,
5
5
  "description": "Dialog SDK",
6
6
  "main": "dist/index.js",
@@ -26,6 +26,7 @@
26
26
  "author": "Dialog",
27
27
  "license": "MIT",
28
28
  "devDependencies": {
29
+ "@vitest/coverage-v8": "^3.0.0",
29
30
  "esbuild": "^0.25.12",
30
31
  "eslint-import-resolver-typescript": "^4.4.4",
31
32
  "eslint-plugin-import": "^2.32.0",
@@ -46,7 +47,10 @@
46
47
  "lint": "eslint .",
47
48
  "lint:fix": "eslint . --fix",
48
49
  "test": "vitest run",
50
+ "test-coverage": "vitest run --coverage",
51
+ "test-linter": "eslint .",
49
52
  "test-type": "tsc --noEmit",
53
+ "smoke": "chmod +x scripts/smoke.sh && ./scripts/smoke.sh",
50
54
  "link": "pnpm link --global",
51
55
  "unlink": "pnpm unlink",
52
56
  "watch": "tsc --watch",