@nvisy/sdk 0.1.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.
- package/CHANGELOG.md +49 -0
- package/CONTRIBUTING.md +72 -0
- package/LICENSE.txt +21 -0
- package/README.md +108 -0
- package/dist/builder-BEUIGfoZ.d.ts +138 -0
- package/dist/builder.d.ts +2 -0
- package/dist/builder.js +424 -0
- package/dist/builder.js.map +1 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +424 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +124 -0
- package/dist/errors.js +201 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +555 -0
- package/dist/index.js.map +1 -0
- package/package.json +92 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
import createClient from 'openapi-fetch';
|
|
2
|
+
|
|
3
|
+
// src/client.ts
|
|
4
|
+
|
|
5
|
+
// src/config.ts
|
|
6
|
+
var ENV_VARS = {
|
|
7
|
+
API_KEY: "NVISY_API_KEY",
|
|
8
|
+
BASE_URL: "NVISY_BASE_URL",
|
|
9
|
+
TIMEOUT: "NVISY_TIMEOUT",
|
|
10
|
+
MAX_RETRIES: "NVISY_MAX_RETRIES"
|
|
11
|
+
};
|
|
12
|
+
var DEFAULTS = {
|
|
13
|
+
baseUrl: "https://api.nvisy.com",
|
|
14
|
+
timeout: 3e4,
|
|
15
|
+
maxRetries: 3,
|
|
16
|
+
headers: {}
|
|
17
|
+
};
|
|
18
|
+
function loadConfigFromEnv() {
|
|
19
|
+
const config = {};
|
|
20
|
+
const apiKey = process.env[ENV_VARS.API_KEY];
|
|
21
|
+
if (apiKey) {
|
|
22
|
+
config.apiKey = apiKey;
|
|
23
|
+
}
|
|
24
|
+
const baseUrl = process.env[ENV_VARS.BASE_URL];
|
|
25
|
+
if (baseUrl) {
|
|
26
|
+
config.baseUrl = baseUrl;
|
|
27
|
+
}
|
|
28
|
+
const timeout = process.env[ENV_VARS.TIMEOUT];
|
|
29
|
+
if (timeout) {
|
|
30
|
+
const timeoutMs = parseInt(timeout, 10);
|
|
31
|
+
if (!Number.isNaN(timeoutMs)) {
|
|
32
|
+
config.timeout = timeoutMs;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const maxRetries = process.env[ENV_VARS.MAX_RETRIES];
|
|
36
|
+
if (maxRetries) {
|
|
37
|
+
const retries = parseInt(maxRetries, 10);
|
|
38
|
+
if (!Number.isNaN(retries)) {
|
|
39
|
+
config.maxRetries = retries;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return config;
|
|
43
|
+
}
|
|
44
|
+
function resolveConfig(userConfig) {
|
|
45
|
+
const envConfig = loadConfigFromEnv();
|
|
46
|
+
const mergedConfig = { ...envConfig, ...userConfig };
|
|
47
|
+
return {
|
|
48
|
+
apiKey: mergedConfig.apiKey || "",
|
|
49
|
+
baseUrl: mergedConfig.baseUrl || DEFAULTS.baseUrl,
|
|
50
|
+
timeout: mergedConfig.timeout ?? DEFAULTS.timeout,
|
|
51
|
+
maxRetries: mergedConfig.maxRetries ?? DEFAULTS.maxRetries,
|
|
52
|
+
headers: { ...DEFAULTS.headers, ...mergedConfig.headers }
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function getEnvironmentVariables() {
|
|
56
|
+
return { ...ENV_VARS };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/errors.ts
|
|
60
|
+
var ClientError = class extends Error {
|
|
61
|
+
name;
|
|
62
|
+
constructor(message) {
|
|
63
|
+
super(message);
|
|
64
|
+
this.name = this.constructor.name;
|
|
65
|
+
if (Error.captureStackTrace) {
|
|
66
|
+
Error.captureStackTrace(this, this.constructor);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Convert error to JSON representation
|
|
71
|
+
*/
|
|
72
|
+
toJSON() {
|
|
73
|
+
return {
|
|
74
|
+
name: this.name,
|
|
75
|
+
message: this.message,
|
|
76
|
+
context: ""
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
var ConfigError = class _ConfigError extends ClientError {
|
|
81
|
+
/** Field that caused the error (for validation errors) */
|
|
82
|
+
field;
|
|
83
|
+
/** Reason why the configuration is invalid */
|
|
84
|
+
reason;
|
|
85
|
+
constructor(message, options) {
|
|
86
|
+
super(message);
|
|
87
|
+
this.field = options?.field;
|
|
88
|
+
this.reason = options?.reason;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Create error for missing API key
|
|
92
|
+
*/
|
|
93
|
+
static missingApiKey() {
|
|
94
|
+
return new _ConfigError("API key is required", {
|
|
95
|
+
field: "apiKey",
|
|
96
|
+
reason: "API key must be provided in configuration"
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Create error for invalid configuration field
|
|
101
|
+
*/
|
|
102
|
+
static invalidField(field, reason) {
|
|
103
|
+
return new _ConfigError(`Invalid configuration for ${field}: ${reason}`, {
|
|
104
|
+
field,
|
|
105
|
+
reason
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Create error for missing required field
|
|
110
|
+
*/
|
|
111
|
+
static missingField(field) {
|
|
112
|
+
return new _ConfigError(`Missing required configuration field: ${field}`, {
|
|
113
|
+
field,
|
|
114
|
+
reason: "This field is required"
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Convert error to JSON representation
|
|
119
|
+
*/
|
|
120
|
+
toJSON() {
|
|
121
|
+
return {
|
|
122
|
+
name: this.name,
|
|
123
|
+
message: this.message,
|
|
124
|
+
context: this.field || this.reason ? `field: ${this.field}, reason: ${this.reason}` : ""
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
var NetworkError = class _NetworkError extends ClientError {
|
|
129
|
+
/** Original error that caused this network error */
|
|
130
|
+
cause;
|
|
131
|
+
constructor(message, cause) {
|
|
132
|
+
super(message);
|
|
133
|
+
this.cause = cause;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Create error for network/connection issues
|
|
137
|
+
*/
|
|
138
|
+
static connection(message, cause) {
|
|
139
|
+
return new _NetworkError(message, cause);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Create error for request timeout
|
|
143
|
+
*/
|
|
144
|
+
static timeout(timeoutMs) {
|
|
145
|
+
return new _NetworkError(`Request timed out after ${timeoutMs}ms`);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Create error for aborted request
|
|
149
|
+
*/
|
|
150
|
+
static aborted() {
|
|
151
|
+
return new _NetworkError("Request was aborted");
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Create error for DNS resolution failure
|
|
155
|
+
*/
|
|
156
|
+
static dnsResolution(hostname) {
|
|
157
|
+
return new _NetworkError(`Failed to resolve hostname: ${hostname}`);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Convert error to JSON representation
|
|
161
|
+
*/
|
|
162
|
+
toJSON() {
|
|
163
|
+
return {
|
|
164
|
+
name: this.name,
|
|
165
|
+
message: this.message,
|
|
166
|
+
context: this.cause ? `cause: ${this.cause.message}` : ""
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
var ApiError = class _ApiError extends ClientError {
|
|
171
|
+
/** Error response from server */
|
|
172
|
+
errorResponse;
|
|
173
|
+
/** HTTP status code */
|
|
174
|
+
statusCode;
|
|
175
|
+
/** Request ID for debugging */
|
|
176
|
+
requestId;
|
|
177
|
+
constructor(message, statusCode, options) {
|
|
178
|
+
super(message);
|
|
179
|
+
this.statusCode = statusCode;
|
|
180
|
+
this.errorResponse = options?.errorResponse;
|
|
181
|
+
this.requestId = options?.requestId;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Create error from HTTP response
|
|
185
|
+
*/
|
|
186
|
+
static fromResponse(response, errorData, requestId) {
|
|
187
|
+
const message = errorData?.message || `HTTP ${response.status}: ${response.statusText}`;
|
|
188
|
+
return new _ApiError(message, response.status, {
|
|
189
|
+
errorResponse: errorData,
|
|
190
|
+
requestId
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Create error for rate limiting
|
|
195
|
+
*/
|
|
196
|
+
static rateLimited(retryAfter, requestId) {
|
|
197
|
+
const message = retryAfter ? `Rate limited. Retry after ${retryAfter} seconds` : "Rate limited";
|
|
198
|
+
return new _ApiError(message, 429, {
|
|
199
|
+
requestId,
|
|
200
|
+
errorResponse: {
|
|
201
|
+
name: "RateLimitError",
|
|
202
|
+
message,
|
|
203
|
+
context: retryAfter ? `retryAfter: ${retryAfter}` : ""
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Check if error is a client error (4xx)
|
|
209
|
+
*/
|
|
210
|
+
isClientError() {
|
|
211
|
+
return this.statusCode ? this.statusCode >= 400 && this.statusCode < 500 : false;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Check if error is a server error (5xx)
|
|
215
|
+
*/
|
|
216
|
+
isServerError() {
|
|
217
|
+
return this.statusCode ? this.statusCode >= 500 : false;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Check if error is retryable based on HTTP status
|
|
221
|
+
*/
|
|
222
|
+
isRetryable() {
|
|
223
|
+
if (!this.statusCode) return false;
|
|
224
|
+
return this.statusCode >= 500 || // Server errors
|
|
225
|
+
this.statusCode === 408 || // Request timeout
|
|
226
|
+
this.statusCode === 429;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Get retry delay in milliseconds (returns null if not retryable)
|
|
230
|
+
*/
|
|
231
|
+
getRetryDelay() {
|
|
232
|
+
if (!this.isRetryable()) return null;
|
|
233
|
+
if (this.statusCode === 429 && this.errorResponse?.context) {
|
|
234
|
+
const match = this.errorResponse.context.match(/retryAfter: (\d+)/);
|
|
235
|
+
if (match) {
|
|
236
|
+
const retryAfter = parseInt(match[1], 10);
|
|
237
|
+
return retryAfter * 1e3;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (this.statusCode >= 500) {
|
|
241
|
+
return 1e3;
|
|
242
|
+
}
|
|
243
|
+
return 1e3;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Convert error to JSON representation
|
|
247
|
+
*/
|
|
248
|
+
toJSON() {
|
|
249
|
+
return {
|
|
250
|
+
name: this.name,
|
|
251
|
+
message: this.message,
|
|
252
|
+
context: `statusCode: ${this.statusCode}${this.requestId ? `, requestId: ${this.requestId}` : ""}${this.errorResponse ? `, errorResponse: ${JSON.stringify(this.errorResponse)}` : ""}`
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// src/client.ts
|
|
258
|
+
var Client = class _Client {
|
|
259
|
+
#config;
|
|
260
|
+
#openApiClient;
|
|
261
|
+
/**
|
|
262
|
+
* Create a new Nvisy client instance
|
|
263
|
+
*/
|
|
264
|
+
constructor(userConfig) {
|
|
265
|
+
try {
|
|
266
|
+
this.#validateConfig(userConfig);
|
|
267
|
+
this.#config = resolveConfig(userConfig);
|
|
268
|
+
} catch (error) {
|
|
269
|
+
if (error instanceof ConfigError) {
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
throw ConfigError.invalidField(
|
|
273
|
+
"config",
|
|
274
|
+
`Configuration error: ${String(error)}`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
this.#openApiClient = createClient({
|
|
278
|
+
baseUrl: this.#config.baseUrl,
|
|
279
|
+
headers: {
|
|
280
|
+
Authorization: `Bearer ${this.#config.apiKey}`,
|
|
281
|
+
"Content-Type": "application/json",
|
|
282
|
+
"User-Agent": this.#buildUserAgent(),
|
|
283
|
+
...this.#config.headers
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Create a new ClientBuilder for fluent configuration
|
|
289
|
+
*/
|
|
290
|
+
static builder() {
|
|
291
|
+
return new ClientBuilder();
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Create a client from environment variables
|
|
295
|
+
*/
|
|
296
|
+
static fromEnvironment() {
|
|
297
|
+
return ClientBuilder.fromEnvironment().build();
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Get the current configuration (readonly copy)
|
|
301
|
+
*/
|
|
302
|
+
getConfig() {
|
|
303
|
+
return Object.freeze({ ...this.#config });
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Get the underlying openapi-fetch client for advanced usage
|
|
307
|
+
*/
|
|
308
|
+
getOpenApiClient() {
|
|
309
|
+
return this.#openApiClient;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Validate configuration by reusing ClientBuilder validation
|
|
313
|
+
*/
|
|
314
|
+
#validateConfig(config) {
|
|
315
|
+
const builder = new ClientBuilder().withApiKey(config.apiKey);
|
|
316
|
+
if (config.baseUrl !== void 0) {
|
|
317
|
+
builder.withBaseUrl(config.baseUrl);
|
|
318
|
+
}
|
|
319
|
+
if (config.timeout !== void 0) {
|
|
320
|
+
builder.withTimeout(config.timeout);
|
|
321
|
+
}
|
|
322
|
+
if (config.maxRetries !== void 0) {
|
|
323
|
+
builder.withMaxRetries(config.maxRetries);
|
|
324
|
+
}
|
|
325
|
+
if (config.headers !== void 0) {
|
|
326
|
+
builder.withHeaders(config.headers);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Build user agent string
|
|
331
|
+
*/
|
|
332
|
+
#buildUserAgent() {
|
|
333
|
+
const sdkVersion = "1.0.0";
|
|
334
|
+
const nodeVersion = process.version;
|
|
335
|
+
const platform = process.platform;
|
|
336
|
+
return `@nvisy/sdk/${sdkVersion} (${platform}; Node.js ${nodeVersion})`;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Create a new client with modified configuration
|
|
340
|
+
*/
|
|
341
|
+
withConfig(configChanges) {
|
|
342
|
+
const newConfig = {
|
|
343
|
+
apiKey: this.#config.apiKey,
|
|
344
|
+
baseUrl: this.#config.baseUrl,
|
|
345
|
+
timeout: this.#config.timeout,
|
|
346
|
+
maxRetries: this.#config.maxRetries,
|
|
347
|
+
headers: this.#config.headers,
|
|
348
|
+
...configChanges
|
|
349
|
+
};
|
|
350
|
+
return new _Client(newConfig);
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Create a new client with additional headers
|
|
354
|
+
*/
|
|
355
|
+
withHeaders(additionalHeaders) {
|
|
356
|
+
return this.withConfig({
|
|
357
|
+
headers: { ...this.#config.headers, ...additionalHeaders }
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Create a new client with a different timeout
|
|
362
|
+
*/
|
|
363
|
+
withTimeout(timeoutMs) {
|
|
364
|
+
return this.withConfig({ timeout: timeoutMs });
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Create a new client with different retry settings
|
|
368
|
+
*/
|
|
369
|
+
withMaxRetries(maxRetries) {
|
|
370
|
+
return this.withConfig({ maxRetries });
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// src/builder.ts
|
|
375
|
+
var RESERVED_HEADERS = ["authorization", "content-type", "user-agent"];
|
|
376
|
+
var ClientBuilder = class _ClientBuilder {
|
|
377
|
+
#config = {};
|
|
378
|
+
/**
|
|
379
|
+
* Create a ClientBuilder instance with an API key
|
|
380
|
+
*/
|
|
381
|
+
static fromApiKey(apiKey) {
|
|
382
|
+
return new _ClientBuilder().withApiKey(apiKey);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Create a ClientBuilder instance from environment variables
|
|
386
|
+
*/
|
|
387
|
+
static fromEnvironment() {
|
|
388
|
+
const envConfig = loadConfigFromEnv();
|
|
389
|
+
if (!envConfig.apiKey) {
|
|
390
|
+
throw ConfigError.missingApiKey();
|
|
391
|
+
}
|
|
392
|
+
const builder = new _ClientBuilder().withApiKey(envConfig.apiKey);
|
|
393
|
+
if (envConfig.baseUrl) {
|
|
394
|
+
builder.withBaseUrl(envConfig.baseUrl);
|
|
395
|
+
}
|
|
396
|
+
if (envConfig.timeout) {
|
|
397
|
+
builder.withTimeout(envConfig.timeout);
|
|
398
|
+
}
|
|
399
|
+
if (envConfig.maxRetries !== void 0) {
|
|
400
|
+
builder.withMaxRetries(envConfig.maxRetries);
|
|
401
|
+
}
|
|
402
|
+
if (envConfig.headers) {
|
|
403
|
+
builder.withHeaders(envConfig.headers);
|
|
404
|
+
}
|
|
405
|
+
return builder;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Set the API key for authentication
|
|
409
|
+
*/
|
|
410
|
+
withApiKey(apiKey) {
|
|
411
|
+
this.#validateString("apiKey", apiKey);
|
|
412
|
+
const trimmedKey = apiKey.trim();
|
|
413
|
+
if (trimmedKey.length < 10) {
|
|
414
|
+
throw ConfigError.invalidField(
|
|
415
|
+
"apiKey",
|
|
416
|
+
"must be at least 10 characters"
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(trimmedKey)) {
|
|
420
|
+
throw ConfigError.invalidField("apiKey", "contains invalid characters");
|
|
421
|
+
}
|
|
422
|
+
this.#config.apiKey = trimmedKey;
|
|
423
|
+
return this;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Set the base URL for the API
|
|
427
|
+
*/
|
|
428
|
+
withBaseUrl(baseUrl) {
|
|
429
|
+
this.#validateString("baseUrl", baseUrl);
|
|
430
|
+
this.#validateUrl(baseUrl);
|
|
431
|
+
this.#config.baseUrl = baseUrl;
|
|
432
|
+
return this;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Set the request timeout in milliseconds
|
|
436
|
+
*/
|
|
437
|
+
withTimeout(timeoutMs) {
|
|
438
|
+
this.#validateInteger("timeout", timeoutMs, 1e3, 3e5);
|
|
439
|
+
this.#config.timeout = timeoutMs;
|
|
440
|
+
return this;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Set the maximum number of retry attempts
|
|
444
|
+
*/
|
|
445
|
+
withMaxRetries(maxRetries) {
|
|
446
|
+
this.#validateInteger("maxRetries", maxRetries, 0, 5);
|
|
447
|
+
this.#config.maxRetries = maxRetries;
|
|
448
|
+
return this;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Add a single custom header (merges with existing headers)
|
|
452
|
+
*/
|
|
453
|
+
withHeader(name, value) {
|
|
454
|
+
this.#validateSingleHeader(name, value);
|
|
455
|
+
if (!this.#config.headers) {
|
|
456
|
+
this.#config.headers = {};
|
|
457
|
+
}
|
|
458
|
+
this.#config.headers[name] = value;
|
|
459
|
+
return this;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Set custom headers (merges with existing headers)
|
|
463
|
+
*/
|
|
464
|
+
withHeaders(headers) {
|
|
465
|
+
if (!headers || typeof headers !== "object" || Array.isArray(headers)) {
|
|
466
|
+
throw ConfigError.invalidField("headers", "must be a valid object");
|
|
467
|
+
}
|
|
468
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
469
|
+
this.withHeader(name, value);
|
|
470
|
+
}
|
|
471
|
+
return this;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Build and return the configured client instance
|
|
475
|
+
*/
|
|
476
|
+
build() {
|
|
477
|
+
if (!this.#config.apiKey) {
|
|
478
|
+
throw ConfigError.missingApiKey();
|
|
479
|
+
}
|
|
480
|
+
return new Client(this.#config);
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Get the current configuration (for debugging/testing)
|
|
484
|
+
*/
|
|
485
|
+
getConfig() {
|
|
486
|
+
return { ...this.#config };
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Validate string field
|
|
490
|
+
*/
|
|
491
|
+
#validateString(fieldName, value) {
|
|
492
|
+
if (!value || typeof value !== "string" || value.trim().length === 0) {
|
|
493
|
+
throw ConfigError.invalidField(fieldName, "must be a non-empty string");
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Validate integer field with range
|
|
498
|
+
*/
|
|
499
|
+
#validateInteger(fieldName, value, min, max) {
|
|
500
|
+
if (!Number.isInteger(value) || value < min) {
|
|
501
|
+
throw ConfigError.invalidField(fieldName, `must be an integer >= ${min}`);
|
|
502
|
+
}
|
|
503
|
+
if (value > max) {
|
|
504
|
+
throw ConfigError.invalidField(fieldName, `must not exceed ${max}`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Validate URL format
|
|
509
|
+
*/
|
|
510
|
+
#validateUrl(baseUrl) {
|
|
511
|
+
let url;
|
|
512
|
+
try {
|
|
513
|
+
url = new URL(baseUrl);
|
|
514
|
+
} catch {
|
|
515
|
+
throw ConfigError.invalidField("baseUrl", "must be a valid URL");
|
|
516
|
+
}
|
|
517
|
+
const allowedProtocols = ["https:", "http:"];
|
|
518
|
+
if (!allowedProtocols.includes(url.protocol)) {
|
|
519
|
+
throw ConfigError.invalidField(
|
|
520
|
+
"baseUrl",
|
|
521
|
+
`protocol must be one of: ${allowedProtocols.join(", ")}`
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Validate single header name and value
|
|
527
|
+
*/
|
|
528
|
+
#validateSingleHeader(name, value) {
|
|
529
|
+
if (!name || typeof name !== "string" || name.trim().length === 0) {
|
|
530
|
+
throw ConfigError.invalidField(
|
|
531
|
+
"header name",
|
|
532
|
+
"must be a non-empty string"
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
if (typeof value !== "string") {
|
|
536
|
+
throw ConfigError.invalidField("header value", "must be a string");
|
|
537
|
+
}
|
|
538
|
+
if (!/^[a-zA-Z0-9!#$%&'*+\-.^_`|~]+$/.test(name)) {
|
|
539
|
+
throw ConfigError.invalidField(
|
|
540
|
+
"header name",
|
|
541
|
+
`invalid header name: ${name}`
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
if (RESERVED_HEADERS.includes(name.toLowerCase())) {
|
|
545
|
+
throw ConfigError.invalidField(
|
|
546
|
+
"header name",
|
|
547
|
+
`header "${name}" is reserved and cannot be overridden`
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
export { ApiError, Client, ClientBuilder, ClientError, ConfigError, NetworkError, getEnvironmentVariables, loadConfigFromEnv, resolveConfig };
|
|
554
|
+
//# sourceMappingURL=index.js.map
|
|
555
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/errors.ts","../src/client.ts","../src/builder.ts"],"names":[],"mappings":";;;;;AAyCA,IAAM,QAAA,GAAW;AAAA,EAChB,OAAA,EAAS,eAAA;AAAA,EACT,QAAA,EAAU,gBAAA;AAAA,EACV,OAAA,EAAS,eAAA;AAAA,EACT,WAAA,EAAa;AACd,CAAA;AAKA,IAAM,QAAA,GAAW;AAAA,EAChB,OAAA,EAAS,uBAAA;AAAA,EACT,OAAA,EAAS,GAAA;AAAA,EACT,UAAA,EAAY,CAAA;AAAA,EACZ,SAAS;AACV,CAAA;AAKO,SAAS,iBAAA,GAA2C;AAC1D,EAAA,MAAM,SAAgC,EAAC;AAEvC,EAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,GAAA,CAAI,QAAA,CAAS,OAAO,CAAA;AAC3C,EAAA,IAAI,MAAA,EAAQ;AACX,IAAA,MAAA,CAAO,MAAA,GAAS,MAAA;AAAA,EACjB;AAEA,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,GAAA,CAAI,QAAA,CAAS,QAAQ,CAAA;AAC7C,EAAA,IAAI,OAAA,EAAS;AACZ,IAAA,MAAA,CAAO,OAAA,GAAU,OAAA;AAAA,EAClB;AAEA,EAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,GAAA,CAAI,QAAA,CAAS,OAAO,CAAA;AAC5C,EAAA,IAAI,OAAA,EAAS;AACZ,IAAA,MAAM,SAAA,GAAY,QAAA,CAAS,OAAA,EAAS,EAAE,CAAA;AACtC,IAAA,IAAI,CAAC,MAAA,CAAO,KAAA,CAAM,SAAS,CAAA,EAAG;AAC7B,MAAA,MAAA,CAAO,OAAA,GAAU,SAAA;AAAA,IAClB;AAAA,EACD;AAEA,EAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,GAAA,CAAI,QAAA,CAAS,WAAW,CAAA;AACnD,EAAA,IAAI,UAAA,EAAY;AACf,IAAA,MAAM,OAAA,GAAU,QAAA,CAAS,UAAA,EAAY,EAAE,CAAA;AACvC,IAAA,IAAI,CAAC,MAAA,CAAO,KAAA,CAAM,OAAO,CAAA,EAAG;AAC3B,MAAA,MAAA,CAAO,UAAA,GAAa,OAAA;AAAA,IACrB;AAAA,EACD;AAEA,EAAA,OAAO,MAAA;AACR;AAKO,SAAS,cAAc,UAAA,EAAgD;AAC7E,EAAA,MAAM,YAAY,iBAAA,EAAkB;AACpC,EAAA,MAAM,YAAA,GAAe,EAAE,GAAG,SAAA,EAAW,GAAG,UAAA,EAAW;AAEnD,EAAA,OAAO;AAAA,IACN,MAAA,EAAQ,aAAa,MAAA,IAAU,EAAA;AAAA,IAC/B,OAAA,EAAS,YAAA,CAAa,OAAA,IAAW,QAAA,CAAS,OAAA;AAAA,IAC1C,OAAA,EAAS,YAAA,CAAa,OAAA,IAAW,QAAA,CAAS,OAAA;AAAA,IAC1C,UAAA,EAAY,YAAA,CAAa,UAAA,IAAc,QAAA,CAAS,UAAA;AAAA,IAChD,SAAS,EAAE,GAAG,SAAS,OAAA,EAAS,GAAG,aAAa,OAAA;AAAQ,GACzD;AACD;AAKO,SAAS,uBAAA,GAAkD;AACjE,EAAA,OAAO,EAAE,GAAG,QAAA,EAAS;AACtB;;;ACnGO,IAAe,WAAA,GAAf,cAAmC,KAAA,CAAM;AAAA,EAC/B,IAAA;AAAA,EAEhB,YAAY,OAAA,EAAiB;AAC5B,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,KAAK,WAAA,CAAY,IAAA;AAG7B,IAAA,IAAI,MAAM,iBAAA,EAAmB;AAC5B,MAAA,KAAA,CAAM,iBAAA,CAAkB,IAAA,EAAM,IAAA,CAAK,WAAW,CAAA;AAAA,IAC/C;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAA,GAAwB;AACvB,IAAA,OAAO;AAAA,MACN,MAAM,IAAA,CAAK,IAAA;AAAA,MACX,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,OAAA,EAAS;AAAA,KACV;AAAA,EACD;AACD;AAKO,IAAM,WAAA,GAAN,MAAM,YAAA,SAAoB,WAAA,CAAY;AAAA;AAAA,EAE5B,KAAA;AAAA;AAAA,EAEA,MAAA;AAAA,EAEhB,WAAA,CACC,SACA,OAAA,EAIC;AACD,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,QAAQ,OAAA,EAAS,KAAA;AACtB,IAAA,IAAA,CAAK,SAAS,OAAA,EAAS,MAAA;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,aAAA,GAA6B;AACnC,IAAA,OAAO,IAAI,aAAY,qBAAA,EAAuB;AAAA,MAC7C,KAAA,EAAO,QAAA;AAAA,MACP,MAAA,EAAQ;AAAA,KACR,CAAA;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,YAAA,CAAa,KAAA,EAAe,MAAA,EAA6B;AAC/D,IAAA,OAAO,IAAI,YAAA,CAAY,CAAA,0BAAA,EAA6B,KAAK,CAAA,EAAA,EAAK,MAAM,CAAA,CAAA,EAAI;AAAA,MACvE,KAAA;AAAA,MACA;AAAA,KACA,CAAA;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,aAAa,KAAA,EAA4B;AAC/C,IAAA,OAAO,IAAI,YAAA,CAAY,CAAA,sCAAA,EAAyC,KAAK,CAAA,CAAA,EAAI;AAAA,MACxE,KAAA;AAAA,MACA,MAAA,EAAQ;AAAA,KACR,CAAA;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAA,GAAwB;AACvB,IAAA,OAAO;AAAA,MACN,MAAM,IAAA,CAAK,IAAA;AAAA,MACX,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,OAAA,EACC,IAAA,CAAK,KAAA,IAAS,IAAA,CAAK,MAAA,GAChB,CAAA,OAAA,EAAU,IAAA,CAAK,KAAK,CAAA,UAAA,EAAa,IAAA,CAAK,MAAM,CAAA,CAAA,GAC5C;AAAA,KACL;AAAA,EACD;AACD;AAKO,IAAM,YAAA,GAAN,MAAM,aAAA,SAAqB,WAAA,CAAY;AAAA;AAAA,EAE7B,KAAA;AAAA,EAEhB,WAAA,CAAY,SAAiB,KAAA,EAAe;AAC3C,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,KAAA,GAAQ,KAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,UAAA,CAAW,OAAA,EAAiB,KAAA,EAA6B;AAC/D,IAAA,OAAO,IAAI,aAAA,CAAa,OAAA,EAAS,KAAK,CAAA;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,QAAQ,SAAA,EAAiC;AAC/C,IAAA,OAAO,IAAI,aAAA,CAAa,CAAA,wBAAA,EAA2B,SAAS,CAAA,EAAA,CAAI,CAAA;AAAA,EACjE;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,OAAA,GAAwB;AAC9B,IAAA,OAAO,IAAI,cAAa,qBAAqB,CAAA;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,cAAc,QAAA,EAAgC;AACpD,IAAA,OAAO,IAAI,aAAA,CAAa,CAAA,4BAAA,EAA+B,QAAQ,CAAA,CAAE,CAAA;AAAA,EAClE;AAAA;AAAA;AAAA;AAAA,EAKA,MAAA,GAAwB;AACvB,IAAA,OAAO;AAAA,MACN,MAAM,IAAA,CAAK,IAAA;AAAA,MACX,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,SAAS,IAAA,CAAK,KAAA,GAAQ,UAAU,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA,CAAA,GAAK;AAAA,KACxD;AAAA,EACD;AACD;AAKO,IAAM,QAAA,GAAN,MAAM,SAAA,SAAiB,WAAA,CAAY;AAAA;AAAA,EAEzB,aAAA;AAAA;AAAA,EAEA,UAAA;AAAA;AAAA,EAEA,SAAA;AAAA,EAEhB,WAAA,CACC,OAAA,EACA,UAAA,EACA,OAAA,EAIC;AACD,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,UAAA,GAAa,UAAA;AAClB,IAAA,IAAA,CAAK,gBAAgB,OAAA,EAAS,aAAA;AAC9B,IAAA,IAAA,CAAK,YAAY,OAAA,EAAS,SAAA;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,YAAA,CACN,QAAA,EACA,SAAA,EACA,SAAA,EACW;AACX,IAAA,MAAM,OAAA,GACL,WAAW,OAAA,IAAW,CAAA,KAAA,EAAQ,SAAS,MAAM,CAAA,EAAA,EAAK,SAAS,UAAU,CAAA,CAAA;AAEtE,IAAA,OAAO,IAAI,SAAA,CAAS,OAAA,EAAS,QAAA,CAAS,MAAA,EAAQ;AAAA,MAC7C,aAAA,EAAe,SAAA;AAAA,MACf;AAAA,KACA,CAAA;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,WAAA,CAAY,UAAA,EAAqB,SAAA,EAA8B;AACrE,IAAA,MAAM,OAAA,GAAU,UAAA,GACb,CAAA,0BAAA,EAA6B,UAAU,CAAA,QAAA,CAAA,GACvC,cAAA;AAEH,IAAA,OAAO,IAAI,SAAA,CAAS,OAAA,EAAS,GAAA,EAAK;AAAA,MACjC,SAAA;AAAA,MACA,aAAA,EAAe;AAAA,QACd,IAAA,EAAM,gBAAA;AAAA,QACN,OAAA;AAAA,QACA,OAAA,EAAS,UAAA,GAAa,CAAA,YAAA,EAAe,UAAU,CAAA,CAAA,GAAK;AAAA;AACrD,KACA,CAAA;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,aAAA,GAAyB;AACxB,IAAA,OAAO,KAAK,UAAA,GACT,IAAA,CAAK,cAAc,GAAA,IAAO,IAAA,CAAK,aAAa,GAAA,GAC5C,KAAA;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,aAAA,GAAyB;AACxB,IAAA,OAAO,IAAA,CAAK,UAAA,GAAa,IAAA,CAAK,UAAA,IAAc,GAAA,GAAM,KAAA;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKA,WAAA,GAAuB;AACtB,IAAA,IAAI,CAAC,IAAA,CAAK,UAAA,EAAY,OAAO,KAAA;AAG7B,IAAA,OACC,KAAK,UAAA,IAAc,GAAA;AAAA,IACnB,KAAK,UAAA,KAAe,GAAA;AAAA,IACpB,KAAK,UAAA,KAAe,GAAA;AAAA,EAEtB;AAAA;AAAA;AAAA;AAAA,EAKA,aAAA,GAA+B;AAC9B,IAAA,IAAI,CAAC,IAAA,CAAK,WAAA,EAAY,EAAG,OAAO,IAAA;AAGhC,IAAA,IAAI,IAAA,CAAK,UAAA,KAAe,GAAA,IAAO,IAAA,CAAK,eAAe,OAAA,EAAS;AAC3D,MAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,aAAA,CAAc,OAAA,CAAQ,MAAM,mBAAmB,CAAA;AAClE,MAAA,IAAI,KAAA,EAAO;AACV,QAAA,MAAM,UAAA,GAAa,QAAA,CAAS,KAAA,CAAM,CAAC,GAAG,EAAE,CAAA;AACxC,QAAA,OAAO,UAAA,GAAa,GAAA;AAAA,MACrB;AAAA,IACD;AAGA,IAAA,IAAI,IAAA,CAAK,cAAc,GAAA,EAAK;AAC3B,MAAA,OAAO,GAAA;AAAA,IACR;AAEA,IAAA,OAAO,GAAA;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,MAAA,GAAwB;AACvB,IAAA,OAAO;AAAA,MACN,MAAM,IAAA,CAAK,IAAA;AAAA,MACX,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,OAAA,EAAS,eAAe,IAAA,CAAK,UAAU,GACtC,IAAA,CAAK,SAAA,GAAY,CAAA,aAAA,EAAgB,IAAA,CAAK,SAAS,CAAA,CAAA,GAAK,EACrD,CAAA,EACC,IAAA,CAAK,gBACF,CAAA,iBAAA,EAAoB,IAAA,CAAK,UAAU,IAAA,CAAK,aAAa,CAAC,CAAA,CAAA,GACtD,EACJ,CAAA;AAAA,KACD;AAAA,EACD;AACD;;;ACnRO,IAAM,MAAA,GAAN,MAAM,OAAA,CAAO;AAAA,EACnB,OAAA;AAAA,EACA,cAAA;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,UAAA,EAA0B;AACrC,IAAA,IAAI;AAEH,MAAA,IAAA,CAAK,gBAAgB,UAAU,CAAA;AAE/B,MAAA,IAAA,CAAK,OAAA,GAAU,cAAc,UAAU,CAAA;AAAA,IACxC,SAAS,KAAA,EAAO;AACf,MAAA,IAAI,iBAAiB,WAAA,EAAa;AACjC,QAAA,MAAM,KAAA;AAAA,MACP;AACA,MAAA,MAAM,WAAA,CAAY,YAAA;AAAA,QACjB,QAAA;AAAA,QACA,CAAA,qBAAA,EAAwB,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,OACtC;AAAA,IACD;AAGA,IAAA,IAAA,CAAK,iBAAiB,YAAA,CAAa;AAAA,MAClC,OAAA,EAAS,KAAK,OAAA,CAAQ,OAAA;AAAA,MACtB,OAAA,EAAS;AAAA,QACR,aAAA,EAAe,CAAA,OAAA,EAAU,IAAA,CAAK,OAAA,CAAQ,MAAM,CAAA,CAAA;AAAA,QAC5C,cAAA,EAAgB,kBAAA;AAAA,QAChB,YAAA,EAAc,KAAK,eAAA,EAAgB;AAAA,QACnC,GAAG,KAAK,OAAA,CAAQ;AAAA;AACjB,KACA,CAAA;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,OAAA,GAAyB;AAC/B,IAAA,OAAO,IAAI,aAAA,EAAc;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,eAAA,GAA0B;AAChC,IAAA,OAAO,aAAA,CAAc,eAAA,EAAgB,CAAE,KAAA,EAAM;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA,EAKA,SAAA,GAA4C;AAC3C,IAAA,OAAO,OAAO,MAAA,CAAO,EAAE,GAAG,IAAA,CAAK,SAAS,CAAA;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAA,GAAoD;AACnD,IAAA,OAAO,IAAA,CAAK,cAAA;AAAA,EACb;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,MAAA,EAA4B;AAC3C,IAAA,MAAM,UAAU,IAAI,aAAA,EAAc,CAAE,UAAA,CAAW,OAAO,MAAM,CAAA;AAE5D,IAAA,IAAI,MAAA,CAAO,YAAY,MAAA,EAAW;AACjC,MAAA,OAAA,CAAQ,WAAA,CAAY,OAAO,OAAO,CAAA;AAAA,IACnC;AAEA,IAAA,IAAI,MAAA,CAAO,YAAY,MAAA,EAAW;AACjC,MAAA,OAAA,CAAQ,WAAA,CAAY,OAAO,OAAO,CAAA;AAAA,IACnC;AAEA,IAAA,IAAI,MAAA,CAAO,eAAe,MAAA,EAAW;AACpC,MAAA,OAAA,CAAQ,cAAA,CAAe,OAAO,UAAU,CAAA;AAAA,IACzC;AAEA,IAAA,IAAI,MAAA,CAAO,YAAY,MAAA,EAAW;AACjC,MAAA,OAAA,CAAQ,WAAA,CAAY,OAAO,OAAO,CAAA;AAAA,IACnC;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,eAAA,GAA0B;AAEzB,IAAA,MAAM,UAAA,GAAa,OAAA;AACnB,IAAA,MAAM,cAAc,OAAA,CAAQ,OAAA;AAC5B,IAAA,MAAM,WAAW,OAAA,CAAQ,QAAA;AAEzB,IAAA,OAAO,CAAA,WAAA,EAAc,UAAU,CAAA,EAAA,EAAK,QAAQ,aAAa,WAAW,CAAA,CAAA,CAAA;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,aAAA,EAA8C;AACxD,IAAA,MAAM,SAAA,GAA0B;AAAA,MAC/B,MAAA,EAAQ,KAAK,OAAA,CAAQ,MAAA;AAAA,MACrB,OAAA,EAAS,KAAK,OAAA,CAAQ,OAAA;AAAA,MACtB,OAAA,EAAS,KAAK,OAAA,CAAQ,OAAA;AAAA,MACtB,UAAA,EAAY,KAAK,OAAA,CAAQ,UAAA;AAAA,MACzB,OAAA,EAAS,KAAK,OAAA,CAAQ,OAAA;AAAA,MACtB,GAAG;AAAA,KACJ;AACA,IAAA,OAAO,IAAI,QAAO,SAAS,CAAA;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,iBAAA,EAAmD;AAC9D,IAAA,OAAO,KAAK,UAAA,CAAW;AAAA,MACtB,SAAS,EAAE,GAAG,KAAK,OAAA,CAAQ,OAAA,EAAS,GAAG,iBAAA;AAAkB,KACzD,CAAA;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,SAAA,EAA2B;AACtC,IAAA,OAAO,IAAA,CAAK,UAAA,CAAW,EAAE,OAAA,EAAS,WAAW,CAAA;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,UAAA,EAA4B;AAC1C,IAAA,OAAO,IAAA,CAAK,UAAA,CAAW,EAAE,UAAA,EAAY,CAAA;AAAA,EACtC;AACD;;;AC3IA,IAAM,gBAAA,GAAmB,CAAC,eAAA,EAAiB,cAAA,EAAgB,YAAY,CAAA;AAKhE,IAAM,aAAA,GAAN,MAAM,cAAA,CAAc;AAAA,EAC1B,UAAiC,EAAC;AAAA;AAAA;AAAA;AAAA,EAKlC,OAAO,WAAW,MAAA,EAA+B;AAChD,IAAA,OAAO,IAAI,cAAA,EAAc,CAAE,UAAA,CAAW,MAAM,CAAA;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,eAAA,GAAiC;AACvC,IAAA,MAAM,YAAY,iBAAA,EAAkB;AAEpC,IAAA,IAAI,CAAC,UAAU,MAAA,EAAQ;AACtB,MAAA,MAAM,YAAY,aAAA,EAAc;AAAA,IACjC;AAEA,IAAA,MAAM,UAAU,IAAI,cAAA,EAAc,CAAE,UAAA,CAAW,UAAU,MAAM,CAAA;AAE/D,IAAA,IAAI,UAAU,OAAA,EAAS;AACtB,MAAA,OAAA,CAAQ,WAAA,CAAY,UAAU,OAAO,CAAA;AAAA,IACtC;AACA,IAAA,IAAI,UAAU,OAAA,EAAS;AACtB,MAAA,OAAA,CAAQ,WAAA,CAAY,UAAU,OAAO,CAAA;AAAA,IACtC;AACA,IAAA,IAAI,SAAA,CAAU,eAAe,MAAA,EAAW;AACvC,MAAA,OAAA,CAAQ,cAAA,CAAe,UAAU,UAAU,CAAA;AAAA,IAC5C;AACA,IAAA,IAAI,UAAU,OAAA,EAAS;AACtB,MAAA,OAAA,CAAQ,WAAA,CAAY,UAAU,OAAO,CAAA;AAAA,IACtC;AAEA,IAAA,OAAO,OAAA;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,MAAA,EAAsB;AAChC,IAAA,IAAA,CAAK,eAAA,CAAgB,UAAU,MAAM,CAAA;AAErC,IAAA,MAAM,UAAA,GAAa,OAAO,IAAA,EAAK;AAC/B,IAAA,IAAI,UAAA,CAAW,SAAS,EAAA,EAAI;AAC3B,MAAA,MAAM,WAAA,CAAY,YAAA;AAAA,QACjB,QAAA;AAAA,QACA;AAAA,OACD;AAAA,IACD;AAEA,IAAA,IAAI,CAAC,kBAAA,CAAmB,IAAA,CAAK,UAAU,CAAA,EAAG;AACzC,MAAA,MAAM,WAAA,CAAY,YAAA,CAAa,QAAA,EAAU,6BAA6B,CAAA;AAAA,IACvE;AAEA,IAAA,IAAA,CAAK,QAAQ,MAAA,GAAS,UAAA;AACtB,IAAA,OAAO,IAAA;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,OAAA,EAAuB;AAClC,IAAA,IAAA,CAAK,eAAA,CAAgB,WAAW,OAAO,CAAA;AACvC,IAAA,IAAA,CAAK,aAAa,OAAO,CAAA;AACzB,IAAA,IAAA,CAAK,QAAQ,OAAA,GAAU,OAAA;AACvB,IAAA,OAAO,IAAA;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,SAAA,EAAyB;AACpC,IAAA,IAAA,CAAK,gBAAA,CAAiB,SAAA,EAAW,SAAA,EAAW,GAAA,EAAM,GAAO,CAAA;AACzD,IAAA,IAAA,CAAK,QAAQ,OAAA,GAAU,SAAA;AACvB,IAAA,OAAO,IAAA;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,UAAA,EAA0B;AACxC,IAAA,IAAA,CAAK,gBAAA,CAAiB,YAAA,EAAc,UAAA,EAAY,CAAA,EAAG,CAAC,CAAA;AACpD,IAAA,IAAA,CAAK,QAAQ,UAAA,GAAa,UAAA;AAC1B,IAAA,OAAO,IAAA;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,UAAA,CAAW,MAAc,KAAA,EAAqB;AAC7C,IAAA,IAAA,CAAK,qBAAA,CAAsB,MAAM,KAAK,CAAA;AAEtC,IAAA,IAAI,CAAC,IAAA,CAAK,OAAA,CAAQ,OAAA,EAAS;AAC1B,MAAA,IAAA,CAAK,OAAA,CAAQ,UAAU,EAAC;AAAA,IACzB;AAEA,IAAA,IAAA,CAAK,OAAA,CAAQ,OAAA,CAAQ,IAAI,CAAA,GAAI,KAAA;AAC7B,IAAA,OAAO,IAAA;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,OAAA,EAAuC;AAClD,IAAA,IAAI,CAAC,WAAW,OAAO,OAAA,KAAY,YAAY,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,EAAG;AACtE,MAAA,MAAM,WAAA,CAAY,YAAA,CAAa,SAAA,EAAW,wBAAwB,CAAA;AAAA,IACnE;AAEA,IAAA,KAAA,MAAW,CAAC,IAAA,EAAM,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,OAAO,CAAA,EAAG;AACpD,MAAA,IAAA,CAAK,UAAA,CAAW,MAAM,KAAK,CAAA;AAAA,IAC5B;AAEA,IAAA,OAAO,IAAA;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,KAAA,GAAgB;AACf,IAAA,IAAI,CAAC,IAAA,CAAK,OAAA,CAAQ,MAAA,EAAQ;AACzB,MAAA,MAAM,YAAY,aAAA,EAAc;AAAA,IACjC;AAEA,IAAA,OAAO,IAAI,MAAA,CAAO,IAAA,CAAK,OAAuB,CAAA;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA,EAKA,SAAA,GAA6C;AAC5C,IAAA,OAAO,EAAE,GAAG,IAAA,CAAK,OAAA,EAAQ;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,eAAA,CAAgB,WAAmB,KAAA,EAAqB;AACvD,IAAA,IAAI,CAAC,SAAS,OAAO,KAAA,KAAU,YAAY,KAAA,CAAM,IAAA,EAAK,CAAE,MAAA,KAAW,CAAA,EAAG;AACrE,MAAA,MAAM,WAAA,CAAY,YAAA,CAAa,SAAA,EAAW,4BAA4B,CAAA;AAAA,IACvE;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAA,CACC,SAAA,EACA,KAAA,EACA,GAAA,EACA,GAAA,EACO;AACP,IAAA,IAAI,CAAC,MAAA,CAAO,SAAA,CAAU,KAAK,CAAA,IAAK,QAAQ,GAAA,EAAK;AAC5C,MAAA,MAAM,WAAA,CAAY,YAAA,CAAa,SAAA,EAAW,CAAA,sBAAA,EAAyB,GAAG,CAAA,CAAE,CAAA;AAAA,IACzE;AAEA,IAAA,IAAI,QAAQ,GAAA,EAAK;AAChB,MAAA,MAAM,WAAA,CAAY,YAAA,CAAa,SAAA,EAAW,CAAA,gBAAA,EAAmB,GAAG,CAAA,CAAE,CAAA;AAAA,IACnE;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,OAAA,EAAuB;AACnC,IAAA,IAAI,GAAA;AACJ,IAAA,IAAI;AACH,MAAA,GAAA,GAAM,IAAI,IAAI,OAAO,CAAA;AAAA,IACtB,CAAA,CAAA,MAAQ;AACP,MAAA,MAAM,WAAA,CAAY,YAAA,CAAa,SAAA,EAAW,qBAAqB,CAAA;AAAA,IAChE;AAEA,IAAA,MAAM,gBAAA,GAAmB,CAAC,QAAA,EAAU,OAAO,CAAA;AAC3C,IAAA,IAAI,CAAC,gBAAA,CAAiB,QAAA,CAAS,GAAA,CAAI,QAAQ,CAAA,EAAG;AAC7C,MAAA,MAAM,WAAA,CAAY,YAAA;AAAA,QACjB,SAAA;AAAA,QACA,CAAA,yBAAA,EAA4B,gBAAA,CAAiB,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,OACxD;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,qBAAA,CAAsB,MAAc,KAAA,EAAqB;AACxD,IAAA,IAAI,CAAC,QAAQ,OAAO,IAAA,KAAS,YAAY,IAAA,CAAK,IAAA,EAAK,CAAE,MAAA,KAAW,CAAA,EAAG;AAClE,MAAA,MAAM,WAAA,CAAY,YAAA;AAAA,QACjB,aAAA;AAAA,QACA;AAAA,OACD;AAAA,IACD;AACA,IAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC9B,MAAA,MAAM,WAAA,CAAY,YAAA,CAAa,cAAA,EAAgB,kBAAkB,CAAA;AAAA,IAClE;AAGA,IAAA,IAAI,CAAC,gCAAA,CAAiC,IAAA,CAAK,IAAI,CAAA,EAAG;AACjD,MAAA,MAAM,WAAA,CAAY,YAAA;AAAA,QACjB,aAAA;AAAA,QACA,wBAAwB,IAAI,CAAA;AAAA,OAC7B;AAAA,IACD;AAGA,IAAA,IAAI,gBAAA,CAAiB,QAAA,CAAS,IAAA,CAAK,WAAA,EAAa,CAAA,EAAG;AAClD,MAAA,MAAM,WAAA,CAAY,YAAA;AAAA,QACjB,aAAA;AAAA,QACA,WAAW,IAAI,CAAA,sCAAA;AAAA,OAChB;AAAA,IACD;AAAA,EACD;AACD","file":"index.js","sourcesContent":["/**\n * Configuration options for the Nvisy client\n */\nexport interface ClientConfig {\n\t/**\n\t * API key for authentication\n\t */\n\tapiKey: string;\n\n\t/**\n\t * Base URL for the Nvisy API\n\t * @default \"https://api.nvisy.com\"\n\t */\n\tbaseUrl?: string;\n\n\t/**\n\t * Request timeout in milliseconds\n\t * @default 30000\n\t */\n\ttimeout?: number;\n\n\t/**\n\t * Maximum number of retry attempts for failed requests\n\t * @default 3\n\t */\n\tmaxRetries?: number;\n\n\t/**\n\t * Custom headers to include with requests\n\t */\n\theaders?: Record<string, string>;\n}\n\n/**\n * Internal fully-resolved configuration\n */\nexport type ResolvedClientConfig = Required<ClientConfig>;\n\n/**\n * Environment variable names for configuration\n */\nconst ENV_VARS = {\n\tAPI_KEY: \"NVISY_API_KEY\",\n\tBASE_URL: \"NVISY_BASE_URL\",\n\tTIMEOUT: \"NVISY_TIMEOUT\",\n\tMAX_RETRIES: \"NVISY_MAX_RETRIES\",\n} as const;\n\n/**\n * Default configuration values\n */\nconst DEFAULTS = {\n\tbaseUrl: \"https://api.nvisy.com\",\n\ttimeout: 30_000,\n\tmaxRetries: 3,\n\theaders: {},\n} as const;\n\n/**\n * Load configuration from environment variables\n */\nexport function loadConfigFromEnv(): Partial<ClientConfig> {\n\tconst config: Partial<ClientConfig> = {};\n\n\tconst apiKey = process.env[ENV_VARS.API_KEY];\n\tif (apiKey) {\n\t\tconfig.apiKey = apiKey;\n\t}\n\n\tconst baseUrl = process.env[ENV_VARS.BASE_URL];\n\tif (baseUrl) {\n\t\tconfig.baseUrl = baseUrl;\n\t}\n\n\tconst timeout = process.env[ENV_VARS.TIMEOUT];\n\tif (timeout) {\n\t\tconst timeoutMs = parseInt(timeout, 10);\n\t\tif (!Number.isNaN(timeoutMs)) {\n\t\t\tconfig.timeout = timeoutMs;\n\t\t}\n\t}\n\n\tconst maxRetries = process.env[ENV_VARS.MAX_RETRIES];\n\tif (maxRetries) {\n\t\tconst retries = parseInt(maxRetries, 10);\n\t\tif (!Number.isNaN(retries)) {\n\t\t\tconfig.maxRetries = retries;\n\t\t}\n\t}\n\n\treturn config;\n}\n\n/**\n * Resolve configuration with defaults\n */\nexport function resolveConfig(userConfig: ClientConfig): ResolvedClientConfig {\n\tconst envConfig = loadConfigFromEnv();\n\tconst mergedConfig = { ...envConfig, ...userConfig };\n\n\treturn {\n\t\tapiKey: mergedConfig.apiKey || \"\",\n\t\tbaseUrl: mergedConfig.baseUrl || DEFAULTS.baseUrl,\n\t\ttimeout: mergedConfig.timeout ?? DEFAULTS.timeout,\n\t\tmaxRetries: mergedConfig.maxRetries ?? DEFAULTS.maxRetries,\n\t\theaders: { ...DEFAULTS.headers, ...mergedConfig.headers },\n\t};\n}\n\n/**\n * Get available environment variable names\n */\nexport function getEnvironmentVariables(): Record<string, string> {\n\treturn { ...ENV_VARS };\n}\n","/**\n * Error response structure from server\n */\nexport interface ErrorResponse {\n\t/** Error type/name */\n\tname: string;\n\t/** Human-readable error message */\n\tmessage: string;\n\t/** Additional error context */\n\tcontext: string;\n}\n\n/**\n * Abstract base error class for all Nvisy SDK errors\n */\nexport abstract class ClientError extends Error {\n\tpublic readonly name: string;\n\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = this.constructor.name;\n\n\t\t// Maintains proper stack trace for where our error was thrown (only available on V8)\n\t\tif (Error.captureStackTrace) {\n\t\t\tError.captureStackTrace(this, this.constructor);\n\t\t}\n\t}\n\n\t/**\n\t * Convert error to JSON representation\n\t */\n\ttoJSON(): ErrorResponse {\n\t\treturn {\n\t\t\tname: this.name,\n\t\t\tmessage: this.message,\n\t\t\tcontext: \"\",\n\t\t};\n\t}\n}\n\n/**\n * Configuration error - thrown when client configuration is invalid\n */\nexport class ConfigError extends ClientError {\n\t/** Field that caused the error (for validation errors) */\n\tpublic readonly field?: string;\n\t/** Reason why the configuration is invalid */\n\tpublic readonly reason?: string;\n\n\tconstructor(\n\t\tmessage: string,\n\t\toptions?: {\n\t\t\tfield?: string;\n\t\t\treason?: string;\n\t\t},\n\t) {\n\t\tsuper(message);\n\t\tthis.field = options?.field;\n\t\tthis.reason = options?.reason;\n\t}\n\n\t/**\n\t * Create error for missing API key\n\t */\n\tstatic missingApiKey(): ConfigError {\n\t\treturn new ConfigError(\"API key is required\", {\n\t\t\tfield: \"apiKey\",\n\t\t\treason: \"API key must be provided in configuration\",\n\t\t});\n\t}\n\n\t/**\n\t * Create error for invalid configuration field\n\t */\n\tstatic invalidField(field: string, reason: string): ConfigError {\n\t\treturn new ConfigError(`Invalid configuration for ${field}: ${reason}`, {\n\t\t\tfield,\n\t\t\treason,\n\t\t});\n\t}\n\n\t/**\n\t * Create error for missing required field\n\t */\n\tstatic missingField(field: string): ConfigError {\n\t\treturn new ConfigError(`Missing required configuration field: ${field}`, {\n\t\t\tfield,\n\t\t\treason: \"This field is required\",\n\t\t});\n\t}\n\n\t/**\n\t * Convert error to JSON representation\n\t */\n\ttoJSON(): ErrorResponse {\n\t\treturn {\n\t\t\tname: this.name,\n\t\t\tmessage: this.message,\n\t\t\tcontext:\n\t\t\t\tthis.field || this.reason\n\t\t\t\t\t? `field: ${this.field}, reason: ${this.reason}`\n\t\t\t\t\t: \"\",\n\t\t};\n\t}\n}\n\n/**\n * Network error - thrown when network requests fail\n */\nexport class NetworkError extends ClientError {\n\t/** Original error that caused this network error */\n\tpublic readonly cause?: Error;\n\n\tconstructor(message: string, cause?: Error) {\n\t\tsuper(message);\n\t\tthis.cause = cause;\n\t}\n\n\t/**\n\t * Create error for network/connection issues\n\t */\n\tstatic connection(message: string, cause?: Error): NetworkError {\n\t\treturn new NetworkError(message, cause);\n\t}\n\n\t/**\n\t * Create error for request timeout\n\t */\n\tstatic timeout(timeoutMs: number): NetworkError {\n\t\treturn new NetworkError(`Request timed out after ${timeoutMs}ms`);\n\t}\n\n\t/**\n\t * Create error for aborted request\n\t */\n\tstatic aborted(): NetworkError {\n\t\treturn new NetworkError(\"Request was aborted\");\n\t}\n\n\t/**\n\t * Create error for DNS resolution failure\n\t */\n\tstatic dnsResolution(hostname: string): NetworkError {\n\t\treturn new NetworkError(`Failed to resolve hostname: ${hostname}`);\n\t}\n\n\t/**\n\t * Convert error to JSON representation\n\t */\n\ttoJSON(): ErrorResponse {\n\t\treturn {\n\t\t\tname: this.name,\n\t\t\tmessage: this.message,\n\t\t\tcontext: this.cause ? `cause: ${this.cause.message}` : \"\",\n\t\t};\n\t}\n}\n\n/**\n * API error - thrown when server responds with an error\n */\nexport class ApiError extends ClientError {\n\t/** Error response from server */\n\tpublic readonly errorResponse?: ErrorResponse;\n\t/** HTTP status code */\n\tpublic readonly statusCode: number;\n\t/** Request ID for debugging */\n\tpublic readonly requestId?: string;\n\n\tconstructor(\n\t\tmessage: string,\n\t\tstatusCode: number,\n\t\toptions?: {\n\t\t\terrorResponse?: ErrorResponse;\n\t\t\trequestId?: string;\n\t\t},\n\t) {\n\t\tsuper(message);\n\t\tthis.statusCode = statusCode;\n\t\tthis.errorResponse = options?.errorResponse;\n\t\tthis.requestId = options?.requestId;\n\t}\n\n\t/**\n\t * Create error from HTTP response\n\t */\n\tstatic fromResponse(\n\t\tresponse: Response,\n\t\terrorData?: ErrorResponse,\n\t\trequestId?: string,\n\t): ApiError {\n\t\tconst message =\n\t\t\terrorData?.message || `HTTP ${response.status}: ${response.statusText}`;\n\n\t\treturn new ApiError(message, response.status, {\n\t\t\terrorResponse: errorData,\n\t\t\trequestId,\n\t\t});\n\t}\n\n\t/**\n\t * Create error for rate limiting\n\t */\n\tstatic rateLimited(retryAfter?: number, requestId?: string): ApiError {\n\t\tconst message = retryAfter\n\t\t\t? `Rate limited. Retry after ${retryAfter} seconds`\n\t\t\t: \"Rate limited\";\n\n\t\treturn new ApiError(message, 429, {\n\t\t\trequestId,\n\t\t\terrorResponse: {\n\t\t\t\tname: \"RateLimitError\",\n\t\t\t\tmessage,\n\t\t\t\tcontext: retryAfter ? `retryAfter: ${retryAfter}` : \"\",\n\t\t\t},\n\t\t});\n\t}\n\n\t/**\n\t * Check if error is a client error (4xx)\n\t */\n\tisClientError(): boolean {\n\t\treturn this.statusCode\n\t\t\t? this.statusCode >= 400 && this.statusCode < 500\n\t\t\t: false;\n\t}\n\n\t/**\n\t * Check if error is a server error (5xx)\n\t */\n\tisServerError(): boolean {\n\t\treturn this.statusCode ? this.statusCode >= 500 : false;\n\t}\n\n\t/**\n\t * Check if error is retryable based on HTTP status\n\t */\n\tisRetryable(): boolean {\n\t\tif (!this.statusCode) return false;\n\n\t\t// Retry on server errors and specific client errors\n\t\treturn (\n\t\t\tthis.statusCode >= 500 || // Server errors\n\t\t\tthis.statusCode === 408 || // Request timeout\n\t\t\tthis.statusCode === 429 // Rate limited\n\t\t);\n\t}\n\n\t/**\n\t * Get retry delay in milliseconds (returns null if not retryable)\n\t */\n\tgetRetryDelay(): number | null {\n\t\tif (!this.isRetryable()) return null;\n\n\t\t// For rate limiting, check if we have retry-after info\n\t\tif (this.statusCode === 429 && this.errorResponse?.context) {\n\t\t\tconst match = this.errorResponse.context.match(/retryAfter: (\\d+)/);\n\t\t\tif (match) {\n\t\t\t\tconst retryAfter = parseInt(match[1], 10);\n\t\t\t\treturn retryAfter * 1000; // Convert seconds to milliseconds\n\t\t\t}\n\t\t}\n\n\t\t// Default delays based on error type\n\t\tif (this.statusCode >= 500) {\n\t\t\treturn 1000; // 1 second for server errors\n\t\t}\n\n\t\treturn 1000; // Default 1 second\n\t}\n\n\t/**\n\t * Convert error to JSON representation\n\t */\n\ttoJSON(): ErrorResponse {\n\t\treturn {\n\t\t\tname: this.name,\n\t\t\tmessage: this.message,\n\t\t\tcontext: `statusCode: ${this.statusCode}${\n\t\t\t\tthis.requestId ? `, requestId: ${this.requestId}` : \"\"\n\t\t\t}${\n\t\t\t\tthis.errorResponse\n\t\t\t\t\t? `, errorResponse: ${JSON.stringify(this.errorResponse)}`\n\t\t\t\t\t: \"\"\n\t\t\t}`,\n\t\t};\n\t}\n}\n","import createClient from \"openapi-fetch\";\nimport { ClientBuilder } from \"./builder.js\";\nimport {\n\ttype ClientConfig,\n\ttype ResolvedClientConfig,\n\tresolveConfig,\n} from \"./config.js\";\nimport { ConfigError } from \"./errors.js\";\n\n/**\n * Main client class for interacting with the Nvisy document redaction API\n */\nexport class Client {\n\t#config: ResolvedClientConfig;\n\t#openApiClient: ReturnType<typeof createClient>;\n\n\t/**\n\t * Create a new Nvisy client instance\n\t */\n\tconstructor(userConfig: ClientConfig) {\n\t\ttry {\n\t\t\t// Validate configuration first\n\t\t\tthis.#validateConfig(userConfig);\n\t\t\t// Resolve configuration with defaults\n\t\t\tthis.#config = resolveConfig(userConfig);\n\t\t} catch (error) {\n\t\t\tif (error instanceof ConfigError) {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tthrow ConfigError.invalidField(\n\t\t\t\t\"config\",\n\t\t\t\t`Configuration error: ${String(error)}`,\n\t\t\t);\n\t\t}\n\n\t\t// Create openapi-fetch client with proper headers\n\t\tthis.#openApiClient = createClient({\n\t\t\tbaseUrl: this.#config.baseUrl,\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${this.#config.apiKey}`,\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\"User-Agent\": this.#buildUserAgent(),\n\t\t\t\t...this.#config.headers,\n\t\t\t},\n\t\t});\n\t}\n\n\t/**\n\t * Create a new ClientBuilder for fluent configuration\n\t */\n\tstatic builder(): ClientBuilder {\n\t\treturn new ClientBuilder();\n\t}\n\n\t/**\n\t * Create a client from environment variables\n\t */\n\tstatic fromEnvironment(): Client {\n\t\treturn ClientBuilder.fromEnvironment().build();\n\t}\n\n\t/**\n\t * Get the current configuration (readonly copy)\n\t */\n\tgetConfig(): Readonly<ResolvedClientConfig> {\n\t\treturn Object.freeze({ ...this.#config });\n\t}\n\n\t/**\n\t * Get the underlying openapi-fetch client for advanced usage\n\t */\n\tgetOpenApiClient(): ReturnType<typeof createClient> {\n\t\treturn this.#openApiClient;\n\t}\n\n\t/**\n\t * Validate configuration by reusing ClientBuilder validation\n\t */\n\t#validateConfig(config: ClientConfig): void {\n\t\tconst builder = new ClientBuilder().withApiKey(config.apiKey);\n\n\t\tif (config.baseUrl !== undefined) {\n\t\t\tbuilder.withBaseUrl(config.baseUrl);\n\t\t}\n\n\t\tif (config.timeout !== undefined) {\n\t\t\tbuilder.withTimeout(config.timeout);\n\t\t}\n\n\t\tif (config.maxRetries !== undefined) {\n\t\t\tbuilder.withMaxRetries(config.maxRetries);\n\t\t}\n\n\t\tif (config.headers !== undefined) {\n\t\t\tbuilder.withHeaders(config.headers);\n\t\t}\n\t}\n\n\t/**\n\t * Build user agent string\n\t */\n\t#buildUserAgent(): string {\n\t\t// In a real implementation, this would import from package.json\n\t\tconst sdkVersion = \"1.0.0\";\n\t\tconst nodeVersion = process.version;\n\t\tconst platform = process.platform;\n\n\t\treturn `@nvisy/sdk/${sdkVersion} (${platform}; Node.js ${nodeVersion})`;\n\t}\n\n\t/**\n\t * Create a new client with modified configuration\n\t */\n\twithConfig(configChanges: Partial<ClientConfig>): Client {\n\t\tconst newConfig: ClientConfig = {\n\t\t\tapiKey: this.#config.apiKey,\n\t\t\tbaseUrl: this.#config.baseUrl,\n\t\t\ttimeout: this.#config.timeout,\n\t\t\tmaxRetries: this.#config.maxRetries,\n\t\t\theaders: this.#config.headers,\n\t\t\t...configChanges,\n\t\t};\n\t\treturn new Client(newConfig);\n\t}\n\n\t/**\n\t * Create a new client with additional headers\n\t */\n\twithHeaders(additionalHeaders: Record<string, string>): Client {\n\t\treturn this.withConfig({\n\t\t\theaders: { ...this.#config.headers, ...additionalHeaders },\n\t\t});\n\t}\n\n\t/**\n\t * Create a new client with a different timeout\n\t */\n\twithTimeout(timeoutMs: number): Client {\n\t\treturn this.withConfig({ timeout: timeoutMs });\n\t}\n\n\t/**\n\t * Create a new client with different retry settings\n\t */\n\twithMaxRetries(maxRetries: number): Client {\n\t\treturn this.withConfig({ maxRetries });\n\t}\n}\n","import { Client } from \"./client.js\";\nimport type { ClientConfig } from \"./config.js\";\nimport { loadConfigFromEnv } from \"./config.js\";\nimport { ConfigError } from \"./errors.js\";\n\n/**\n * Reserved headers that cannot be overridden\n */\nconst RESERVED_HEADERS = [\"authorization\", \"content-type\", \"user-agent\"];\n\n/**\n * Builder class for constructing client instances with a fluent API\n */\nexport class ClientBuilder {\n\t#config: Partial<ClientConfig> = {};\n\n\t/**\n\t * Create a ClientBuilder instance with an API key\n\t */\n\tstatic fromApiKey(apiKey: string): ClientBuilder {\n\t\treturn new ClientBuilder().withApiKey(apiKey);\n\t}\n\n\t/**\n\t * Create a ClientBuilder instance from environment variables\n\t */\n\tstatic fromEnvironment(): ClientBuilder {\n\t\tconst envConfig = loadConfigFromEnv();\n\n\t\tif (!envConfig.apiKey) {\n\t\t\tthrow ConfigError.missingApiKey();\n\t\t}\n\n\t\tconst builder = new ClientBuilder().withApiKey(envConfig.apiKey);\n\n\t\tif (envConfig.baseUrl) {\n\t\t\tbuilder.withBaseUrl(envConfig.baseUrl);\n\t\t}\n\t\tif (envConfig.timeout) {\n\t\t\tbuilder.withTimeout(envConfig.timeout);\n\t\t}\n\t\tif (envConfig.maxRetries !== undefined) {\n\t\t\tbuilder.withMaxRetries(envConfig.maxRetries);\n\t\t}\n\t\tif (envConfig.headers) {\n\t\t\tbuilder.withHeaders(envConfig.headers);\n\t\t}\n\n\t\treturn builder;\n\t}\n\n\t/**\n\t * Set the API key for authentication\n\t */\n\twithApiKey(apiKey: string): this {\n\t\tthis.#validateString(\"apiKey\", apiKey);\n\n\t\tconst trimmedKey = apiKey.trim();\n\t\tif (trimmedKey.length < 10) {\n\t\t\tthrow ConfigError.invalidField(\n\t\t\t\t\"apiKey\",\n\t\t\t\t\"must be at least 10 characters\",\n\t\t\t);\n\t\t}\n\n\t\tif (!/^[a-zA-Z0-9_-]+$/.test(trimmedKey)) {\n\t\t\tthrow ConfigError.invalidField(\"apiKey\", \"contains invalid characters\");\n\t\t}\n\n\t\tthis.#config.apiKey = trimmedKey;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Set the base URL for the API\n\t */\n\twithBaseUrl(baseUrl: string): this {\n\t\tthis.#validateString(\"baseUrl\", baseUrl);\n\t\tthis.#validateUrl(baseUrl);\n\t\tthis.#config.baseUrl = baseUrl;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Set the request timeout in milliseconds\n\t */\n\twithTimeout(timeoutMs: number): this {\n\t\tthis.#validateInteger(\"timeout\", timeoutMs, 1000, 300_000);\n\t\tthis.#config.timeout = timeoutMs;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Set the maximum number of retry attempts\n\t */\n\twithMaxRetries(maxRetries: number): this {\n\t\tthis.#validateInteger(\"maxRetries\", maxRetries, 0, 5);\n\t\tthis.#config.maxRetries = maxRetries;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Add a single custom header (merges with existing headers)\n\t */\n\twithHeader(name: string, value: string): this {\n\t\tthis.#validateSingleHeader(name, value);\n\n\t\tif (!this.#config.headers) {\n\t\t\tthis.#config.headers = {};\n\t\t}\n\n\t\tthis.#config.headers[name] = value;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Set custom headers (merges with existing headers)\n\t */\n\twithHeaders(headers: Record<string, string>): this {\n\t\tif (!headers || typeof headers !== \"object\" || Array.isArray(headers)) {\n\t\t\tthrow ConfigError.invalidField(\"headers\", \"must be a valid object\");\n\t\t}\n\n\t\tfor (const [name, value] of Object.entries(headers)) {\n\t\t\tthis.withHeader(name, value);\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Build and return the configured client instance\n\t */\n\tbuild(): Client {\n\t\tif (!this.#config.apiKey) {\n\t\t\tthrow ConfigError.missingApiKey();\n\t\t}\n\n\t\treturn new Client(this.#config as ClientConfig);\n\t}\n\n\t/**\n\t * Get the current configuration (for debugging/testing)\n\t */\n\tgetConfig(): Readonly<Partial<ClientConfig>> {\n\t\treturn { ...this.#config };\n\t}\n\n\t/**\n\t * Validate string field\n\t */\n\t#validateString(fieldName: string, value: string): void {\n\t\tif (!value || typeof value !== \"string\" || value.trim().length === 0) {\n\t\t\tthrow ConfigError.invalidField(fieldName, \"must be a non-empty string\");\n\t\t}\n\t}\n\n\t/**\n\t * Validate integer field with range\n\t */\n\t#validateInteger(\n\t\tfieldName: string,\n\t\tvalue: number,\n\t\tmin: number,\n\t\tmax: number,\n\t): void {\n\t\tif (!Number.isInteger(value) || value < min) {\n\t\t\tthrow ConfigError.invalidField(fieldName, `must be an integer >= ${min}`);\n\t\t}\n\n\t\tif (value > max) {\n\t\t\tthrow ConfigError.invalidField(fieldName, `must not exceed ${max}`);\n\t\t}\n\t}\n\n\t/**\n\t * Validate URL format\n\t */\n\t#validateUrl(baseUrl: string): void {\n\t\tlet url: URL;\n\t\ttry {\n\t\t\turl = new URL(baseUrl);\n\t\t} catch {\n\t\t\tthrow ConfigError.invalidField(\"baseUrl\", \"must be a valid URL\");\n\t\t}\n\n\t\tconst allowedProtocols = [\"https:\", \"http:\"];\n\t\tif (!allowedProtocols.includes(url.protocol)) {\n\t\t\tthrow ConfigError.invalidField(\n\t\t\t\t\"baseUrl\",\n\t\t\t\t`protocol must be one of: ${allowedProtocols.join(\", \")}`,\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Validate single header name and value\n\t */\n\t#validateSingleHeader(name: string, value: string): void {\n\t\tif (!name || typeof name !== \"string\" || name.trim().length === 0) {\n\t\t\tthrow ConfigError.invalidField(\n\t\t\t\t\"header name\",\n\t\t\t\t\"must be a non-empty string\",\n\t\t\t);\n\t\t}\n\t\tif (typeof value !== \"string\") {\n\t\t\tthrow ConfigError.invalidField(\"header value\", \"must be a string\");\n\t\t}\n\n\t\t// Check for invalid header names (RFC 7230)\n\t\tif (!/^[a-zA-Z0-9!#$%&'*+\\-.^_`|~]+$/.test(name)) {\n\t\t\tthrow ConfigError.invalidField(\n\t\t\t\t\"header name\",\n\t\t\t\t`invalid header name: ${name}`,\n\t\t\t);\n\t\t}\n\n\t\t// Check for reserved headers\n\t\tif (RESERVED_HEADERS.includes(name.toLowerCase())) {\n\t\t\tthrow ConfigError.invalidField(\n\t\t\t\t\"header name\",\n\t\t\t\t`header \"${name}\" is reserved and cannot be overridden`,\n\t\t\t);\n\t\t}\n\t}\n}\n"]}
|