@stackone/transport 1.6.2 → 1.8.1

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 ADDED
@@ -0,0 +1,755 @@
1
+ import { z } from "zod";
2
+ import axios from "axios";
3
+ import qs from "qs";
4
+ import { isMissing, notMissing } from "@stackone/utils";
5
+ import { safeEvaluate } from "@stackone/expressions";
6
+
7
+ //#region src/authorization/authorizationHeaders.ts
8
+ const createAuthorizationHeaders = (authenticationParams) => {
9
+ switch (authenticationParams.type) {
10
+ case "basic": return createBasicAuthorizationHeader(authenticationParams);
11
+ case "bearer": return createBearerAuthorizationHeader(authenticationParams);
12
+ default: throw new Error("Invalid authentication type");
13
+ }
14
+ };
15
+ const createBasicAuthorizationHeader = ({ username = "", password = "", encoding = "base64" }) => {
16
+ const credentialsEncoding = encoding;
17
+ const encodedCredentials = Buffer.from(`${username}:${password}`).toString(credentialsEncoding);
18
+ return { authorization: `Basic ${encodedCredentials}` };
19
+ };
20
+ const createBearerAuthorizationHeader = ({ token, includeBearer = true }) => {
21
+ return { authorization: `${includeBearer ? "Bearer " : ""}${token}` };
22
+ };
23
+
24
+ //#endregion
25
+ //#region src/customErrors/schemas.ts
26
+ const CUSTOM_ERROR_CONFIG_SCHEMA = z.object({
27
+ receivedStatus: z.number(),
28
+ targetStatus: z.number(),
29
+ message: z.string().optional(),
30
+ condition: z.string().optional()
31
+ });
32
+
33
+ //#endregion
34
+ //#region src/customErrors/types.ts
35
+ const HttpErrorMessages = {
36
+ 100: "Continue",
37
+ 101: "Switching Protocols",
38
+ 102: "Processing",
39
+ 200: "OK",
40
+ 201: "Created",
41
+ 202: "Accepted",
42
+ 203: "Non-Authoritative Information",
43
+ 204: "No Content",
44
+ 205: "Reset Content",
45
+ 206: "Partial Content",
46
+ 207: "Multi-Status",
47
+ 208: "Already Reported",
48
+ 226: "IM Used",
49
+ 300: "Multiple Choices",
50
+ 301: "Moved Permanently",
51
+ 302: "Found",
52
+ 303: "See Other",
53
+ 304: "Not Modified",
54
+ 305: "Use Proxy",
55
+ 307: "Temporary Redirect",
56
+ 308: "Permanent Redirect",
57
+ 400: "Bad request",
58
+ 401: "Unauthorized",
59
+ 402: "Payment Required",
60
+ 403: "Forbidden",
61
+ 404: "Not Found",
62
+ 405: "Method Not Allowed",
63
+ 406: "Not Acceptable",
64
+ 407: "Proxy Authentication Required",
65
+ 408: "Request Timeout",
66
+ 409: "Conflict",
67
+ 410: "Gone",
68
+ 411: "Length Required",
69
+ 412: "Precondition Failed",
70
+ 413: "Payload Too Large",
71
+ 414: "URI Too Long",
72
+ 415: "Unsupported Media Type",
73
+ 416: "Range Not Satisfiable",
74
+ 417: "Expectation Failed",
75
+ 418: "I'm a teapot",
76
+ 421: "Misdirected Request",
77
+ 422: "Unprocessable Entity",
78
+ 423: "Locked",
79
+ 424: "Failed Dependency",
80
+ 425: "Too Early",
81
+ 426: "Upgrade Required",
82
+ 428: "Precondition Required",
83
+ 429: "Too many Requests",
84
+ 431: "Request Header Fields Too Large",
85
+ 451: "Unavailable For Legal Reasons",
86
+ 499: "Client Closed Request",
87
+ 500: "Internal server error",
88
+ 501: "Not Implemented",
89
+ 502: "Bad Gateway",
90
+ 503: "Unavailable",
91
+ 504: "Gateway Timeout",
92
+ 505: "HTTP Version Not Supported",
93
+ 506: "Variant Also Negotiates",
94
+ 507: "Insufficient Storage",
95
+ 508: "Loop Detected",
96
+ 510: "Not Extended",
97
+ 511: "Network Authentication Required"
98
+ };
99
+
100
+ //#endregion
101
+ //#region src/errors/httpResponseError.ts
102
+ /**
103
+ * Represents an error resulting from an HTTP response.
104
+ *
105
+ * @remarks
106
+ * This class is used to encapsulate HTTP response errors, including the response object and an optional message.
107
+ * Extend this class to create more specific HTTP error types if needed.
108
+ *
109
+ * @example
110
+ * try {
111
+ * // ... code that may throw an HttpResponseError
112
+ * } catch (error) {
113
+ * if (error instanceof HttpResponseError) {
114
+ * console.error(error.response.status, error.message);
115
+ * }
116
+ * }
117
+ *
118
+ * @public
119
+ */
120
+ var HttpResponseError = class HttpResponseError extends Error {
121
+ response;
122
+ /**
123
+ * Creates a new HttpResponseError instance.
124
+ *
125
+ * @param response - The HttpResponse object associated with the error.
126
+ * @param message - Optional. A custom error message. If not provided, a default message is generated from the response status and message.
127
+ */
128
+ constructor(response, message) {
129
+ super(message);
130
+ this.name = "HttpResponseError";
131
+ this.response = response;
132
+ this.message = message ?? `HTTP error: ${response.status}`;
133
+ if (Error.captureStackTrace) Error.captureStackTrace(this, HttpResponseError);
134
+ }
135
+ /**
136
+ * Returns a string representation of the error, including the status code and message.
137
+ *
138
+ * @returns A string describing the error.
139
+ */
140
+ toString() {
141
+ const message = this.message ? `: ${this.message}` : "";
142
+ return `${this.name} [${this.response.status}]${message}`;
143
+ }
144
+ };
145
+
146
+ //#endregion
147
+ //#region src/httpClient/httpClient.ts
148
+ const createAxiosInstance = () => {
149
+ return axios.create({ maxBodyLength: Infinity });
150
+ };
151
+ var HttpClient = class {
152
+ #transportInstance;
153
+ constructor({ transportInstance = createAxiosInstance() } = {}) {
154
+ this.#transportInstance = transportInstance;
155
+ }
156
+ async request({ headers = {}, url, method = "get", queryParams, maxRedirects = 0, responseType, cacheTTL, context, payload, httpsAgent }) {
157
+ try {
158
+ const urlWithQueryParams = new URLSearchParams(queryParams).toString();
159
+ const requestHeaders = this.#normalizeHeaders(headers);
160
+ const response = await this.#transportInstance.request({
161
+ headers: requestHeaders,
162
+ url: urlWithQueryParams ? `${url}?${urlWithQueryParams}` : url,
163
+ method,
164
+ maxRedirects,
165
+ responseType,
166
+ data: this.#getSafePayload(payload, requestHeaders),
167
+ httpsAgent
168
+ });
169
+ const requestResponse = {
170
+ data: response.data,
171
+ status: response.status,
172
+ headers: this.#extractAxiosHeaders(response.headers),
173
+ requestUrl: url,
174
+ responseType: response?.config?.responseType,
175
+ responseTime: /* @__PURE__ */ new Date()
176
+ };
177
+ return requestResponse;
178
+ } catch (e) {
179
+ throw e;
180
+ }
181
+ }
182
+ async get({ headers, url, queryParams, maxRedirects, cacheTTL, context }) {
183
+ return this.request({
184
+ url,
185
+ method: "get",
186
+ queryParams,
187
+ headers,
188
+ maxRedirects,
189
+ cacheTTL,
190
+ context
191
+ });
192
+ }
193
+ async post({ headers, url, maxRedirects, cacheTTL, context, payload }) {
194
+ return this.request({
195
+ url,
196
+ method: "post",
197
+ headers,
198
+ maxRedirects,
199
+ cacheTTL,
200
+ context,
201
+ payload
202
+ });
203
+ }
204
+ #normalizeHeaders(requestHeaders) {
205
+ if (!requestHeaders) return {};
206
+ const headers = {};
207
+ Object.keys(requestHeaders).forEach((key) => {
208
+ headers[key.toLowerCase()] = requestHeaders[key] || "";
209
+ });
210
+ return headers;
211
+ }
212
+ #extractAxiosHeaders(axiosHeaders) {
213
+ if (!axiosHeaders) return {};
214
+ const headers = {};
215
+ Object.keys(axiosHeaders).forEach((key) => {
216
+ headers[key] = axiosHeaders[key] || "";
217
+ });
218
+ return headers;
219
+ }
220
+ #isFormUrlEncoded(headers) {
221
+ return headers["content-type"] === "application/x-www-form-urlencoded";
222
+ }
223
+ #getSafePayload(payload, headers) {
224
+ if (!payload) return void 0;
225
+ if (this.#isFormUrlEncoded(headers)) return qs.stringify(payload);
226
+ return payload;
227
+ }
228
+ };
229
+
230
+ //#endregion
231
+ //#region src/httpClient/httpClientManager.ts
232
+ const buildHttpClientInstance = () => {
233
+ return new HttpClient();
234
+ };
235
+ var HttpClientManager = class {
236
+ static httpClientInstance = null;
237
+ static async getInstance({ getHttpClient = buildHttpClientInstance } = {}) {
238
+ this.httpClientInstance ??= getHttpClient();
239
+ return this.httpClientInstance;
240
+ }
241
+ static resetInstance() {
242
+ this.httpClientInstance = null;
243
+ }
244
+ };
245
+
246
+ //#endregion
247
+ //#region src/httpClient/types.ts
248
+ const HttpMethods = [
249
+ "get",
250
+ "post",
251
+ "put",
252
+ "delete",
253
+ "patch"
254
+ ];
255
+
256
+ //#endregion
257
+ //#region src/lockManager/index.ts
258
+ /**
259
+ * Manages asynchronous locks based on string keys.
260
+ * Useful for ensuring sequential execution of async operations that share a resource.
261
+ */
262
+ var LockManager = class {
263
+ locks;
264
+ constructor() {
265
+ this.locks = /* @__PURE__ */ new Map();
266
+ }
267
+ /**
268
+ * Acquires a lock for the given key and runs the provided asynchronous operation.
269
+ * Ensures the operation executes only after previous locks on the same key are released.
270
+ *
271
+ * @param key - A unique string representing the lock.
272
+ * @param operation - An async function to run while the lock is held.
273
+ * @returns The result of the operation.
274
+ */
275
+ async withLock(key, operation) {
276
+ await this.lock(key);
277
+ try {
278
+ return operation();
279
+ } finally {
280
+ this.unlock(key);
281
+ }
282
+ }
283
+ /**
284
+ * Queues a lock for the given key. If the key is already locked,
285
+ * waits until the previous lock is released.
286
+ *
287
+ * @param key - A unique string representing the lock.
288
+ */
289
+ async lock(key) {
290
+ let unlock;
291
+ const promise = new Promise((resolve) => unlock = resolve);
292
+ const hasLock = this.locks.has(key);
293
+ const currentLocks = this.locks.get(key);
294
+ if (hasLock && currentLocks) {
295
+ currentLocks.push({
296
+ lock: promise,
297
+ unlock
298
+ });
299
+ const previousLockIndex = currentLocks.length - 2;
300
+ await currentLocks[previousLockIndex].lock;
301
+ } else this.locks.set(key, [{
302
+ lock: promise,
303
+ unlock
304
+ }]);
305
+ }
306
+ /**
307
+ * Releases the lock for the given key. If more locks are queued, allows the next one to proceed.
308
+ *
309
+ * @param key - A unique string representing the lock.
310
+ */
311
+ unlock(key) {
312
+ const hasLock = this.locks.has(key);
313
+ const currentLocks = this.locks.get(key);
314
+ if (hasLock && currentLocks && currentLocks.length > 0) {
315
+ const nextUnlock = currentLocks.shift()?.unlock;
316
+ nextUnlock?.();
317
+ }
318
+ if (this.queueLength(key) === 0) this.locks.delete(key);
319
+ }
320
+ /**
321
+ * Returns the number of queued locks for a given key.
322
+ *
323
+ * @param key - The lock key to check.
324
+ * @returns Number of locks in the queue, or undefined if none exist.
325
+ */
326
+ queueLength(key) {
327
+ return this.locks.get(key)?.length;
328
+ }
329
+ /**
330
+ * Clears all locks and queued operations.
331
+ */
332
+ close() {
333
+ this.locks.clear();
334
+ }
335
+ };
336
+
337
+ //#endregion
338
+ //#region src/memoryStore/constants.ts
339
+ const ONE_THOUSAND = 1e3;
340
+ const ONE_HUNDRED = 100;
341
+ const ONE_MINUTE_IN_SECONDS = 60;
342
+ const ONE_MINUTE_IN_MS = 6e4;
343
+ const DEFAULT_CACHE_TTL = ONE_MINUTE_IN_SECONDS;
344
+ const DEFAULT_EVICTION_FREQUENCY = ONE_MINUTE_IN_MS;
345
+ const DEFAULT_STALE_DATA_THRESHOLD = ONE_MINUTE_IN_MS * 10;
346
+ const DEFAULT_TRUNCATION_THRESHOLD = ONE_HUNDRED;
347
+ const DEFAULT_TRUNCATION_PERCENTAGE = 10;
348
+ let MemoryStoreErrorCodes = /* @__PURE__ */ function(MemoryStoreErrorCodes$1) {
349
+ MemoryStoreErrorCodes$1["MemoryStorePruneError"] = "MemoryStorePruneError";
350
+ return MemoryStoreErrorCodes$1;
351
+ }({});
352
+
353
+ //#endregion
354
+ //#region src/memoryStore/index.ts
355
+ var MemoryStore = class {
356
+ config;
357
+ instantiator;
358
+ dataStore;
359
+ lockManager;
360
+ expiryMap;
361
+ evictionFrequency;
362
+ staleDataThreshold;
363
+ truncateThreshold;
364
+ truncationPercentage;
365
+ logger;
366
+ typeGuard;
367
+ dispose;
368
+ evictionInterval;
369
+ lastAccessedAt = Date.now();
370
+ /**
371
+ * Constructs a new MemoryStore instance with optional configuration.
372
+ * @param config Configuration options for the store.
373
+ */
374
+ constructor(config = {}) {
375
+ this.config = config;
376
+ this.initialize(config);
377
+ }
378
+ initialize(config = this.config) {
379
+ this.instantiator = config?.instantiator ?? "Unknown";
380
+ this.logger = config?.logger;
381
+ this.dataStore = config?.dataStore ?? /* @__PURE__ */ new Map();
382
+ this.lockManager = config?.lockManager ?? new LockManager();
383
+ this.expiryMap = config?.expiryMap ?? /* @__PURE__ */ new Map();
384
+ this.evictionFrequency = config?.evictionFrequency ?? DEFAULT_EVICTION_FREQUENCY;
385
+ this.staleDataThreshold = config?.staleDataThreshold ?? DEFAULT_STALE_DATA_THRESHOLD;
386
+ this.truncateThreshold = config?.truncateThreshold ?? DEFAULT_TRUNCATION_THRESHOLD;
387
+ this.truncationPercentage = config?.truncationPercentage ?? DEFAULT_TRUNCATION_PERCENTAGE;
388
+ this.typeGuard = config?.typeGuard;
389
+ this.dispose = config?.dispose;
390
+ this.startEvictionTask();
391
+ }
392
+ /**
393
+ * Retrieves data for a given key.
394
+ * @param key Cache key.
395
+ * @returns The cached data or `null` if not found.
396
+ */
397
+ async getData(key) {
398
+ if (!this.isReady()) this.initialize();
399
+ this.updateLastAccessedAt();
400
+ return this.lockManager.withLock(key, async () => this.dataStore.get(key) ?? null);
401
+ }
402
+ /**
403
+ * Stores data in the cache.
404
+ * @param key The key to store the data under.
405
+ * @param value The value to store.
406
+ * @param cacheTTL Time-to-live in seconds (default: 60s).
407
+ * @returns `true` if data was stored, `false` if rejected by type guard.
408
+ */
409
+ async setData({ key, value, cacheTTL = DEFAULT_CACHE_TTL }) {
410
+ if (!this.isReady()) this.initialize();
411
+ this.updateLastAccessedAt();
412
+ if (notMissing(this.typeGuard) && !this.typeGuard(value)) return false;
413
+ const cacheTTLInMs = cacheTTL * ONE_THOUSAND;
414
+ const expiryTime = Date.now() + cacheTTLInMs;
415
+ await this.lockManager.withLock(key, async () => {
416
+ if (notMissing(this.typeGuard) && this.typeGuard(value) || isMissing(this.typeGuard) && this.typeGuardBypass(value)) this.dataStore.set(key, value);
417
+ this.expiryMap.set(key, expiryTime);
418
+ });
419
+ return true;
420
+ }
421
+ typeGuardBypass(data) {
422
+ this.logger?.debug({
423
+ category: "MemoryStore",
424
+ message: `${this.instantiator} MemoryStore setting data without type guard - you should probably configure one`
425
+ });
426
+ return true;
427
+ }
428
+ /**
429
+ * Deletes a key from the cache.
430
+ * Calls the dispose callback if defined.
431
+ * @param key Cache key to delete.
432
+ * @returns `true` if deleted, `false` otherwise.
433
+ */
434
+ async delete(key) {
435
+ if (!this.isReady()) this.initialize();
436
+ this.updateLastAccessedAt();
437
+ return this.lockManager.withLock(key, async () => {
438
+ if (this.dispose) {
439
+ const value = this.dataStore.get(key);
440
+ await this.dispose(key, value);
441
+ }
442
+ return this.dataStore.delete(key);
443
+ });
444
+ }
445
+ /**
446
+ * Prunes expired or stale keys from the cache.
447
+ * Also triggers truncation if store exceeds threshold.
448
+ * @returns Stats on pruning if any keys were removed.
449
+ */
450
+ async pruneExpiredKeys() {
451
+ const dataStoreSize = this.dataStore.size;
452
+ const shouldTruncate = dataStoreSize >= this.truncateThreshold;
453
+ if (dataStoreSize <= 0) return;
454
+ const asyncDeletions = [];
455
+ let index = 0;
456
+ this.dataStore.forEach(async (value, key) => {
457
+ const currentTime = Date.now();
458
+ const expiresAt = this.expiryMap.get(key) ?? 0;
459
+ const truncateLimit = this.truncateThreshold * this.truncationPercentage / ONE_HUNDRED;
460
+ if (expiresAt <= currentTime || shouldTruncate && index >= 0 && index <= truncateLimit) asyncDeletions.push(this.lockManager.withLock(key, async () => {
461
+ if (this.dispose) await this.dispose(key, value);
462
+ return this.dataStore.delete(key);
463
+ }));
464
+ index++;
465
+ });
466
+ await Promise.all(asyncDeletions);
467
+ const prunedDataStoreSize = this.dataStore.size;
468
+ return {
469
+ dataStoreSize,
470
+ prunedDataStoreSize
471
+ };
472
+ }
473
+ startEvictionTask() {
474
+ if (notMissing(this.evictionInterval)) return;
475
+ const executePrune = async () => {
476
+ let result;
477
+ try {
478
+ const instanceDataExpiry = this.lastAccessedAt + this.staleDataThreshold;
479
+ const now = Date.now();
480
+ if (instanceDataExpiry < now) {
481
+ this.logger?.warning({
482
+ message: `Closing the ${this.instantiator}'s MemoryStore instance - received no requests for a while.`,
483
+ category: "MemoryStore"
484
+ });
485
+ this.close();
486
+ return;
487
+ }
488
+ result = await this.pruneExpiredKeys();
489
+ this.evictionInterval = setTimeout(executePrune, this.evictionFrequency);
490
+ } catch (error) {
491
+ if (error instanceof Error) this.logger?.error({
492
+ message: "Error during pruning expired keys:",
493
+ category: "MemoryStore",
494
+ error,
495
+ code: MemoryStoreErrorCodes.MemoryStorePruneError
496
+ });
497
+ this.evictionInterval = setTimeout(executePrune, this.evictionFrequency);
498
+ } finally {
499
+ if (notMissing(result?.dataStoreSize) && notMissing(result?.prunedDataStoreSize)) {
500
+ const { dataStoreSize, prunedDataStoreSize } = result;
501
+ const prunedKeysTotal = dataStoreSize - prunedDataStoreSize;
502
+ this.logger?.debug({
503
+ message: `Pruned ${prunedKeysTotal} expired keys, ${prunedDataStoreSize} remain, scheduling next prune.`,
504
+ category: "MemoryStore",
505
+ context: { instantiator: this.instantiator }
506
+ });
507
+ }
508
+ }
509
+ };
510
+ this.evictionInterval = setTimeout(executePrune, this.evictionFrequency);
511
+ }
512
+ stopEvictionTask() {
513
+ if (this.evictionInterval) {
514
+ clearTimeout(this.evictionInterval);
515
+ this.evictionInterval = void 0;
516
+ }
517
+ }
518
+ updateLastAccessedAt() {
519
+ this.lastAccessedAt = Date.now();
520
+ }
521
+ /**
522
+ * Checks if the store is initialized and ready for use.
523
+ * @returns `true` if ready, `false` otherwise.
524
+ */
525
+ isReady() {
526
+ return notMissing(this.evictionInterval) && notMissing(this.dataStore) && notMissing(this.expiryMap) && notMissing(this.lockManager);
527
+ }
528
+ /**
529
+ * Stops eviction and clears all data.
530
+ */
531
+ close() {
532
+ this.stopEvictionTask();
533
+ this.dataStore.clear();
534
+ this.expiryMap.clear();
535
+ this.lockManager.close();
536
+ }
537
+ /**
538
+ * Lists keys and their data, with pagination.
539
+ * @param partialKey Match keys containing this string.
540
+ * @param cursor Optional offset for pagination.
541
+ * @param limit Max number of items to return.
542
+ * @returns Matching items and next cursor (if any).
543
+ */
544
+ async listData({ partialKey, cursor, limit }) {
545
+ const mapKeys = Array.from(this.dataStore.keys());
546
+ const matchingKeys = mapKeys.filter((key) => key.includes(partialKey));
547
+ const matches = [];
548
+ const parsedCursor = cursor ? parseInt(cursor, 10) : 0;
549
+ for (let i = parsedCursor; i < limit + parsedCursor; i++) {
550
+ const key = matchingKeys[i];
551
+ if (!key) break;
552
+ const data = await this.getData(key);
553
+ if (data) matches.push(data);
554
+ if (matches.length >= limit) break;
555
+ }
556
+ return {
557
+ items: matches,
558
+ cursor: matches.length < limit ? void 0 : (parsedCursor + limit).toString()
559
+ };
560
+ }
561
+ };
562
+
563
+ //#endregion
564
+ //#region src/instanceManager/constants.ts
565
+ const DEFAULT_INSTANCE_TTL = 300;
566
+
567
+ //#endregion
568
+ //#region src/instanceManager/index.ts
569
+ /**
570
+ * Singleton cache manager that wraps a `MemoryStore` and ensures only one instance is used across the app.
571
+ * Primarily used for simple key-value caching with TTL and optional logger support.
572
+ */
573
+ var InstanceManager = class InstanceManager {
574
+ static instance = null;
575
+ static logger = null;
576
+ dataStore = null;
577
+ constructor(logger) {
578
+ this.initialize(logger);
579
+ }
580
+ /**
581
+ * Returns the singleton instance of the InstanceManager.
582
+ * If it hasn't been created yet, it initializes it with optional logger.
583
+ *
584
+ * @param logger Optional logger instance.
585
+ * @returns The shared InstanceManager instance.
586
+ */
587
+ static getInstance(logger) {
588
+ if (isMissing(InstanceManager.instance)) InstanceManager.instance = new InstanceManager(logger);
589
+ return InstanceManager.instance;
590
+ }
591
+ /**
592
+ * Initializes the internal MemoryStore with optional logger.
593
+ *
594
+ * @param logger Optional logger for debug/info messages.
595
+ */
596
+ initialize(logger) {
597
+ this.dataStore = new MemoryStore({
598
+ instantiator: InstanceManager.name,
599
+ logger,
600
+ typeGuard: (data) => notMissing(data)
601
+ });
602
+ InstanceManager.logger = logger ?? null;
603
+ InstanceManager.logger?.info({
604
+ category: InstanceManager.name,
605
+ message: "InstanceManager initialized."
606
+ });
607
+ }
608
+ /**
609
+ * Retrieves a value from the cache.
610
+ *
611
+ * @param key The key to fetch.
612
+ * @returns The cached value, or `null` if not found.
613
+ */
614
+ async get(key) {
615
+ this.ensureReady();
616
+ return await InstanceManager.getInstance().dataStore?.getData(key) ?? null;
617
+ }
618
+ /**
619
+ * Stores a value in the cache with optional TTL.
620
+ *
621
+ * @param key The key to associate with the value.
622
+ * @param value The value to store.
623
+ * @param cacheTTL Optional TTL in seconds (defaults to `DEFAULT_INSTANCE_TTL`).
624
+ * @returns `true` if successfully stored, `false` otherwise.
625
+ */
626
+ async set(key, value, cacheTTL) {
627
+ this.ensureReady();
628
+ return await InstanceManager.getInstance().dataStore?.setData({
629
+ key,
630
+ value,
631
+ cacheTTL: notMissing(cacheTTL) ? cacheTTL : DEFAULT_INSTANCE_TTL
632
+ }) ?? false;
633
+ }
634
+ /**
635
+ * Ensures the store is initialized. If not, reinitializes it.
636
+ */
637
+ ensureReady() {
638
+ if (!this.isReady()) this.initialize();
639
+ }
640
+ /**
641
+ * Checks if the cache is initialized and ready.
642
+ *
643
+ * @returns `true` if ready, `false` otherwise.
644
+ */
645
+ isReady() {
646
+ return notMissing(this?.dataStore);
647
+ }
648
+ /**
649
+ * Clears the current instance and internal store.
650
+ */
651
+ close() {
652
+ InstanceManager.instance = null;
653
+ this.dataStore = null;
654
+ }
655
+ };
656
+ /**
657
+ * Singleton export for convenient shared access.
658
+ */
659
+ const instanceManager = InstanceManager.getInstance();
660
+
661
+ //#endregion
662
+ //#region src/parsers/requestParameters.ts
663
+ const parseRequestParameters = (parameters) => {
664
+ return parameters.reduce((acc, param) => {
665
+ if (Array.isArray(acc[param.in][param.name])) acc[param.in][param.name] = acc[param.in][param.name].concat(param.value);
666
+ else if (typeof acc[param.in][param.name] === "object" && typeof param.value === "object") acc[param.in][param.name] = {
667
+ ...acc[param.in][param.name],
668
+ ...param.value
669
+ };
670
+ else acc[param.in][param.name] = param.in === "body" ? param.value : String(param.value);
671
+ return acc;
672
+ }, {
673
+ query: {},
674
+ body: {},
675
+ headers: {}
676
+ });
677
+ };
678
+
679
+ //#endregion
680
+ //#region src/parsers/types.ts
681
+ const RequestParameterLocations = [
682
+ "query",
683
+ "body",
684
+ "headers"
685
+ ];
686
+
687
+ //#endregion
688
+ //#region src/customErrors/index.ts
689
+ const translateCustomError = (response, configs) => {
690
+ if (isMissing(configs) || configs?.length === 0 || isMissing(response)) return {
691
+ data: response.data,
692
+ status: response.status,
693
+ headers: response.headers,
694
+ requestUrl: response.requestUrl,
695
+ responseType: response.responseType,
696
+ responseTime: response.responseTime,
697
+ message: response.message
698
+ };
699
+ const customErrors = configs?.filter((errorConfig) => errorConfig.receivedStatus === response.status);
700
+ if (customErrors?.length) for (const customError of customErrors) {
701
+ const conditionExpression = customError.condition;
702
+ const isConditionMet = safeEvaluate(conditionExpression, response) === true;
703
+ if (isMissing(conditionExpression) || isConditionMet) return {
704
+ data: response.data,
705
+ headers: response.headers,
706
+ requestUrl: response.requestUrl,
707
+ responseType: response.responseType,
708
+ responseTime: response.responseTime,
709
+ status: customError.targetStatus,
710
+ message: customError?.message ?? HttpErrorMessages[customError.targetStatus] ?? "Unknown error"
711
+ };
712
+ }
713
+ return response;
714
+ };
715
+
716
+ //#endregion
717
+ //#region src/validators/statusCodes.ts
718
+ const isSuccessStatusCode = (status) => notMissing(status) && status >= 200 && status <= 299;
719
+ const isFailedStatusCode = (status) => notMissing(status) && status >= 400 && status <= 599;
720
+ const isInfoStatusCode = (status) => notMissing(status) && status >= 100 && status <= 199;
721
+
722
+ //#endregion
723
+ //#region src/requestClient/restClient.ts
724
+ var RestClient = class {
725
+ async performRequest({ httpClient, url, method, headers, body, customErrorConfigs }) {
726
+ let response;
727
+ try {
728
+ response = await httpClient?.request({
729
+ method,
730
+ url,
731
+ headers,
732
+ maxRedirects: 0,
733
+ payload: body
734
+ });
735
+ } catch (error) {
736
+ if (isMissing(error?.response)) throw error;
737
+ else response = error.response;
738
+ }
739
+ const translatedResponse = translateCustomError(response, customErrorConfigs);
740
+ if (isFailedStatusCode(translatedResponse?.status)) throw new HttpResponseError(translatedResponse, translatedResponse.message);
741
+ else return translatedResponse;
742
+ }
743
+ };
744
+
745
+ //#endregion
746
+ //#region src/requestClient/requestClientFactory.ts
747
+ var RequestClientFactory = class {
748
+ static build(type = "rest") {
749
+ if (type === "rest") return new RestClient();
750
+ else throw new Error(`Unknown request client type: ${type}`);
751
+ }
752
+ };
753
+
754
+ //#endregion
755
+ export { CUSTOM_ERROR_CONFIG_SCHEMA, HttpClient, HttpClientManager, HttpErrorMessages, HttpMethods, HttpResponseError, InstanceManager, LockManager, MemoryStore, RequestClientFactory, RequestParameterLocations, createAuthorizationHeaders, isFailedStatusCode, isInfoStatusCode, isSuccessStatusCode, parseRequestParameters };