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