@oapiex/sdk-kit 0.1.9 → 0.1.11

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,1159 @@
1
+ import "dotenv/config";
2
+ import axios from "axios";
3
+ import path from "path";
4
+ import { createJiti } from "jiti";
5
+ import path$1 from "node:path";
6
+ import crypto from "crypto";
7
+ import { Logger } from "@h3ravel/shared";
8
+
9
+ //#region src/utilities/global.ts
10
+ String.prototype.toKebabCase = function() {
11
+ return this.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
12
+ };
13
+ String.prototype.toCamelCase = function() {
14
+ return this.replace(/[-_ ]+([a-zA-Z0-9])/g, (_, c) => c.toUpperCase()).replace(/^[A-Z]/, (c) => c.toLowerCase());
15
+ };
16
+ String.prototype.toPascalCase = function() {
17
+ return this.replace(/(^\w|[-_ ]+\w)/g, (match) => match.replace(/[-_ ]+/, "").toUpperCase());
18
+ };
19
+ String.prototype.toSnakeCase = function() {
20
+ return this.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toLowerCase();
21
+ };
22
+ String.prototype.toSlug = function(separator = "-") {
23
+ return this.toSnakeCase().replace(/[/__:]+/g, "_").replace(/_/g, separator);
24
+ };
25
+ String.prototype.toTitleCase = function() {
26
+ return this.toLowerCase().replace(/(^|\s)\w/g, (match) => match.toUpperCase());
27
+ };
28
+ String.prototype.toCleanCase = function() {
29
+ return this.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\s+/g, " ").split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ").trim().replace(/\b\w{1,3}\b/g, (match) => match.toUpperCase());
30
+ };
31
+ String.prototype.truncate = function(n, suffix = "…") {
32
+ return this.length > n ? this.slice(0, n - 1) + suffix : this.toString();
33
+ };
34
+
35
+ //#endregion
36
+ //#region src/utilities/helpers.ts
37
+ /**
38
+ * Takes a value like the one prodvided in a destructuring assignment where for instance
39
+ * a single value might have been provided as opposed to an array of values and
40
+ * normalizes it.
41
+ *
42
+ * Examples:
43
+ * Definition:
44
+ * (...value: string[]) => normalizeValue(value)
45
+ *
46
+ * Usage:
47
+ * normalizeValue('singleValue') // returns ['singleValue']
48
+ * normalizeValue('value1', 'value2') // returns ['value1', 'value2']
49
+ * normalizeValue(['value1', 'value2']) // returns ['value1', 'value2']
50
+ *
51
+ * @param value
52
+ * @returns
53
+ */
54
+ const normalizeValue = (value) => {
55
+ if (Array.isArray(value)) return value;
56
+ return [value];
57
+ };
58
+ /**
59
+ * Builds a full url based on endpoint provided
60
+ *
61
+ * @param baseUrl
62
+ * @param endpoint
63
+ *
64
+ * @returns
65
+ */
66
+ const buildUrl = (baseUrl, ...endpoint) => {
67
+ return (baseUrl + path.normalize(path.join(...normalizeValue(endpoint)))).replace(/([^:]\/)\/+/g, "$1");
68
+ };
69
+
70
+ //#endregion
71
+ //#region src/config.ts
72
+ const defaultConfig = {
73
+ environment: "sandbox",
74
+ urls: {
75
+ live: "",
76
+ sandbox: ""
77
+ },
78
+ headers: {},
79
+ debugLevel: 0
80
+ };
81
+ const createDefaultConfig = () => ({
82
+ ...defaultConfig,
83
+ urls: { ...defaultConfig.urls ?? {} },
84
+ headers: { ...defaultConfig.headers ?? {} }
85
+ });
86
+ const mergeConfig = (baseConfig, config) => ({
87
+ ...baseConfig,
88
+ ...config,
89
+ urls: config.urls ? {
90
+ ...baseConfig.urls ?? defaultConfig.urls,
91
+ ...config.urls
92
+ } : baseConfig.urls,
93
+ headers: config.headers ? {
94
+ ...baseConfig.headers ?? defaultConfig.headers,
95
+ ...config.headers
96
+ } : baseConfig.headers
97
+ });
98
+ const defineConfig = (config) => config;
99
+
100
+ //#endregion
101
+ //#region src/utilities/Manager.ts
102
+ const CONFIG_BASENAMES = [
103
+ "oapiex.config.ts",
104
+ "oapiex.config.js",
105
+ "oapiex.config.cjs"
106
+ ];
107
+ let globalConfig = createDefaultConfig();
108
+ let loadedConfigRoot = null;
109
+ const pickInitOptions = (value) => {
110
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
111
+ const config = value;
112
+ const scoped = config.sdkKit ?? config.sdk;
113
+ if (scoped && typeof scoped === "object" && !Array.isArray(scoped)) return pickInitOptions(scoped);
114
+ const nextConfig = [
115
+ "clientId",
116
+ "clientSecret",
117
+ "encryptionKey",
118
+ "environment",
119
+ "urls",
120
+ "headers",
121
+ "timeout",
122
+ "auth",
123
+ "debugLevel"
124
+ ].reduce((result, key) => {
125
+ if (config[key] !== void 0) result[key] = config[key];
126
+ return result;
127
+ }, {});
128
+ return Object.keys(nextConfig).length > 0 ? nextConfig : null;
129
+ };
130
+ const loadUserConfig = (rootDir = process.cwd()) => {
131
+ const syncLoad = createJiti(import.meta.url, {
132
+ interopDefault: true,
133
+ moduleCache: false,
134
+ fsCache: false
135
+ });
136
+ for (const basename of CONFIG_BASENAMES) {
137
+ const configPath = path$1.join(rootDir, basename);
138
+ try {
139
+ const loaded = syncLoad(configPath);
140
+ const loadedModule = typeof loaded === "object" && loaded !== null ? loaded : null;
141
+ const sdkConfig = pickInitOptions(loadedModule?.default ?? loadedModule?.config ?? loaded);
142
+ if (sdkConfig) return sdkConfig;
143
+ } catch {
144
+ continue;
145
+ }
146
+ }
147
+ return null;
148
+ };
149
+ const ensureConfigLoaded = (rootDir = process.cwd()) => {
150
+ if (loadedConfigRoot === rootDir) return globalConfig;
151
+ globalConfig = createDefaultConfig();
152
+ loadedConfigRoot = rootDir;
153
+ const userConfig = loadUserConfig(rootDir);
154
+ if (userConfig) globalConfig = mergeConfig(globalConfig, userConfig);
155
+ return globalConfig;
156
+ };
157
+ const updateConfig = (config) => {
158
+ const userConfig = mergeConfig(ensureConfigLoaded(), config);
159
+ globalConfig = userConfig;
160
+ return userConfig;
161
+ };
162
+ const getConfig = () => ensureConfigLoaded();
163
+ const resetConfig = () => {
164
+ loadedConfigRoot = null;
165
+ globalConfig = createDefaultConfig();
166
+ return globalConfig;
167
+ };
168
+
169
+ //#endregion
170
+ //#region src/Builder.ts
171
+ var Builder = class {
172
+ static baseUrls = {
173
+ live: "",
174
+ sandbox: ""
175
+ };
176
+ /**
177
+ * API Environment
178
+ */
179
+ static environment;
180
+ constructor() {}
181
+ /**
182
+ * Sets the environment for the builder
183
+ *
184
+ * @param env
185
+ */
186
+ static setEnvironment(env) {
187
+ this.environment = env;
188
+ }
189
+ /**
190
+ * Gets the base url based on environment
191
+ *
192
+ * @returns
193
+ */
194
+ static baseUrl() {
195
+ const config = getConfig();
196
+ const env = this.environment ?? config.environment ?? this.normalizeEnvironment(process.env.ENVIRONMENT) ?? "sandbox";
197
+ const configuredUrl = config.urls?.[env];
198
+ if (configuredUrl) return this.normalizeBaseUrl(configuredUrl);
199
+ return this.normalizeBaseUrl(this.baseUrls[env]);
200
+ }
201
+ /**
202
+ * Builds a full url based on endpoint provided
203
+ *
204
+ * @param endpoint
205
+ * @returns
206
+ */
207
+ static buildUrl(...endpoint) {
208
+ return buildUrl(this.baseUrl(), ...endpoint);
209
+ }
210
+ /**
211
+ * Builds parameters for query or path
212
+ *
213
+ * @param params
214
+ * @param type
215
+ * @returns
216
+ */
217
+ static buildParams(params, type = "path") {
218
+ let built = "";
219
+ if (type === "path") built = Object.values(params).join("/");
220
+ else {
221
+ const queryParams = [];
222
+ for (const [key, value] of Object.entries(params)) queryParams.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
223
+ built = queryParams.join("&");
224
+ }
225
+ return built;
226
+ }
227
+ /**
228
+ * Assigns parameters to url {placeholders} based on type
229
+ *
230
+ * @param url
231
+ * @param params
232
+ * @param type
233
+ *
234
+ * @returns
235
+ */
236
+ static assignParamsToUrl(url, params, type = "path") {
237
+ let builtUrl = url;
238
+ if (type === "path") for (const [key, value] of Object.entries(params)) {
239
+ builtUrl = builtUrl.replace(`{${key}}`, encodeURIComponent(String(value)));
240
+ builtUrl = builtUrl.replace(`:${key}`, encodeURIComponent(String(value)));
241
+ }
242
+ else {
243
+ if (Object.keys(params).length === 0) return builtUrl;
244
+ const queryString = this.buildParams(params, "query");
245
+ const separator = builtUrl.includes("?") ? "&" : "?";
246
+ builtUrl += `${separator}${queryString}`;
247
+ }
248
+ return builtUrl;
249
+ }
250
+ /**
251
+ * Builds the target url by assigning both path and query parameters
252
+ *
253
+ * @param path
254
+ * @param params
255
+ * @param queryParams
256
+ * @returns
257
+ */
258
+ static buildTargetUrl(path$2, params = {}, queryParams = {}) {
259
+ const url = this.buildUrl(path$2);
260
+ let builtUrl = this.assignParamsToUrl(url, params, "path");
261
+ builtUrl = this.assignParamsToUrl(builtUrl, queryParams, "query");
262
+ return builtUrl;
263
+ }
264
+ /**
265
+ * Encrypts specified keys in the input object and returns a new object with
266
+ * encrypted values.
267
+ *
268
+ * @param input The input object containing the data to be encrypted.
269
+ * @param keysToEncrypt The keys in the input object that should be encrypted.
270
+ * @param outputMapping A mapping of input keys to output keys for the encrypted values.
271
+ * @returns A new object with the specified keys encrypted.
272
+ */
273
+ static async encryptDetails(input, keysToEncrypt = [], outputMapping = {}) {
274
+ const encryptionKey = getConfig().encryptionKey ?? process.env.ENCRYPTION_KEY;
275
+ if (!encryptionKey) throw new Error("Encryption key is required to encrypt details");
276
+ const nonce = crypto.randomBytes(12).toString("base64").slice(0, 12);
277
+ const encryptableKeys = keysToEncrypt.length > 0 ? keysToEncrypt : Object.keys(input);
278
+ const encrypted = Object.fromEntries(Object.entries(input).map(([key, value]) => {
279
+ if (encryptableKeys.includes(key) && typeof value === "string") return [outputMapping?.[key] || key, this.encryptAES(value, encryptionKey, nonce)];
280
+ return [key, value];
281
+ }));
282
+ for (const key of encryptableKeys) delete input[key];
283
+ return encrypted;
284
+ }
285
+ /**
286
+ * Encrypts data using AES-GCM encryption
287
+ * @param data
288
+ * @param token
289
+ * @param nonce
290
+ * @returns
291
+ */
292
+ static async encryptAES(data, token, nonce) {
293
+ if (nonce.length !== 12) throw new Error("Nonce must be exactly 12 characters long");
294
+ const cryptoSubtle = globalThis.crypto?.subtle || crypto.webcrypto?.subtle;
295
+ if (!cryptoSubtle) throw new Error("Crypto API is not available in this environment.");
296
+ const decodedKeyBytes = Uint8Array.from(atob(token), (c) => c.charCodeAt(0));
297
+ const key = await cryptoSubtle.importKey("raw", decodedKeyBytes, { name: "AES-GCM" }, false, ["encrypt"]);
298
+ const iv = new TextEncoder().encode(nonce);
299
+ const encryptedData = await cryptoSubtle.encrypt({
300
+ name: "AES-GCM",
301
+ iv
302
+ }, key, new TextEncoder().encode(data));
303
+ return btoa(String.fromCharCode(...new Uint8Array(encryptedData)));
304
+ }
305
+ static normalizeBaseUrl = (url) => url.endsWith("/") ? url : `${url}/`;
306
+ static normalizeEnvironment = (value) => {
307
+ if (value === "live" || value === "sandbox") return value;
308
+ };
309
+ };
310
+
311
+ //#endregion
312
+ //#region src/Exceptions/BadRequestException.ts
313
+ var BadRequestException = class extends Error {
314
+ statusCode = 400;
315
+ type = "INVALID_REQUEST";
316
+ code = "400";
317
+ data;
318
+ constructor(data) {
319
+ super(data?.error.message ?? data?.message ?? "Bad request. The server could not understand the request due to invalid syntax.");
320
+ if (data?.error.code) this.type = data.error.type;
321
+ if (data?.error.code) this.code = data.error.code;
322
+ this.data = data ?? {
323
+ status: "failed",
324
+ error: {
325
+ type: this.type,
326
+ code: this.code,
327
+ message: this.message,
328
+ validation_errors: []
329
+ }
330
+ };
331
+ this.name = "BadRequestException";
332
+ }
333
+ };
334
+
335
+ //#endregion
336
+ //#region src/Exceptions/ForbiddenRequestException.ts
337
+ var ForbiddenRequestException = class extends Error {
338
+ statusCode = 403;
339
+ type = "FORBIDDEN";
340
+ code = "403";
341
+ data;
342
+ constructor(data) {
343
+ super(data?.error.message ?? data?.message ?? "Forbidden request. You do not have permission to access this resource.");
344
+ if (data?.error.code) this.type = data.error.type;
345
+ if (data?.error.code) this.code = data.error.code;
346
+ this.data = data ?? {
347
+ status: "failed",
348
+ error: {
349
+ type: this.type,
350
+ code: this.code,
351
+ message: this.message
352
+ }
353
+ };
354
+ this.name = "ForbiddenRequestException";
355
+ }
356
+ };
357
+
358
+ //#endregion
359
+ //#region src/Exceptions/UnauthorizedRequestException.ts
360
+ var UnauthorizedRequestException = class extends Error {
361
+ statusCode = 401;
362
+ type = "UNAUTHORIZED";
363
+ code = "401";
364
+ data;
365
+ constructor(data) {
366
+ super(data?.error.message ?? data?.message ?? "Forbidden request. You do not have permission to access this resource.");
367
+ if (data?.error.code) this.type = data.error.type;
368
+ if (data?.error.code) this.code = data.error.code;
369
+ this.data = data ?? {
370
+ status: "failed",
371
+ error: {
372
+ type: this.type,
373
+ code: this.code,
374
+ message: this.message
375
+ }
376
+ };
377
+ this.name = "UnauthorizedRequestException";
378
+ }
379
+ };
380
+
381
+ //#endregion
382
+ //#region src/Exceptions/HttpException.ts
383
+ var HttpException = class HttpException extends Error {
384
+ statusCode = 500;
385
+ parent;
386
+ constructor(data, statusCode, parent) {
387
+ super(data.message);
388
+ this.data = data;
389
+ this.name = "HttpException";
390
+ this.parent = parent;
391
+ if (statusCode) this.statusCode = statusCode;
392
+ }
393
+ /**
394
+ * Create an exception from status code
395
+ *
396
+ * @param code
397
+ * @param data
398
+ */
399
+ static fromCode(code, data, parent) {
400
+ switch (code) {
401
+ case 400: return new BadRequestException(data);
402
+ case 401: return new UnauthorizedRequestException(data);
403
+ case 403: return new ForbiddenRequestException(data);
404
+ default: return new HttpException(data, code, parent);
405
+ }
406
+ }
407
+ };
408
+
409
+ //#endregion
410
+ //#region src/utilities/Log.ts
411
+ var Logger$1 = class Logger$1 {
412
+ /**
413
+ * Render a log message with colors based on the type of each value
414
+ *
415
+ * @param message
416
+ */
417
+ static run(...message) {
418
+ console.log(message.map((m) => typeof m === "object" ? new Logger$1().build(m, message.length === 1 ? 0 : 2) : m).join("\n"));
419
+ }
420
+ /**
421
+ * Recursively build a log string with colors based on the type of each value
422
+ *
423
+ * @param obj
424
+ * @param indent
425
+ * @returns
426
+ */
427
+ build = (obj, indent = 0) => {
428
+ const indentation = " ".repeat(indent);
429
+ let str = "";
430
+ for (const key in obj) {
431
+ const value = obj[key];
432
+ if (typeof value === "object" && value !== null) {
433
+ str += `${indentation}${this.stringFormatter(key)}:`;
434
+ str += `\n${this.build(value, indent + 2)}\n`;
435
+ } else {
436
+ let coloredValue;
437
+ switch (typeof value) {
438
+ case "string":
439
+ coloredValue = Logger.log(value, "green", false);
440
+ break;
441
+ case "number":
442
+ coloredValue = Logger.log(String(value), "yellow", false);
443
+ break;
444
+ case "boolean":
445
+ coloredValue = Logger.log(String(value), "blue", false);
446
+ break;
447
+ case "object":
448
+ if (value === null) coloredValue = Logger.log("null", "gray", false);
449
+ else coloredValue = Logger.log(JSON.stringify(value), "cyan", false);
450
+ break;
451
+ default: coloredValue = value;
452
+ }
453
+ str += `${indentation}${this.stringFormatter(key)}: ${coloredValue}\n`;
454
+ }
455
+ }
456
+ return str;
457
+ };
458
+ /**
459
+ * Format a string to be more human-readable.
460
+ *
461
+ * @param str
462
+ * @returns
463
+ */
464
+ stringFormatter = (str) => {
465
+ return str.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\s+/g, " ").split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ").trim().replace(/^(\w{2})$/, (_, p1) => p1.toUpperCase());
466
+ };
467
+ };
468
+
469
+ //#endregion
470
+ //#region src/Http.ts
471
+ var Http = class Http {
472
+ /**
473
+ * Bearer token
474
+ */
475
+ static bearerToken;
476
+ /**
477
+ * Debug level
478
+ */
479
+ static debugLevel = 0;
480
+ static apiInstance;
481
+ /**
482
+ * Creates an instance of Http.
483
+ *
484
+ * @param method
485
+ * @param url
486
+ * @param headers
487
+ * @param body
488
+ */
489
+ constructor(headers = {}, body) {
490
+ this.headers = headers;
491
+ this.body = body;
492
+ }
493
+ /**
494
+ * Set the debug level
495
+ *
496
+ * @param debug
497
+ */
498
+ static setDebugLevel(level = 0) {
499
+ this.debugLevel = level ?? 0;
500
+ }
501
+ /**
502
+ * Set the API instance
503
+ *
504
+ * @param api
505
+ */
506
+ static setApiInstance(api) {
507
+ this.apiInstance = api;
508
+ }
509
+ /**
510
+ * Set the bearer token
511
+ *
512
+ * @param token
513
+ */
514
+ static setBearerToken(token) {
515
+ this.bearerToken = token;
516
+ updateConfig({ auth: {
517
+ type: "bearer",
518
+ token
519
+ } });
520
+ }
521
+ static setAuth(auth) {
522
+ updateConfig({ auth });
523
+ }
524
+ static setBasicAuth(username, password) {
525
+ this.setAuth({
526
+ type: "basic",
527
+ username,
528
+ password
529
+ });
530
+ }
531
+ static setApiKey(name, value, location = "header", prefix) {
532
+ this.setAuth({
533
+ type: "apiKey",
534
+ name,
535
+ value,
536
+ in: location,
537
+ prefix
538
+ });
539
+ }
540
+ static clearAuth() {
541
+ this.bearerToken = void 0;
542
+ updateConfig({ auth: void 0 });
543
+ }
544
+ setDefaultHeaders(defaults) {
545
+ const config = getConfig();
546
+ this.headers = {
547
+ ...defaults,
548
+ ...config.headers ?? {},
549
+ ...this.headers
550
+ };
551
+ }
552
+ getHeaders() {
553
+ return this.headers;
554
+ }
555
+ getBody() {
556
+ return this.body;
557
+ }
558
+ axiosApi() {
559
+ const config = getConfig();
560
+ this.setDefaultHeaders({
561
+ "Accept": "application/json",
562
+ "Content-Type": "application/json"
563
+ });
564
+ const instance = axios.create({
565
+ baseURL: Builder.baseUrl(),
566
+ headers: this.getHeaders(),
567
+ timeout: config.timeout
568
+ });
569
+ if (Http.debugLevel > 0) {
570
+ instance.interceptors.request.use((request) => {
571
+ Logger$1.run("Starting Request:", {
572
+ url: request.url,
573
+ method: request.method,
574
+ headers: Http.debugLevel < 3 ? Object.fromEntries(Object.entries(request.headers || {}).filter(([key]) => key.toLowerCase() !== "authorization")) : request.headers,
575
+ params: request.params,
576
+ data: request.data
577
+ });
578
+ return request;
579
+ }, (error) => {
580
+ Logger$1.run("Request Error:", error);
581
+ return Promise.reject(error);
582
+ });
583
+ instance.interceptors.response.use((response) => {
584
+ Logger$1.run("Response:", {
585
+ status: response.status,
586
+ data: Http.debugLevel < 3 ? Object.fromEntries(Object.entries(response.data || {}).filter(([key]) => key.toLowerCase() !== "access_token")) : response.data
587
+ });
588
+ return response;
589
+ }, (error) => {
590
+ Logger$1.run("Response Error:", {
591
+ status: error.response?.status,
592
+ data: Object.fromEntries(Object.entries(error.response?.data || {}).filter(([key]) => key.toLowerCase() !== "access_token"))
593
+ });
594
+ return Promise.reject(error);
595
+ });
596
+ }
597
+ return instance;
598
+ }
599
+ /**
600
+ * Makes a GET request
601
+ *
602
+ * @param url
603
+ * @param headers
604
+ * @param params
605
+ * @returns
606
+ */
607
+ static async get(url, params, headers = {}) {
608
+ return this.send(url, "GET", void 0, headers, params);
609
+ }
610
+ /**
611
+ *
612
+ *
613
+ * @param url
614
+ * @param headers
615
+ * @param params
616
+ * @returns
617
+ */
618
+ static async send(url, method, body, headers = {}, params) {
619
+ try {
620
+ const request = await this.prepareRequest({
621
+ url,
622
+ method,
623
+ body,
624
+ headers: this.toHeaderRecord(headers),
625
+ params: { ...params ?? {} }
626
+ });
627
+ const { data } = await new Http(request.headers, request.body).axiosApi()({
628
+ url: request.url,
629
+ method: request.method,
630
+ data: request.body,
631
+ params: request.params
632
+ });
633
+ return {
634
+ success: true,
635
+ message: data.message || "Request successful",
636
+ data: data.data ?? data,
637
+ meta: data.meta
638
+ };
639
+ } catch (e) {
640
+ const error = e.response?.data ?? {};
641
+ throw this.exception(e.response?.status ?? 500, error || e, e);
642
+ }
643
+ }
644
+ static async prepareRequest(request) {
645
+ let prepared = {
646
+ ...request,
647
+ headers: { ...request.headers },
648
+ params: { ...request.params ?? {} }
649
+ };
650
+ for (const auth of this.getConfiguredAuth()) prepared = await this.applyAuth(prepared, auth);
651
+ return prepared;
652
+ }
653
+ static getConfiguredAuth() {
654
+ const configuredAuth = getConfig().auth;
655
+ if (configuredAuth) return Array.isArray(configuredAuth) ? configuredAuth : [configuredAuth];
656
+ if (this.bearerToken) return [{
657
+ type: "bearer",
658
+ token: this.bearerToken
659
+ }];
660
+ return [];
661
+ }
662
+ static async applyAuth(request, auth) {
663
+ switch (auth.type) {
664
+ case "bearer":
665
+ this.setHeaderIfMissing(request.headers, "Authorization", `${auth.prefix ?? "Bearer"} ${auth.token}`.trim());
666
+ return request;
667
+ case "oauth2":
668
+ this.setHeaderIfMissing(request.headers, "Authorization", `${auth.tokenType ?? "Bearer"} ${auth.accessToken}`.trim());
669
+ return request;
670
+ case "basic": {
671
+ const encoded = Buffer.from(`${auth.username}:${auth.password}`).toString("base64");
672
+ this.setHeaderIfMissing(request.headers, "Authorization", `Basic ${encoded}`);
673
+ return request;
674
+ }
675
+ case "apiKey": {
676
+ const value = auth.prefix ? `${auth.prefix} ${auth.value}`.trim() : auth.value;
677
+ const location = auth.in ?? "header";
678
+ if (location === "query") {
679
+ if (request.params[auth.name] === void 0) request.params[auth.name] = value;
680
+ return request;
681
+ }
682
+ if (location === "cookie") {
683
+ this.appendCookie(request.headers, auth.name, value);
684
+ return request;
685
+ }
686
+ this.setHeaderIfMissing(request.headers, auth.name, value);
687
+ return request;
688
+ }
689
+ case "custom": return await auth.apply({
690
+ ...request,
691
+ headers: { ...request.headers },
692
+ params: { ...request.params }
693
+ });
694
+ }
695
+ }
696
+ static setHeaderIfMissing(headers, name, value) {
697
+ if (!Object.keys(headers).find((header) => header.toLowerCase() === name.toLowerCase())) headers[name] = value;
698
+ }
699
+ static appendCookie(headers, name, value) {
700
+ const headerName = Object.keys(headers).find((header) => header.toLowerCase() === "cookie") ?? "Cookie";
701
+ const existingCookie = headers[headerName];
702
+ const cookieEntry = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
703
+ if (!existingCookie) {
704
+ headers[headerName] = cookieEntry;
705
+ return;
706
+ }
707
+ if (!existingCookie.split(";").map((part) => part.trim()).some((part) => part.startsWith(`${encodeURIComponent(name)}=`))) headers[headerName] = `${existingCookie}; ${cookieEntry}`;
708
+ }
709
+ /**
710
+ * Create an HttpException from status and error
711
+ *
712
+ * @param status
713
+ * @param error
714
+ * @returns
715
+ */
716
+ static exception(status, error, originalError) {
717
+ const exception = HttpException.fromCode(status, {
718
+ success: false,
719
+ message: `Request failed: ${error.error?.message || "An error occurred"}`,
720
+ status: "failed",
721
+ data: void 0,
722
+ meta: {},
723
+ error: {
724
+ type: ((typeof error.error === "string" ? error.error : error.error?.type) ?? "UNKNOWN_ERROR").toUpperCase(),
725
+ code: error.error?.code ?? "000000",
726
+ message: error.error?.message ?? error.error_description ?? error.message,
727
+ validation_errors: error.error?.validation_errors ?? []
728
+ }
729
+ }, originalError);
730
+ if (this.apiInstance) this.apiInstance.setLastException(exception);
731
+ return exception;
732
+ }
733
+ static toHeaderRecord = (headers) => {
734
+ return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key, String(value)]));
735
+ };
736
+ };
737
+
738
+ //#endregion
739
+ //#region src/Apis/BaseApi.ts
740
+ var BaseApi = class {
741
+ /**
742
+ * API Core instance for API bootstrapping and shared utilities
743
+ */
744
+ #core;
745
+ lastException;
746
+ /**
747
+ * Create a BaseApi instance
748
+ *
749
+ * @param coreInstance Core instance
750
+ */
751
+ constructor(coreInstance) {
752
+ this.#core = coreInstance;
753
+ }
754
+ /**
755
+ * Get the owning core instance for SDK-specific API bootstrapping.
756
+ */
757
+ get core() {
758
+ return this.#core;
759
+ }
760
+ /**
761
+ * Hook for SDK-specific API registration.
762
+ */
763
+ boot() {}
764
+ /**
765
+ * Set access validator function
766
+ *
767
+ * @param validator
768
+ */
769
+ setAccessValidator(validator) {
770
+ this.#core.setAccessValidator(validator);
771
+ }
772
+ /**
773
+ * Get the last exception
774
+ *
775
+ * @returns
776
+ */
777
+ getLastException() {
778
+ return this.lastException;
779
+ }
780
+ /**
781
+ * Set the last exception
782
+ *
783
+ * @param exception
784
+ */
785
+ setLastException(exception) {
786
+ this.lastException = exception;
787
+ }
788
+ /**
789
+ * Initialize BaseApi and its sub-APIs
790
+ *
791
+ * @param coreInstance Core instance
792
+ * @returns
793
+ */
794
+ static initialize(coreInstance) {
795
+ Http.setDebugLevel(coreInstance.debugLevel);
796
+ const baseApi = new this(coreInstance);
797
+ Http.setApiInstance(baseApi);
798
+ baseApi.boot();
799
+ return baseApi;
800
+ }
801
+ };
802
+
803
+ //#endregion
804
+ //#region src/RuntimeSdk.ts
805
+ const createRuntimeApi = (core, bundle) => {
806
+ const api = BaseApi.initialize(core);
807
+ for (const group of bundle.manifest.groups) {
808
+ const namespace = {};
809
+ for (const operation of group.operations) namespace[operation.methodName] = async (...args) => {
810
+ await core.validateAccess();
811
+ const [pathParams, queryParams, body, headers] = normalizeRuntimeArguments(operation, args);
812
+ validateRequiredArguments(operation.pathParams, pathParams, "path");
813
+ validateRequiredArguments(operation.queryParams, queryParams, "query");
814
+ validateRequiredArguments(operation.headerParams, headers, "header");
815
+ if (operation.hasBody && operation.bodyRequired && body == null) throw new Error(`Missing required request body for ${operation.method} ${operation.path}`);
816
+ const { data } = await Http.send(core.builder.buildTargetUrl(operation.path, pathParams, queryParams), operation.method, body ?? {}, headers);
817
+ return data;
818
+ };
819
+ api[group.propertyName] = namespace;
820
+ }
821
+ return api;
822
+ };
823
+ const createSdk = (bundle, options) => {
824
+ return new Core(options).useDocument(bundle);
825
+ };
826
+ const normalizeRuntimeArguments = (operation, args) => {
827
+ let cursor = 0;
828
+ return [
829
+ operation.pathParams.length > 0 ? toRecord(args[cursor++]) : {},
830
+ operation.queryParams.length > 0 ? toRecord(args[cursor++]) : {},
831
+ operation.hasBody ? args[cursor++] : void 0,
832
+ operation.headerParams.length > 0 ? toRecord(args[cursor++]) : {}
833
+ ];
834
+ };
835
+ const validateRequiredArguments = (parameters, values, location) => {
836
+ for (const parameter of parameters) {
837
+ if (!parameter.required) continue;
838
+ if (values[parameter.name] === void 0 && values[parameter.accessor] === void 0) throw new Error(`Missing required ${location} parameter: ${parameter.name}`);
839
+ }
840
+ };
841
+ const toRecord = (value) => {
842
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) return value;
843
+ return {};
844
+ };
845
+
846
+ //#endregion
847
+ //#region src/Core.ts
848
+ var Core = class Core {
849
+ static apiClass = BaseApi;
850
+ debugLevel = 0;
851
+ /**
852
+ * Client ID
853
+ */
854
+ clientId;
855
+ /**
856
+ * Client Secret
857
+ */
858
+ clientSecret;
859
+ /**
860
+ * API Environment
861
+ */
862
+ environment = "live";
863
+ accessValidator;
864
+ /**
865
+ * API Instance
866
+ */
867
+ api;
868
+ /**
869
+ * Builder Instance
870
+ */
871
+ builder = Builder;
872
+ constructor(clientId, clientSecret, encryptionKey, env, config) {
873
+ const currentConfig = getConfig();
874
+ const resolvedConfig = clientId && typeof clientId === "object" ? this.resolveInitOptionsFromObject(clientId, currentConfig) : this.resolveInitOptionsFromArgs(clientId, clientSecret, encryptionKey, env, config, currentConfig);
875
+ this.clientId = Core.normalizeCredential(resolvedConfig.clientId);
876
+ this.clientSecret = Core.normalizeCredential(resolvedConfig.clientSecret);
877
+ this.environment = resolvedConfig.environment ?? Core.normalizeEnvironment(process.env.ENVIRONMENT) ?? "live";
878
+ this.configure({
879
+ ...resolvedConfig,
880
+ environment: this.environment
881
+ });
882
+ this.debug(resolvedConfig.debugLevel ?? 0);
883
+ if (!this.clientSecret && !this.hasConfiguredAuth()) throw new Error("Client Secret is required to initialize API instance when auth is not provided");
884
+ this.api = this.createApi();
885
+ }
886
+ createApi() {
887
+ return (this.constructor.apiClass ?? BaseApi).initialize(this);
888
+ }
889
+ init(clientId, clientSecret, encryptionKey, env, config) {
890
+ return new this.constructor(clientId, clientSecret, encryptionKey, env, config);
891
+ }
892
+ configure(config) {
893
+ const nextConfig = updateConfig(config);
894
+ if (nextConfig.environment) {
895
+ this.environment = nextConfig.environment;
896
+ this.builder.setEnvironment(nextConfig.environment);
897
+ }
898
+ if ("clientId" in config) this.clientId = Core.normalizeCredential(nextConfig.clientId);
899
+ if ("clientSecret" in config) this.clientSecret = Core.normalizeCredential(nextConfig.clientSecret);
900
+ if (nextConfig.debugLevel !== void 0) this.debug(nextConfig.debugLevel);
901
+ return this;
902
+ }
903
+ /**
904
+ * Set the debug level
905
+ *
906
+ * @param level
907
+ * @returns
908
+ */
909
+ debug(level = 0) {
910
+ this.debugLevel = level;
911
+ Http.setDebugLevel(level);
912
+ return this;
913
+ }
914
+ /**
915
+ * Get the current environment
916
+ *
917
+ * @returns
918
+ */
919
+ getEnvironment() {
920
+ return this.environment;
921
+ }
922
+ /**
923
+ * Get the configured client identifier.
924
+ */
925
+ getClientId() {
926
+ return this.clientId;
927
+ }
928
+ /**
929
+ * Get the configured client secret.
930
+ */
931
+ getClientSecret() {
932
+ return this.clientSecret;
933
+ }
934
+ /**
935
+ * Get the current shared SDK config.
936
+ */
937
+ getConfig() {
938
+ return getConfig();
939
+ }
940
+ /**
941
+ * Replace the active auth strategy.
942
+ */
943
+ setAuth(auth) {
944
+ return this.configure({ auth });
945
+ }
946
+ /**
947
+ * Clear any configured auth strategy.
948
+ */
949
+ clearAuth() {
950
+ Http.clearAuth();
951
+ return this;
952
+ }
953
+ /**
954
+ * Set access validator function
955
+ *
956
+ * @param validator Function to validate access
957
+ */
958
+ setAccessValidator(validator) {
959
+ this.accessValidator = validator;
960
+ return this;
961
+ }
962
+ /**
963
+ * Validates access using the provided access validator function
964
+ *
965
+ * @throws Error if validation fails
966
+ */
967
+ async validateAccess() {
968
+ const check = this.accessValidator ? await this.accessValidator(this) : true;
969
+ if (check === true || check == null) return;
970
+ if (this.isAuthConfigOrArray(check)) {
971
+ this.setAuth(check);
972
+ return;
973
+ }
974
+ if (this.isConfigUpdate(check)) {
975
+ this.configure(check);
976
+ return;
977
+ }
978
+ throw new Error(typeof check === "string" ? check : "Access validation failed");
979
+ }
980
+ /**
981
+ * Use a manifest bundle to create the API instance
982
+ *
983
+ * @param bundle
984
+ * @returns
985
+ */
986
+ useDocument(bundle) {
987
+ this.api = createRuntimeApi(this, bundle);
988
+ return this;
989
+ }
990
+ /**
991
+ * Use a manifest bundle to create the API instance (alias for useDocument).
992
+ *
993
+ * @param bundle
994
+ * @returns
995
+ */
996
+ useSdk(bundle) {
997
+ return this.useDocument(bundle);
998
+ }
999
+ static normalizeEnvironment = (value) => {
1000
+ if (value === "live" || value === "sandbox") return value;
1001
+ };
1002
+ static normalizeCredential(value) {
1003
+ if (typeof value !== "string") return;
1004
+ const normalized = value.trim();
1005
+ return normalized ? normalized : void 0;
1006
+ }
1007
+ resolveInitOptionsFromObject(config, currentConfig) {
1008
+ return {
1009
+ ...currentConfig,
1010
+ ...config,
1011
+ clientId: Core.normalizeCredential(config.clientId) ?? Core.normalizeCredential(currentConfig.clientId) ?? Core.normalizeCredential(process.env.CLIENT_ID),
1012
+ clientSecret: Core.normalizeCredential(config.clientSecret) ?? Core.normalizeCredential(currentConfig.clientSecret) ?? Core.normalizeCredential(process.env.CLIENT_SECRET),
1013
+ environment: config.environment ?? currentConfig.environment ?? Core.normalizeEnvironment(process.env.ENVIRONMENT) ?? "live",
1014
+ encryptionKey: config.encryptionKey ?? currentConfig.encryptionKey,
1015
+ urls: config.urls ?? currentConfig.urls,
1016
+ headers: config.headers ?? currentConfig.headers,
1017
+ timeout: config.timeout ?? currentConfig.timeout,
1018
+ auth: config.auth ?? currentConfig.auth,
1019
+ debugLevel: config.debugLevel ?? currentConfig.debugLevel ?? 0
1020
+ };
1021
+ }
1022
+ resolveInitOptionsFromArgs(clientId, clientSecret, encryptionKey, env, config, currentConfig) {
1023
+ return {
1024
+ ...currentConfig,
1025
+ ...config ?? {},
1026
+ clientId: Core.normalizeCredential(clientId) ?? Core.normalizeCredential(currentConfig.clientId) ?? Core.normalizeCredential(process.env.CLIENT_ID),
1027
+ clientSecret: Core.normalizeCredential(clientSecret) ?? Core.normalizeCredential(currentConfig.clientSecret) ?? Core.normalizeCredential(process.env.CLIENT_SECRET),
1028
+ environment: env ?? config?.environment ?? currentConfig.environment ?? Core.normalizeEnvironment(process.env.ENVIRONMENT) ?? "live",
1029
+ encryptionKey: encryptionKey ?? config?.encryptionKey ?? currentConfig.encryptionKey,
1030
+ urls: config?.urls ?? currentConfig.urls,
1031
+ headers: config?.headers ?? currentConfig.headers,
1032
+ timeout: config?.timeout ?? currentConfig.timeout,
1033
+ auth: config?.auth ?? currentConfig.auth,
1034
+ debugLevel: config?.debugLevel ?? currentConfig.debugLevel ?? 0
1035
+ };
1036
+ }
1037
+ hasConfiguredAuth() {
1038
+ const auth = getConfig().auth;
1039
+ if (Array.isArray(auth)) return auth.length > 0;
1040
+ return auth != null;
1041
+ }
1042
+ isAuthConfigOrArray(value) {
1043
+ if (Array.isArray(value)) return value.every((entry) => this.isAuthConfig(entry));
1044
+ return this.isAuthConfig(value);
1045
+ }
1046
+ isAuthConfig(value) {
1047
+ return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
1048
+ }
1049
+ isConfigUpdate(value) {
1050
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
1051
+ return [
1052
+ "auth",
1053
+ "clientId",
1054
+ "clientSecret",
1055
+ "debugLevel",
1056
+ "environment",
1057
+ "headers",
1058
+ "timeout",
1059
+ "urls",
1060
+ "encryptionKey"
1061
+ ].some((key) => key in value);
1062
+ }
1063
+ };
1064
+
1065
+ //#endregion
1066
+ //#region src/utilities/AuthCache.ts
1067
+ /**
1068
+ * Cache any auth payload returned from a loader until it expires.
1069
+ */
1070
+ const createAuthCache = (loader) => {
1071
+ let cached;
1072
+ return async (core) => {
1073
+ if (cached && !isExpired(cached.expiresAt)) return cached.auth;
1074
+ cached = await loader(core);
1075
+ return cached.auth;
1076
+ };
1077
+ };
1078
+ /**
1079
+ * Cache bearer or oauth-style access tokens returned from a loader until expiry.
1080
+ */
1081
+ const createAccessTokenCache = (loader) => {
1082
+ return createAuthCache(async (core) => {
1083
+ const token = await loader(core);
1084
+ const expiresAt = token.expiresAt ?? (typeof token.expiresInMs === "number" ? Date.now() + token.expiresInMs : typeof token.expiresInSeconds === "number" ? Date.now() + token.expiresInSeconds * 1e3 : void 0);
1085
+ return {
1086
+ auth: {
1087
+ type: "oauth2",
1088
+ accessToken: token.token,
1089
+ tokenType: token.tokenType ?? "Bearer"
1090
+ },
1091
+ expiresAt
1092
+ };
1093
+ });
1094
+ };
1095
+ const isExpired = (expiresAt) => {
1096
+ return typeof expiresAt === "number" && expiresAt <= Date.now();
1097
+ };
1098
+
1099
+ //#endregion
1100
+ //#region src/utilities/WebhookValidator.ts
1101
+ var WebhookValidator = class {
1102
+ algorithm = "sha256";
1103
+ encoding = "base64";
1104
+ /**
1105
+ * @param secretHash
1106
+ * @param options
1107
+ */
1108
+ constructor(secretHash, options) {
1109
+ this.secretHash = secretHash;
1110
+ this.algorithm = options?.hashAlgorithm || "sha256";
1111
+ this.encoding = options?.encoding || "base64";
1112
+ if (!this.secretHash || this.secretHash.length === 0) this.secretHash = process.env.SECRET_HASH;
1113
+ }
1114
+ /**
1115
+ * Validate webhook signature
1116
+ * @param rawBody - Raw request body as string
1117
+ * @param signature - Signature from request header
1118
+ * @returns {boolean} - True if signature is valid
1119
+ */
1120
+ validate(rawBody, signature) {
1121
+ if (!this.secretHash) throw new Error("Secret hash is required for validation");
1122
+ return crypto.createHmac(this.algorithm, this.secretHash).update(rawBody).digest(this.encoding) === signature;
1123
+ }
1124
+ /**
1125
+ * Generate signature for testing/verification
1126
+ * @param rawBody - Raw request body as string
1127
+ * @returns {string} - Generated signature
1128
+ */
1129
+ generateSignature(rawBody) {
1130
+ if (!this.secretHash) throw new Error("Secret hash is required to generate signature");
1131
+ return crypto.createHmac(this.algorithm, this.secretHash).update(rawBody).digest(this.encoding);
1132
+ }
1133
+ /**
1134
+ * Async version of validate (for large payloads)
1135
+ *
1136
+ * @param rawBody
1137
+ * @param signature
1138
+ * @returns {Promise<boolean>}
1139
+ */
1140
+ validateAsync(rawBody, signature) {
1141
+ if (!this.secretHash) throw new Error("Secret hash is required for validation");
1142
+ const hmac = crypto.createHmac(this.algorithm, this.secretHash);
1143
+ hmac.update(rawBody);
1144
+ return hmac.digest(this.encoding) === signature;
1145
+ }
1146
+ /**
1147
+ * Get current configuration
1148
+ */
1149
+ getConfig() {
1150
+ return {
1151
+ algorithm: this.algorithm,
1152
+ encoding: this.encoding,
1153
+ secretHashLength: this.secretHash?.length
1154
+ };
1155
+ }
1156
+ };
1157
+
1158
+ //#endregion
1159
+ export { BadRequestException, BaseApi, Builder, Core, ForbiddenRequestException, Http, HttpException, UnauthorizedRequestException, WebhookValidator, buildUrl, createAccessTokenCache, createAuthCache, createDefaultConfig, createRuntimeApi, createSdk, defaultConfig, defineConfig, getConfig, globalConfig, loadUserConfig, mergeConfig, normalizeValue, resetConfig, updateConfig };