optropic 1.0.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/dist/index.cjs ADDED
@@ -0,0 +1,662 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AssetsResource: () => AssetsResource,
24
+ AuthenticationError: () => AuthenticationError,
25
+ BatchNotFoundError: () => BatchNotFoundError,
26
+ CodeNotFoundError: () => CodeNotFoundError,
27
+ InvalidCodeError: () => InvalidCodeError,
28
+ InvalidGTINError: () => InvalidGTINError,
29
+ InvalidSerialError: () => InvalidSerialError,
30
+ KeysResource: () => KeysResource,
31
+ NetworkError: () => NetworkError,
32
+ OptropicClient: () => OptropicClient,
33
+ OptropicError: () => OptropicError,
34
+ QuotaExceededError: () => QuotaExceededError,
35
+ RateLimitedError: () => RateLimitedError,
36
+ RevokedCodeError: () => RevokedCodeError,
37
+ SDK_VERSION: () => SDK_VERSION2,
38
+ ServiceUnavailableError: () => ServiceUnavailableError,
39
+ TimeoutError: () => TimeoutError,
40
+ createClient: () => createClient
41
+ });
42
+ module.exports = __toCommonJS(index_exports);
43
+
44
+ // src/errors.ts
45
+ var OptropicError = class extends Error {
46
+ /**
47
+ * Error code for programmatic handling.
48
+ */
49
+ code;
50
+ /**
51
+ * HTTP status code (if applicable).
52
+ */
53
+ statusCode;
54
+ /**
55
+ * Whether this error can be retried.
56
+ */
57
+ retryable;
58
+ /**
59
+ * Additional details about the error.
60
+ */
61
+ details;
62
+ /**
63
+ * Request ID for support reference.
64
+ */
65
+ requestId;
66
+ constructor(code, message, options) {
67
+ super(message, { cause: options?.cause });
68
+ this.name = "OptropicError";
69
+ this.code = code;
70
+ this.statusCode = options?.statusCode;
71
+ this.retryable = options?.retryable ?? false;
72
+ this.details = options?.details;
73
+ this.requestId = options?.requestId;
74
+ if (Error.captureStackTrace) {
75
+ Error.captureStackTrace(this, this.constructor);
76
+ }
77
+ }
78
+ /**
79
+ * Create a JSON-serializable representation.
80
+ */
81
+ toJSON() {
82
+ return {
83
+ name: this.name,
84
+ code: this.code,
85
+ message: this.message,
86
+ statusCode: this.statusCode,
87
+ retryable: this.retryable,
88
+ details: this.details,
89
+ requestId: this.requestId
90
+ };
91
+ }
92
+ };
93
+ var AuthenticationError = class extends OptropicError {
94
+ constructor(message = "Authentication failed. Please check your API key.", options) {
95
+ super(options?.code ?? "INVALID_API_KEY", message, {
96
+ statusCode: 401,
97
+ retryable: false,
98
+ details: options?.details,
99
+ requestId: options?.requestId
100
+ });
101
+ this.name = "AuthenticationError";
102
+ }
103
+ };
104
+ var InvalidSerialError = class extends OptropicError {
105
+ /**
106
+ * The invalid serial that was provided.
107
+ */
108
+ serial;
109
+ constructor(serial, message, options) {
110
+ super(
111
+ "INVALID_SERIAL",
112
+ message ?? `Invalid serial number: "${serial}". Serial must be 1-20 alphanumeric characters.`,
113
+ {
114
+ statusCode: 400,
115
+ retryable: false,
116
+ details: { serial, ...options?.details },
117
+ requestId: options?.requestId
118
+ }
119
+ );
120
+ this.name = "InvalidSerialError";
121
+ this.serial = serial;
122
+ }
123
+ };
124
+ var InvalidGTINError = class extends OptropicError {
125
+ /**
126
+ * The invalid GTIN that was provided.
127
+ */
128
+ gtin;
129
+ constructor(gtin, message, options) {
130
+ super(
131
+ "INVALID_GTIN",
132
+ message ?? `Invalid GTIN: "${gtin}". GTIN must be 8, 12, 13, or 14 digits with valid check digit.`,
133
+ {
134
+ statusCode: 400,
135
+ retryable: false,
136
+ details: { gtin, ...options?.details },
137
+ requestId: options?.requestId
138
+ }
139
+ );
140
+ this.name = "InvalidGTINError";
141
+ this.gtin = gtin;
142
+ }
143
+ };
144
+ var InvalidCodeError = class extends OptropicError {
145
+ /**
146
+ * The invalid code that was provided.
147
+ */
148
+ invalidCode;
149
+ constructor(invalidCode, message, options) {
150
+ super(
151
+ "INVALID_CODE_FORMAT",
152
+ message ?? `Invalid code format: "${invalidCode}". Expected a valid Optropic verification URL or code ID.`,
153
+ {
154
+ statusCode: 400,
155
+ retryable: false,
156
+ details: { code: invalidCode, ...options?.details },
157
+ requestId: options?.requestId
158
+ }
159
+ );
160
+ this.name = "InvalidCodeError";
161
+ this.invalidCode = invalidCode;
162
+ }
163
+ };
164
+ var RevokedCodeError = class extends OptropicError {
165
+ /**
166
+ * The revoked code.
167
+ */
168
+ revokedCode;
169
+ /**
170
+ * Reason for revocation.
171
+ */
172
+ reason;
173
+ /**
174
+ * When the code was revoked.
175
+ */
176
+ revokedAt;
177
+ constructor(code, reason, revokedAt, options) {
178
+ super(
179
+ "CODE_REVOKED",
180
+ `Code has been revoked: ${reason}`,
181
+ {
182
+ statusCode: 410,
183
+ retryable: false,
184
+ details: { code, reason, revokedAt, ...options?.details },
185
+ requestId: options?.requestId
186
+ }
187
+ );
188
+ this.name = "RevokedCodeError";
189
+ this.revokedCode = code;
190
+ this.reason = reason;
191
+ this.revokedAt = revokedAt;
192
+ }
193
+ };
194
+ var CodeNotFoundError = class extends OptropicError {
195
+ /**
196
+ * The code that was not found.
197
+ */
198
+ searchedCode;
199
+ constructor(code, options) {
200
+ super(
201
+ "CODE_NOT_FOUND",
202
+ `Code not found: "${code}". The code may not exist or may have been entered incorrectly.`,
203
+ {
204
+ statusCode: 404,
205
+ retryable: false,
206
+ details: { code, ...options?.details },
207
+ requestId: options?.requestId
208
+ }
209
+ );
210
+ this.name = "CodeNotFoundError";
211
+ this.searchedCode = code;
212
+ }
213
+ };
214
+ var BatchNotFoundError = class extends OptropicError {
215
+ /**
216
+ * The batch ID that was not found.
217
+ */
218
+ batchId;
219
+ constructor(batchId, options) {
220
+ super(
221
+ "BATCH_NOT_FOUND",
222
+ `Batch not found: "${batchId}".`,
223
+ {
224
+ statusCode: 404,
225
+ retryable: false,
226
+ details: { batchId, ...options?.details },
227
+ requestId: options?.requestId
228
+ }
229
+ );
230
+ this.name = "BatchNotFoundError";
231
+ this.batchId = batchId;
232
+ }
233
+ };
234
+ var RateLimitedError = class extends OptropicError {
235
+ /**
236
+ * Seconds until rate limit resets.
237
+ */
238
+ retryAfter;
239
+ /**
240
+ * Current rate limit (requests per period).
241
+ */
242
+ limit;
243
+ /**
244
+ * Remaining requests in current period.
245
+ */
246
+ remaining;
247
+ constructor(retryAfter, options) {
248
+ super(
249
+ "RATE_LIMITED",
250
+ `Rate limit exceeded. Please retry after ${retryAfter} seconds.`,
251
+ {
252
+ statusCode: 429,
253
+ retryable: true,
254
+ details: {
255
+ retryAfter,
256
+ limit: options?.limit,
257
+ remaining: options?.remaining,
258
+ ...options?.details
259
+ },
260
+ requestId: options?.requestId
261
+ }
262
+ );
263
+ this.name = "RateLimitedError";
264
+ this.retryAfter = retryAfter;
265
+ this.limit = options?.limit ?? 0;
266
+ this.remaining = options?.remaining ?? 0;
267
+ }
268
+ };
269
+ var QuotaExceededError = class extends OptropicError {
270
+ /**
271
+ * Current quota limit.
272
+ */
273
+ quotaLimit;
274
+ /**
275
+ * Quota used.
276
+ */
277
+ quotaUsed;
278
+ /**
279
+ * When quota resets (ISO 8601).
280
+ */
281
+ resetsAt;
282
+ constructor(quotaLimit, quotaUsed, resetsAt, options) {
283
+ super(
284
+ "QUOTA_EXCEEDED",
285
+ `Monthly quota exceeded (${quotaUsed}/${quotaLimit}). Resets at ${resetsAt}.`,
286
+ {
287
+ statusCode: 402,
288
+ retryable: false,
289
+ details: { quotaLimit, quotaUsed, resetsAt, ...options?.details },
290
+ requestId: options?.requestId
291
+ }
292
+ );
293
+ this.name = "QuotaExceededError";
294
+ this.quotaLimit = quotaLimit;
295
+ this.quotaUsed = quotaUsed;
296
+ this.resetsAt = resetsAt;
297
+ }
298
+ };
299
+ var NetworkError = class extends OptropicError {
300
+ constructor(message = "Network error occurred. Please check your connection.", options) {
301
+ super("NETWORK_ERROR", message, {
302
+ statusCode: void 0,
303
+ retryable: true,
304
+ details: options?.details,
305
+ requestId: options?.requestId,
306
+ cause: options?.cause
307
+ });
308
+ this.name = "NetworkError";
309
+ }
310
+ };
311
+ var TimeoutError = class extends OptropicError {
312
+ /**
313
+ * Timeout duration in milliseconds.
314
+ */
315
+ timeoutMs;
316
+ constructor(timeoutMs, options) {
317
+ super(
318
+ "TIMEOUT",
319
+ `Request timed out after ${timeoutMs}ms. Please try again.`,
320
+ {
321
+ statusCode: 408,
322
+ retryable: true,
323
+ details: { timeoutMs, ...options?.details },
324
+ requestId: options?.requestId
325
+ }
326
+ );
327
+ this.name = "TimeoutError";
328
+ this.timeoutMs = timeoutMs;
329
+ }
330
+ };
331
+ var ServiceUnavailableError = class extends OptropicError {
332
+ constructor(message = "Service temporarily unavailable. Please try again later.", options) {
333
+ super("SERVICE_UNAVAILABLE", message, {
334
+ statusCode: 503,
335
+ retryable: true,
336
+ details: { retryAfter: options?.retryAfter, ...options?.details },
337
+ requestId: options?.requestId
338
+ });
339
+ this.name = "ServiceUnavailableError";
340
+ }
341
+ };
342
+ function createErrorFromResponse(statusCode, body) {
343
+ const code = body.code ?? "UNKNOWN_ERROR";
344
+ const message = body.message ?? "An unknown error occurred";
345
+ const { details, requestId } = body;
346
+ switch (code) {
347
+ case "INVALID_API_KEY":
348
+ case "EXPIRED_API_KEY":
349
+ case "INSUFFICIENT_PERMISSIONS":
350
+ return new AuthenticationError(message, { code, details, requestId });
351
+ case "INVALID_SERIAL":
352
+ return new InvalidSerialError(
353
+ details?.serial ?? "",
354
+ message,
355
+ { details, requestId }
356
+ );
357
+ case "INVALID_GTIN":
358
+ return new InvalidGTINError(
359
+ details?.gtin ?? "",
360
+ message,
361
+ { details, requestId }
362
+ );
363
+ case "INVALID_CODE_FORMAT":
364
+ return new InvalidCodeError(
365
+ details?.code ?? "",
366
+ message,
367
+ { details, requestId }
368
+ );
369
+ case "CODE_NOT_FOUND":
370
+ return new CodeNotFoundError(
371
+ details?.code ?? "",
372
+ { details, requestId }
373
+ );
374
+ case "BATCH_NOT_FOUND":
375
+ return new BatchNotFoundError(
376
+ details?.batchId ?? "",
377
+ { details, requestId }
378
+ );
379
+ case "CODE_REVOKED":
380
+ return new RevokedCodeError(
381
+ details?.code ?? "",
382
+ details?.reason ?? "Unknown reason",
383
+ details?.revokedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
384
+ { details, requestId }
385
+ );
386
+ case "RATE_LIMITED":
387
+ return new RateLimitedError(
388
+ details?.retryAfter ?? 60,
389
+ {
390
+ limit: details?.limit,
391
+ remaining: details?.remaining,
392
+ details,
393
+ requestId
394
+ }
395
+ );
396
+ case "QUOTA_EXCEEDED":
397
+ return new QuotaExceededError(
398
+ details?.quotaLimit ?? 0,
399
+ details?.quotaUsed ?? 0,
400
+ details?.resetsAt ?? "",
401
+ { details, requestId }
402
+ );
403
+ case "NETWORK_ERROR":
404
+ return new NetworkError(message, { details, requestId });
405
+ case "TIMEOUT":
406
+ return new TimeoutError(
407
+ details?.timeoutMs ?? 3e4,
408
+ { details, requestId }
409
+ );
410
+ case "SERVICE_UNAVAILABLE":
411
+ return new ServiceUnavailableError(message, {
412
+ retryAfter: details?.retryAfter,
413
+ details,
414
+ requestId
415
+ });
416
+ default:
417
+ return new OptropicError(code, message, {
418
+ statusCode,
419
+ retryable: statusCode >= 500,
420
+ details,
421
+ requestId
422
+ });
423
+ }
424
+ }
425
+
426
+ // src/resources/assets.ts
427
+ var AssetsResource = class {
428
+ constructor(request) {
429
+ this.request = request;
430
+ }
431
+ async create(params) {
432
+ return this.request({ method: "POST", path: "/v1/assets", body: params });
433
+ }
434
+ async list(params) {
435
+ const query = params ? this.buildQuery(params) : "";
436
+ return this.request({ method: "GET", path: `/v1/assets${query}` });
437
+ }
438
+ async get(assetId) {
439
+ return this.request({ method: "GET", path: `/v1/assets/${encodeURIComponent(assetId)}` });
440
+ }
441
+ async verify(assetId) {
442
+ return this.request({ method: "GET", path: `/v1/assets/${encodeURIComponent(assetId)}/verify` });
443
+ }
444
+ async revoke(assetId, reason) {
445
+ return this.request({
446
+ method: "POST",
447
+ path: `/v1/assets/${encodeURIComponent(assetId)}/revoke`,
448
+ body: reason ? { reason } : void 0
449
+ });
450
+ }
451
+ async batchCreate(params) {
452
+ return this.request({ method: "POST", path: "/v1/assets/batch", body: params });
453
+ }
454
+ buildQuery(params) {
455
+ const entries = Object.entries(params).filter(([, v]) => v !== void 0);
456
+ if (entries.length === 0) return "";
457
+ return "?" + entries.map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`).join("&");
458
+ }
459
+ };
460
+
461
+ // src/resources/keys.ts
462
+ var KeysResource = class {
463
+ constructor(request) {
464
+ this.request = request;
465
+ }
466
+ async create(params) {
467
+ return this.request({ method: "POST", path: "/v1/keys", body: params });
468
+ }
469
+ async list() {
470
+ return this.request({ method: "GET", path: "/v1/keys" });
471
+ }
472
+ async revoke(keyId) {
473
+ await this.request({ method: "DELETE", path: `/v1/keys/${encodeURIComponent(keyId)}` });
474
+ }
475
+ };
476
+
477
+ // src/client.ts
478
+ var DEFAULT_BASE_URL = "https://api.optropic.com";
479
+ var DEFAULT_TIMEOUT = 3e4;
480
+ var SDK_VERSION = "1.0.0";
481
+ var DEFAULT_RETRY_CONFIG = {
482
+ maxRetries: 3,
483
+ baseDelay: 1e3,
484
+ maxDelay: 1e4
485
+ };
486
+ var OptropicClient = class {
487
+ config;
488
+ baseUrl;
489
+ retryConfig;
490
+ assets;
491
+ keys;
492
+ constructor(config) {
493
+ if (!config.apiKey || !this.isValidApiKey(config.apiKey)) {
494
+ throw new AuthenticationError(
495
+ "Invalid API key format. Expected format: optr_live_xxx or optr_test_xxx"
496
+ );
497
+ }
498
+ this.config = {
499
+ ...config,
500
+ timeout: config.timeout ?? DEFAULT_TIMEOUT
501
+ };
502
+ if (config.baseUrl) {
503
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
504
+ } else {
505
+ this.baseUrl = DEFAULT_BASE_URL;
506
+ }
507
+ this.retryConfig = {
508
+ ...DEFAULT_RETRY_CONFIG,
509
+ ...config.retry
510
+ };
511
+ const boundRequest = this.request.bind(this);
512
+ this.assets = new AssetsResource(boundRequest);
513
+ this.keys = new KeysResource(boundRequest);
514
+ }
515
+ // ─────────────────────────────────────────────────────────────────────────
516
+ // PRIVATE METHODS
517
+ // ─────────────────────────────────────────────────────────────────────────
518
+ isValidApiKey(apiKey) {
519
+ return /^optr_(live|test)_[a-zA-Z0-9_-]{20,}$/.test(apiKey);
520
+ }
521
+ async request(options) {
522
+ const { method, path, body, headers = {}, timeout = this.config.timeout } = options;
523
+ const url = `${this.baseUrl}${path}`;
524
+ const requestHeaders = {
525
+ "Content-Type": "application/json",
526
+ "Accept": "application/json",
527
+ "x-api-key": this.config.apiKey,
528
+ "X-SDK-Version": SDK_VERSION,
529
+ "X-SDK-Language": "typescript",
530
+ ...this.config.headers,
531
+ ...headers
532
+ };
533
+ let lastError = null;
534
+ let attempt = 0;
535
+ while (attempt <= this.retryConfig.maxRetries) {
536
+ try {
537
+ const response = await this.executeRequest(
538
+ url,
539
+ method,
540
+ requestHeaders,
541
+ body,
542
+ timeout
543
+ );
544
+ return response;
545
+ } catch (error) {
546
+ lastError = error;
547
+ if (error instanceof OptropicError && !error.retryable) {
548
+ throw error;
549
+ }
550
+ if (attempt >= this.retryConfig.maxRetries) {
551
+ throw error;
552
+ }
553
+ const delay = Math.min(
554
+ this.retryConfig.baseDelay * Math.pow(2, attempt),
555
+ this.retryConfig.maxDelay
556
+ );
557
+ if (error instanceof RateLimitedError) {
558
+ await this.sleep(error.retryAfter * 1e3);
559
+ } else {
560
+ await this.sleep(delay);
561
+ }
562
+ attempt++;
563
+ }
564
+ }
565
+ throw lastError ?? new OptropicError("UNKNOWN_ERROR", "Request failed");
566
+ }
567
+ async executeRequest(url, method, headers, body, timeout) {
568
+ const controller = new AbortController();
569
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
570
+ try {
571
+ const response = await fetch(url, {
572
+ method,
573
+ headers,
574
+ body: body ? JSON.stringify(body) : void 0,
575
+ signal: controller.signal
576
+ });
577
+ clearTimeout(timeoutId);
578
+ const requestId = response.headers.get("x-request-id") ?? "";
579
+ if (!response.ok) {
580
+ let errorBody;
581
+ try {
582
+ const json2 = await response.json();
583
+ const parsed = json2.error ?? json2;
584
+ errorBody = {
585
+ code: parsed?.code ?? "UNKNOWN_ERROR",
586
+ message: parsed?.message ?? `HTTP ${response.status}: ${response.statusText}`,
587
+ details: parsed?.details
588
+ };
589
+ } catch {
590
+ errorBody = {
591
+ code: "UNKNOWN_ERROR",
592
+ message: `HTTP ${response.status}: ${response.statusText}`
593
+ };
594
+ }
595
+ throw createErrorFromResponse(response.status, {
596
+ // Justified: Error code string from API may not match SDK's ErrorCode enum exactly
597
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
598
+ code: errorBody.code,
599
+ message: errorBody.message,
600
+ details: errorBody.details,
601
+ requestId
602
+ });
603
+ }
604
+ const json = await response.json();
605
+ if (json.error) {
606
+ throw createErrorFromResponse(response.status, {
607
+ // Justified: Error code string from API may not match SDK's ErrorCode enum exactly
608
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
609
+ code: json.error.code,
610
+ message: json.error.message,
611
+ details: json.error.details,
612
+ requestId: json.requestId
613
+ });
614
+ }
615
+ return json.data;
616
+ } catch (error) {
617
+ clearTimeout(timeoutId);
618
+ if (error instanceof OptropicError) {
619
+ throw error;
620
+ }
621
+ if (error instanceof Error) {
622
+ if (error.name === "AbortError") {
623
+ throw new TimeoutError(timeout);
624
+ }
625
+ throw new NetworkError(error.message, { cause: error });
626
+ }
627
+ throw new OptropicError("UNKNOWN_ERROR", "An unexpected error occurred", {
628
+ retryable: false
629
+ });
630
+ }
631
+ }
632
+ sleep(ms) {
633
+ return new Promise((resolve) => setTimeout(resolve, ms));
634
+ }
635
+ };
636
+ function createClient(config) {
637
+ return new OptropicClient(config);
638
+ }
639
+
640
+ // src/index.ts
641
+ var SDK_VERSION2 = "1.0.0";
642
+ // Annotate the CommonJS export names for ESM import in node:
643
+ 0 && (module.exports = {
644
+ AssetsResource,
645
+ AuthenticationError,
646
+ BatchNotFoundError,
647
+ CodeNotFoundError,
648
+ InvalidCodeError,
649
+ InvalidGTINError,
650
+ InvalidSerialError,
651
+ KeysResource,
652
+ NetworkError,
653
+ OptropicClient,
654
+ OptropicError,
655
+ QuotaExceededError,
656
+ RateLimitedError,
657
+ RevokedCodeError,
658
+ SDK_VERSION,
659
+ ServiceUnavailableError,
660
+ TimeoutError,
661
+ createClient
662
+ });