@c-rex/services 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -2,6 +2,14 @@
2
2
  import axios from "axios";
3
3
 
4
4
  // ../constants/src/index.ts
5
+ var ALL = "*";
6
+ var LOG_LEVELS = {
7
+ critical: 2,
8
+ error: 3,
9
+ warning: 4,
10
+ info: 6,
11
+ debug: 7
12
+ };
5
13
  var API = {
6
14
  MAX_RETRY: 3,
7
15
  API_TIMEOUT: 1e4,
@@ -9,140 +17,241 @@ var API = {
9
17
  "content-Type": "application/json"
10
18
  }
11
19
  };
20
+ var SDK_CONFIG_KEY = "crex-sdk-config";
12
21
  var FLAGS_BY_LANG = {
13
22
  "en": "US",
14
23
  "de": "DE"
15
24
  };
16
25
  var EN_LANG = "en";
26
+ var TOPIC = "TOPIC";
27
+ var DOCUMENT = "DOCUMENT";
28
+ var PACKAGE = "PACKAGE";
17
29
  var RESULT_TYPES = {
18
- TOPIC: "TOPIC",
19
- DOCUMENT: "DOCUMENT",
20
- PACKAGE: "PACKAGE"
30
+ TOPIC,
31
+ DOCUMENT,
32
+ PACKAGE
21
33
  };
22
34
  var DEFAULT_COOKIE_LIMIT = 7 * 24 * 60 * 60 * 1e3;
35
+ var CREX_TOKEN_HEADER_KEY = "crex-token";
23
36
 
24
37
  // ../core/src/requests.ts
25
- import { Issuer } from "openid-client";
38
+ import { cookies } from "next/headers";
26
39
 
27
- // ../utils/src/utils.ts
28
- var call = async (method, params) => {
29
- try {
30
- const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/rpc`, {
31
- method: "POST",
32
- headers: { "Content-Type": "application/json" },
33
- body: JSON.stringify({ method, params })
40
+ // ../core/src/logger.ts
41
+ import winston from "winston";
42
+
43
+ // ../core/src/transports/matomo.ts
44
+ import Transport from "winston-transport";
45
+ var MatomoTransport = class extends Transport {
46
+ matomoTransport;
47
+ configs;
48
+ /**
49
+ * Creates a new instance of MatomoTransport.
50
+ *
51
+ * @param configs - The application configuration containing logging settings
52
+ */
53
+ constructor(configs) {
54
+ super({
55
+ level: configs.logs.matomo.minimumLevel,
56
+ silent: configs.logs.matomo.silent
34
57
  });
35
- const json = await res.json();
36
- if (!res.ok) throw new Error(json.error || "Unknown error");
37
- return json.data;
38
- } catch (error) {
39
- console.error(error);
40
- return null;
58
+ this.matomoTransport = new Transport();
59
+ this.configs = configs;
41
60
  }
42
- };
43
- var getCountryCodeByLang = (lang) => {
44
- const mappedKeys = Object.keys(FLAGS_BY_LANG);
45
- if (!mappedKeys.includes(lang)) {
46
- return lang;
61
+ /**
62
+ * Logs a message to Matomo if the message category is included in the configured categories.
63
+ *
64
+ * @param info - The log information including level, message, and category
65
+ * @param callback - Callback function to execute after logging
66
+ */
67
+ log(info, callback) {
68
+ const matomoCategory = this.configs.logs.matomo.categoriesLevel;
69
+ if (matomoCategory.includes(info.category) || matomoCategory.includes(ALL)) {
70
+ this.matomoTransport.log(info, callback);
71
+ }
47
72
  }
48
- const country = FLAGS_BY_LANG[lang];
49
- return country;
50
73
  };
51
74
 
52
- // ../utils/src/memory.ts
53
- function isBrowser() {
54
- return typeof window !== "undefined" && typeof document !== "undefined";
55
- }
56
- function saveInMemory(value, key) {
57
- if (isBrowser()) throw new Error("saveInMemory is not supported in browser");
58
- if (typeof global !== "undefined" && !(key in global)) {
59
- global[key] = null;
75
+ // ../core/src/transports/graylog.ts
76
+ import Transport2 from "winston-transport";
77
+ import Graylog2Transport from "winston-graylog2";
78
+ var GraylogTransport = class extends Transport2 {
79
+ graylogTransport;
80
+ configs;
81
+ /**
82
+ * Creates a new instance of GraylogTransport.
83
+ *
84
+ * @param configs - The application configuration containing logging settings
85
+ */
86
+ constructor(configs) {
87
+ super({
88
+ level: configs.logs.graylog.minimumLevel,
89
+ silent: configs.logs.graylog.silent
90
+ });
91
+ this.configs = configs;
92
+ this.graylogTransport = new Graylog2Transport({
93
+ name: "crex.net.documentation",
94
+ //name: "crex.net.blog",
95
+ silent: false,
96
+ handleExceptions: false,
97
+ graylog: {
98
+ servers: [
99
+ { host: "localhost", port: 12201 },
100
+ { host: "https://log.c-rex.net", port: 12202 }
101
+ //TODO: check the URL => https://log.c-rex.net:12202/gelf" GELF??
102
+ ]
103
+ }
104
+ });
60
105
  }
61
- const globalConfig = global[key];
62
- if (globalConfig === null) {
63
- global[key] = value;
106
+ /**
107
+ * Logs a message to Graylog if the message category is included in the configured categories.
108
+ *
109
+ * @param info - The log information including level, message, and category
110
+ * @param callback - Callback function to execute after logging
111
+ */
112
+ log(info, callback) {
113
+ const graylogCategory = this.configs.logs.graylog.categoriesLevel;
114
+ if (graylogCategory.includes(info.category) || graylogCategory.includes(ALL)) {
115
+ this.graylogTransport.log(info, callback);
116
+ }
64
117
  }
65
- }
66
- function getFromMemory(key) {
67
- if (isBrowser()) throw new Error("getFromMemory is not supported in browser");
68
- return global[key];
69
- }
70
-
71
- // ../utils/src/classMerge.ts
72
- import { clsx } from "clsx";
73
- import { twMerge } from "tailwind-merge";
118
+ };
74
119
 
75
- // ../utils/src/params.ts
76
- var createParams = (fieldsList, key = "Fields") => fieldsList.map((item) => ({
77
- key,
78
- value: item
79
- }));
80
- var generateQueryParams = (params) => {
81
- const queryParams = params.map(
82
- (param) => `${encodeURIComponent(param.key)}=${encodeURIComponent(param.value)}`
83
- ).join("&");
84
- return queryParams;
120
+ // ../core/src/logger.ts
121
+ import { getConfigs } from "@c-rex/utils/next-cookies";
122
+ var CrexLogger = class {
123
+ customerConfig;
124
+ logger;
125
+ /**
126
+ * Initializes the logger instance if it hasn't been initialized yet.
127
+ * Loads customer configuration and creates the logger with appropriate transports.
128
+ *
129
+ * @private
130
+ */
131
+ async initLogger() {
132
+ try {
133
+ if (!this.customerConfig) {
134
+ this.customerConfig = await getConfigs();
135
+ }
136
+ if (!this.logger) {
137
+ this.logger = this.createLogger();
138
+ }
139
+ } catch (error) {
140
+ console.error("Error initializing logger:", error);
141
+ }
142
+ }
143
+ /**
144
+ * Logs a message with the specified level and optional category.
145
+ *
146
+ * @param options - Logging options
147
+ * @param options.level - The log level (error, warn, info, etc.)
148
+ * @param options.message - The message to log
149
+ * @param options.category - Optional category for the log message
150
+ */
151
+ async log({ level, message, category }) {
152
+ await this.initLogger();
153
+ this.logger.log(level, message, category);
154
+ }
155
+ /**
156
+ * Creates a new Winston logger instance with configured transports.
157
+ *
158
+ * @private
159
+ * @returns A configured Winston logger instance
160
+ */
161
+ createLogger() {
162
+ return winston.createLogger({
163
+ levels: LOG_LEVELS,
164
+ transports: [
165
+ new winston.transports.Console({
166
+ level: this.customerConfig.logs.console.minimumLevel,
167
+ silent: this.customerConfig.logs.console.silent
168
+ }),
169
+ new MatomoTransport(this.customerConfig),
170
+ new GraylogTransport(this.customerConfig)
171
+ ]
172
+ });
173
+ }
85
174
  };
86
175
 
87
176
  // ../core/src/requests.ts
88
- import { getConfigs } from "@c-rex/utils/next-cookies";
89
- var CREX_TOKEN_HEADER_KEY = "crex-token";
90
- var CREX_TOKEN_EXPIRY_HEADER_KEY = "crex-token-expiry";
91
177
  var CrexApi = class {
92
178
  customerConfig;
93
179
  apiClient;
94
- async manageToken() {
95
- const headersAux = {};
96
- if (this.customerConfig.OIDC.client.enabled) {
97
- let token = getFromMemory(CREX_TOKEN_HEADER_KEY);
98
- const tokenExpiry = Number(
99
- getFromMemory(CREX_TOKEN_EXPIRY_HEADER_KEY)
100
- );
101
- const now = Math.floor(Date.now() / 1e3);
102
- if (!(token && tokenExpiry > now + 60)) {
103
- const { token: tokenAux, tokenExpiry: tokenExpiryAux } = await this.getToken();
104
- token = tokenAux;
105
- saveInMemory(token, CREX_TOKEN_HEADER_KEY);
106
- saveInMemory(tokenExpiryAux.toString(), CREX_TOKEN_EXPIRY_HEADER_KEY);
180
+ logger;
181
+ /**
182
+ * Initializes the API client if it hasn't been initialized yet.
183
+ * Loads customer configuration, creates the axios instance, and initializes the logger.
184
+ *
185
+ * @private
186
+ */
187
+ async initAPI() {
188
+ this.logger = new CrexLogger();
189
+ if (!this.customerConfig) {
190
+ const aux = cookies().get(SDK_CONFIG_KEY);
191
+ if (aux != void 0) {
192
+ this.customerConfig = JSON.parse(aux.value);
193
+ } else {
194
+ this.logger.log({
195
+ level: "error",
196
+ message: `utils.initAPI error: Config cookie not available`
197
+ });
198
+ throw new Error("Config cookie not available");
107
199
  }
108
- headersAux["Authorization"] = `Bearer ${token}`;
109
200
  }
110
- return headersAux;
201
+ if (!this.apiClient) {
202
+ this.apiClient = axios.create({
203
+ baseURL: this.customerConfig.baseUrl
204
+ });
205
+ }
111
206
  }
112
207
  async getToken() {
113
- let token = "";
114
- let tokenExpiry = 0;
115
208
  try {
116
- const now = Math.floor(Date.now() / 1e3);
117
- const issuer = await Issuer.discover(this.customerConfig.OIDC.client.issuer);
118
- const client = new issuer.Client({
119
- client_id: this.customerConfig.OIDC.client.id,
120
- client_secret: this.customerConfig.OIDC.client.secret
209
+ const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/token`, {
210
+ method: "POST",
211
+ credentials: "include"
121
212
  });
122
- const tokenSet = await client.grant({ grant_type: "client_credentials" });
123
- token = tokenSet.access_token;
124
- tokenExpiry = now + tokenSet.expires_at;
213
+ const { token } = await response.json();
214
+ return token;
125
215
  } catch (error) {
126
- call("CrexLogger.log", {
216
+ this.logger.log({
127
217
  level: "error",
128
- message: `API.getToken error when request ${this.customerConfig.OIDC.client.issuer}. Error: ${error}`
218
+ message: `utils.getToken error: ${error}`
129
219
  });
220
+ throw error;
130
221
  }
131
- return {
132
- token,
133
- tokenExpiry
134
- };
135
222
  }
136
- async initAPI() {
137
- if (!this.customerConfig) {
138
- this.customerConfig = await getConfigs();
139
- }
140
- if (!this.apiClient) {
141
- this.apiClient = axios.create({
142
- baseURL: this.customerConfig.baseUrl
223
+ async manageToken() {
224
+ try {
225
+ let token = "";
226
+ const hasToken = cookies().get(CREX_TOKEN_HEADER_KEY);
227
+ if (hasToken == void 0 || hasToken.value === null) {
228
+ const tokenResult = await this.getToken();
229
+ if (tokenResult === null) throw new Error("Token is undefined");
230
+ token = tokenResult;
231
+ } else {
232
+ token = hasToken.value;
233
+ }
234
+ return token;
235
+ } catch (error) {
236
+ this.logger.log({
237
+ level: "error",
238
+ message: `utils.manageToken error: ${error}`
143
239
  });
240
+ throw error;
144
241
  }
145
242
  }
243
+ /**
244
+ * Executes an API request with caching, authentication, and retry logic.
245
+ *
246
+ * @param options - Request options
247
+ * @param options.url - The URL to request
248
+ * @param options.method - The HTTP method to use
249
+ * @param options.params - Optional query parameters
250
+ * @param options.body - Optional request body
251
+ * @param options.headers - Optional request headers
252
+ * @returns The response data
253
+ * @throws Error if the request fails after maximum retries
254
+ */
146
255
  async execute({
147
256
  url,
148
257
  method,
@@ -152,10 +261,14 @@ var CrexApi = class {
152
261
  }) {
153
262
  await this.initAPI();
154
263
  let response = void 0;
155
- headers = {
156
- ...headers,
157
- ...await this.manageToken()
158
- };
264
+ if (this.customerConfig.OIDC.client.enabled) {
265
+ const token = await this.manageToken();
266
+ headers = {
267
+ ...headers,
268
+ Authorization: `Bearer ${token}`
269
+ };
270
+ this.apiClient.defaults.headers.common["Authorization"] = `Bearer ${token}`;
271
+ }
159
272
  for (let retry = 0; retry < API.MAX_RETRY; retry++) {
160
273
  try {
161
274
  response = await this.apiClient.request({
@@ -167,7 +280,7 @@ var CrexApi = class {
167
280
  });
168
281
  break;
169
282
  } catch (error) {
170
- call("CrexLogger.log", {
283
+ this.logger.log({
171
284
  level: "error",
172
285
  message: `API.execute ${retry + 1}\xBA error when request ${url}. Error: ${error}`
173
286
  });
@@ -183,6 +296,106 @@ var CrexApi = class {
183
296
  }
184
297
  };
185
298
 
299
+ // ../utils/src/utils.ts
300
+ var _generateShaKey = async (input) => {
301
+ const encoder = new TextEncoder();
302
+ const data = encoder.encode(input);
303
+ const hashBuffer = await crypto.subtle.digest("SHA-1", data);
304
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
305
+ const base64url = btoa(String.fromCharCode(...hashArray)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
306
+ return base64url.slice(0, 12);
307
+ };
308
+ var getCountryCodeByLang = (lang) => {
309
+ const mappedKeys = Object.keys(FLAGS_BY_LANG);
310
+ if (!mappedKeys.includes(lang)) {
311
+ return lang;
312
+ }
313
+ const country = FLAGS_BY_LANG[lang];
314
+ return country;
315
+ };
316
+
317
+ // ../utils/src/call.ts
318
+ var call = async (method, params) => {
319
+ const shaKey = await _generateShaKey(JSON.stringify({ method, params }));
320
+ const cache = localStorage.getItem(shaKey);
321
+ if (cache !== null) {
322
+ const { data, expireDate } = JSON.parse(cache);
323
+ if (new Date(expireDate) > /* @__PURE__ */ new Date()) {
324
+ return JSON.parse(data);
325
+ } else {
326
+ localStorage.removeItem(shaKey);
327
+ }
328
+ }
329
+ const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/rpc`, {
330
+ method: "POST",
331
+ headers: { "Content-Type": "application/json" },
332
+ body: JSON.stringify({ method, params }),
333
+ credentials: "include"
334
+ });
335
+ const json = await res.json();
336
+ if (!res.ok) throw new Error(json.error || "Unknown error");
337
+ const today = /* @__PURE__ */ new Date();
338
+ const result = {
339
+ data: JSON.stringify(json.data),
340
+ expireDate: new Date(today.getTime() + 1e3 * 60 * 60)
341
+ };
342
+ localStorage.setItem(shaKey, JSON.stringify(result));
343
+ return json.data;
344
+ };
345
+
346
+ // ../utils/src/classMerge.ts
347
+ import { clsx } from "clsx";
348
+ import { twMerge } from "tailwind-merge";
349
+
350
+ // ../utils/src/params.ts
351
+ var createParams = (fieldsList, key = "Fields") => fieldsList.map((item) => ({
352
+ key,
353
+ value: item
354
+ }));
355
+ var generateQueryParams = (params) => {
356
+ const queryParams = params.map(
357
+ (param) => `${encodeURIComponent(param.key)}=${encodeURIComponent(param.value)}`
358
+ ).join("&");
359
+ return queryParams;
360
+ };
361
+
362
+ // ../utils/src/renditions.ts
363
+ var getFileRenditions = ({ renditions }) => {
364
+ if (renditions == void 0 || renditions.length == 0) {
365
+ return {
366
+ filesToDownload: [],
367
+ filesToOpen: []
368
+ };
369
+ }
370
+ const filteredRenditions = renditions.filter(
371
+ (item) => item.format != "application/xhtml+xml" && item.format != "application/json" && item.format != "application/llm+xml"
372
+ );
373
+ if (filteredRenditions.length == 0 || filteredRenditions[0] == void 0) {
374
+ return {
375
+ filesToDownload: [],
376
+ filesToOpen: []
377
+ };
378
+ }
379
+ const filesToDownload = filteredRenditions.map((item) => {
380
+ const filteredLinks = item.links.filter((item2) => item2.rel == "download");
381
+ return {
382
+ format: item.format,
383
+ link: filteredLinks[0].href
384
+ };
385
+ });
386
+ const filesToOpen = filteredRenditions.map((item) => {
387
+ const filteredLinks = item.links.filter((item2) => item2.rel == "view");
388
+ return {
389
+ format: item.format,
390
+ link: filteredLinks[0].href
391
+ };
392
+ });
393
+ return {
394
+ filesToDownload,
395
+ filesToOpen
396
+ };
397
+ };
398
+
186
399
  // src/baseService.ts
187
400
  var BaseService = class {
188
401
  api;
@@ -241,7 +454,15 @@ var RenditionsService = class extends BaseService {
241
454
  constructor() {
242
455
  super("Renditions/");
243
456
  }
244
- async getHTMLRendition(renditions) {
457
+ /**
458
+ * Retrieves the HTML rendition from a list of renditions.
459
+ * Filters for renditions with format 'application/xhtml+xml' and rel 'view'.
460
+ *
461
+ * @param renditions - Array of rendition objects to process
462
+ * @returns A promise that resolves to the HTML content as a string, or empty string if no suitable rendition is found
463
+ * @throws Error if the API request fails
464
+ */
465
+ async getHTMLRendition({ renditions }) {
245
466
  const filteredRenditions = renditions.filter(
246
467
  (item2) => item2.format == "application/xhtml+xml"
247
468
  );
@@ -259,41 +480,6 @@ var RenditionsService = class extends BaseService {
259
480
  });
260
481
  return response;
261
482
  }
262
- getFileRenditions = (renditions) => {
263
- if (renditions == void 0 || renditions.length == 0) {
264
- return {
265
- filesToDownload: [],
266
- filesToOpen: []
267
- };
268
- }
269
- const filteredRenditions = renditions.filter(
270
- (item) => item.format != "application/xhtml+xml" && item.format != "application/json" && item.format != "application/llm+xml"
271
- );
272
- if (filteredRenditions.length == 0 || filteredRenditions[0] == void 0) {
273
- return {
274
- filesToDownload: [],
275
- filesToOpen: []
276
- };
277
- }
278
- const filesToDownload = filteredRenditions.map((item) => {
279
- const filteredLinks = item.links.filter((item2) => item2.rel == "download");
280
- return {
281
- format: item.format,
282
- link: filteredLinks[0].href
283
- };
284
- });
285
- const filesToOpen = filteredRenditions.map((item) => {
286
- const filteredLinks = item.links.filter((item2) => item2.rel == "view");
287
- return {
288
- format: item.format,
289
- link: filteredLinks[0].href
290
- };
291
- });
292
- return {
293
- filesToDownload,
294
- filesToOpen
295
- };
296
- };
297
483
  };
298
484
 
299
485
  // src/directoryNodes.ts
@@ -301,11 +487,26 @@ var DirectoryNodesService = class extends BaseService {
301
487
  constructor() {
302
488
  super("DirectoryNodes/");
303
489
  }
490
+ /**
491
+ * Retrieves a specific directory node by its ID.
492
+ *
493
+ * @param id - The unique identifier of the directory node
494
+ * @returns A promise that resolves to the directory node data
495
+ * @throws Error if the API request fails
496
+ */
304
497
  async getItem(id) {
305
498
  return await this.request({
306
499
  path: id
307
500
  });
308
501
  }
502
+ /**
503
+ * Retrieves a list of directory nodes based on specified filters.
504
+ *
505
+ * @param options - Options for filtering the directory nodes list
506
+ * @param options.filters - Optional array of filter strings to apply
507
+ * @returns A promise that resolves to the directory nodes response
508
+ * @throws Error if the API request fails
509
+ */
309
510
  async getList({
310
511
  filters = []
311
512
  }) {
@@ -335,6 +536,14 @@ var DocumentTypesService = class extends BaseService {
335
536
  constructor() {
336
537
  super("DocumentTypes/");
337
538
  }
539
+ /**
540
+ * Retrieves document type labels for the specified fields.
541
+ * The labels are restricted to English language (EN-us).
542
+ *
543
+ * @param fields - Array of field names to retrieve labels for
544
+ * @returns A promise that resolves to an array of label strings
545
+ * @throws Error if the API request fails
546
+ */
338
547
  async getLabels(fields) {
339
548
  const params = [
340
549
  {
@@ -355,11 +564,12 @@ import { getConfigs as getConfigs2 } from "@c-rex/utils/next-cookies";
355
564
  var transformInformationUnits = async (data) => {
356
565
  const config = await getConfigs2();
357
566
  const items = await Promise.all(data.items.map(async (item) => {
358
- let link = `/topics/${item.shortId}`;
359
567
  const type = item.class.labels.filter((item2) => item2.language === EN_LANG)[0].value.toUpperCase();
360
- const service = new RenditionsService();
361
- const { filesToOpen, filesToDownload } = service.getFileRenditions(item?.renditions);
362
- if (type == RESULT_TYPES.DOCUMENT) {
568
+ const { filesToOpen, filesToDownload } = getFileRenditions({ renditions: item?.renditions });
569
+ let link = `/topics/${item.shortId}`;
570
+ if (config.results.articlePageLayout == "BLOG") {
571
+ link = `/blog/${item.shortId}`;
572
+ } else if (type == RESULT_TYPES.DOCUMENT) {
363
573
  link = `/documents/${item.shortId}`;
364
574
  }
365
575
  return {
@@ -385,6 +595,18 @@ var InformationUnitsService = class extends BaseService {
385
595
  constructor() {
386
596
  super("InformationUnits/");
387
597
  }
598
+ /**
599
+ * Retrieves a list of information units based on specified criteria.
600
+ *
601
+ * @param options - Options for filtering and paginating the information units list
602
+ * @param options.queries - Optional search query string
603
+ * @param options.page - Optional page number for pagination (defaults to 1)
604
+ * @param options.fields - Optional array of fields to include in the response
605
+ * @param options.filters - Optional array of filter strings to apply
606
+ * @param options.languages - Optional array of language codes to filter by
607
+ * @returns A promise that resolves to the information units response
608
+ * @throws Error if the API request fails
609
+ */
388
610
  async getList({
389
611
  queries = "",
390
612
  page = 1,
@@ -415,6 +637,15 @@ var InformationUnitsService = class extends BaseService {
415
637
  transformer: transformInformationUnits
416
638
  });
417
639
  }
640
+ /**
641
+ * Retrieves a specific information unit by its ID.
642
+ * Includes renditions, directory nodes, version information, titles, languages, and labels.
643
+ *
644
+ * @param options - Options for retrieving the information unit
645
+ * @param options.id - The unique identifier of the information unit
646
+ * @returns A promise that resolves to the information unit data
647
+ * @throws Error if the API request fails
648
+ */
418
649
  async getItem({ id }) {
419
650
  const params = [
420
651
  { key: "Fields", value: "renditions" },
@@ -429,32 +660,52 @@ var InformationUnitsService = class extends BaseService {
429
660
  params
430
661
  });
431
662
  }
432
- async getSuggestions({ query }) {
663
+ /**
664
+ * Retrieves autocomplete suggestions based on a query prefix.
665
+ *
666
+ * @param options - Options for retrieving suggestions
667
+ * @param options.query - The query prefix to get suggestions for
668
+ * @param options.language - The language of the suggestions
669
+ * @returns A promise that resolves to an array of suggestion strings
670
+ * @throws Error if the API request fails
671
+ */
672
+ async getSuggestions({ query, language }) {
433
673
  return await this.request({
434
674
  path: `Suggestions`,
435
- params: [{ key: "prefix", value: query }],
675
+ params: [
676
+ { key: "prefix", value: query },
677
+ { key: "lang", value: language }
678
+ ],
436
679
  transformer: (data) => {
437
- return data.suggestions.map((item) => item.value);
680
+ const suggestions = [];
681
+ const comparableList = [];
682
+ data.suggestions.forEach((item) => {
683
+ suggestions.push(item.value);
684
+ comparableList.push(item.value.toLowerCase());
685
+ });
686
+ if (!comparableList.includes(query.toLowerCase())) {
687
+ return [query, ...suggestions];
688
+ }
689
+ return suggestions;
438
690
  }
439
691
  });
440
692
  }
441
693
  };
442
694
 
443
695
  // src/language.ts
696
+ import { getConfigs as getConfigs3 } from "@c-rex/utils/next-cookies";
444
697
  var LanguageService = class extends BaseService {
445
- constructor(endpoint) {
446
- super(endpoint);
698
+ constructor() {
699
+ const configs = getConfigs3();
700
+ super(configs.languageSwitcher.endpoint);
447
701
  }
448
- /*
449
- public static async getInstance(): Promise<LanguageService> {
450
- const customerConfig = await getConfigs()
451
-
452
- if (!LanguageService.instance) {
453
- LanguageService.instance = new LanguageService(customerConfig.languageSwitcher.endpoint);
454
- }
455
- return LanguageService.instance;
456
- }
457
- */
702
+ /**
703
+ * Retrieves a list of available languages and their associated countries.
704
+ * Transforms the API response to include language code, country code, and original value.
705
+ *
706
+ * @returns A promise that resolves to an array of language and country objects
707
+ * @throws Error if the API request fails
708
+ */
458
709
  async getLanguagesAndCountries() {
459
710
  return await this.request({
460
711
  transformer: (data) => {