solometrics 0.0.0-stage → 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/dist/index.cjs ADDED
@@ -0,0 +1,1163 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ APIConnectionError: () => APIConnectionError,
34
+ APIError: () => APIError,
35
+ APIStatusError: () => APIStatusError,
36
+ APITimeoutError: () => APITimeoutError,
37
+ APIUserAbortError: () => APIUserAbortError,
38
+ AuthenticationError: () => AuthenticationError,
39
+ BadRequestError: () => BadRequestError,
40
+ Client: () => Client,
41
+ ConflictError: () => ConflictError,
42
+ GoatMarketResource: () => GoatMarketResource,
43
+ GoatResource: () => GoatResource,
44
+ InternalServerError: () => InternalServerError,
45
+ InventoryResource: () => InventoryResource,
46
+ JobTimeoutError: () => JobTimeoutError,
47
+ NotFoundError: () => NotFoundError,
48
+ PermissionDeniedError: () => PermissionDeniedError,
49
+ RateLimitError: () => RateLimitError,
50
+ SolometricsError: () => SolometricsError,
51
+ StockXCatalogResource: () => StockXCatalogResource,
52
+ StockXCostsResource: () => StockXCostsResource,
53
+ StockXListingsResource: () => StockXListingsResource,
54
+ StockXMarketResource: () => StockXMarketResource,
55
+ StockXOrdersResource: () => StockXOrdersResource,
56
+ StockXResource: () => StockXResource,
57
+ UnprocessableEntityError: () => UnprocessableEntityError,
58
+ UpstreamError: () => UpstreamError,
59
+ UpstreamTimeoutError: () => UpstreamTimeoutError,
60
+ VERSION: () => VERSION
61
+ });
62
+ module.exports = __toCommonJS(index_exports);
63
+
64
+ // src/errors.ts
65
+ var SolometricsError = class extends Error {
66
+ constructor(message, options) {
67
+ super(message, options);
68
+ this.name = new.target.name;
69
+ }
70
+ };
71
+ var APIError = class extends SolometricsError {
72
+ };
73
+ var APIConnectionError = class extends APIError {
74
+ constructor(message = "Connection error.", options) {
75
+ super(message, options);
76
+ }
77
+ };
78
+ var APITimeoutError = class extends APIConnectionError {
79
+ constructor(options) {
80
+ super("Request timed out.", options);
81
+ }
82
+ };
83
+ var APIUserAbortError = class extends APIError {
84
+ constructor(options) {
85
+ super("Request was aborted.", options);
86
+ }
87
+ };
88
+ var APIStatusError = class extends APIError {
89
+ status;
90
+ detail;
91
+ body;
92
+ headers;
93
+ constructor(message, init) {
94
+ super(`${init.status}: ${message}`);
95
+ this.status = init.status;
96
+ this.headers = init.headers;
97
+ this.body = init.body;
98
+ this.detail = isRecord(init.body) && "detail" in init.body ? init.body.detail : null;
99
+ }
100
+ };
101
+ var BadRequestError = class extends APIStatusError {
102
+ };
103
+ var AuthenticationError = class extends APIStatusError {
104
+ };
105
+ var PermissionDeniedError = class extends APIStatusError {
106
+ };
107
+ var NotFoundError = class extends APIStatusError {
108
+ };
109
+ var ConflictError = class extends APIStatusError {
110
+ };
111
+ var UnprocessableEntityError = class extends APIStatusError {
112
+ get errors() {
113
+ return Array.isArray(this.detail) ? this.detail : [];
114
+ }
115
+ };
116
+ var RateLimitError = class extends APIStatusError {
117
+ get retryAfter() {
118
+ return parseRetryAfter(this.headers);
119
+ }
120
+ };
121
+ var InternalServerError = class extends APIStatusError {
122
+ };
123
+ var UpstreamError = class extends InternalServerError {
124
+ };
125
+ var UpstreamTimeoutError = class extends InternalServerError {
126
+ };
127
+ var JobTimeoutError = class extends SolometricsError {
128
+ taskId;
129
+ lastStatus;
130
+ constructor(taskId, lastStatus) {
131
+ const status = isRecord(lastStatus) ? lastStatus.status : lastStatus;
132
+ super(`Job ${taskId} did not finish in time (last status: ${String(status)})`);
133
+ this.taskId = taskId;
134
+ this.lastStatus = lastStatus;
135
+ }
136
+ };
137
+ var STATUS_MAP = {
138
+ 400: BadRequestError,
139
+ 401: AuthenticationError,
140
+ 403: PermissionDeniedError,
141
+ 404: NotFoundError,
142
+ 409: ConflictError,
143
+ 422: UnprocessableEntityError,
144
+ 429: RateLimitError,
145
+ 502: UpstreamError,
146
+ 504: UpstreamTimeoutError
147
+ };
148
+ function parseRetryAfter(headers) {
149
+ const raw = headers.get("retry-after");
150
+ if (raw === null || raw.trim() === "") return null;
151
+ const value = Number(raw);
152
+ return Number.isFinite(value) && value >= 0 ? value : null;
153
+ }
154
+ function makeStatusError(status, statusText, headers, text) {
155
+ let body;
156
+ try {
157
+ body = JSON.parse(text);
158
+ } catch {
159
+ body = text || null;
160
+ }
161
+ const detail = isRecord(body) ? body.detail : void 0;
162
+ let message;
163
+ if (typeof detail === "string") {
164
+ message = detail;
165
+ } else if (Array.isArray(detail)) {
166
+ const parts = detail.map((err) => {
167
+ if (isRecord(err)) {
168
+ const loc = Array.isArray(err.loc) ? err.loc.map(String).join(".") : "";
169
+ return loc ? `${loc}: ${String(err.msg)}` : String(err.msg);
170
+ }
171
+ return String(err);
172
+ });
173
+ message = parts.join("; ") || "Validation error";
174
+ } else if (typeof body === "string" && body) {
175
+ message = body.slice(0, 500);
176
+ } else {
177
+ message = statusText || "API error";
178
+ }
179
+ const cls = STATUS_MAP[status] ?? (status >= 500 ? InternalServerError : APIStatusError);
180
+ return new cls(message, { status, headers, body });
181
+ }
182
+ function isRecord(value) {
183
+ return typeof value === "object" && value !== null && !Array.isArray(value);
184
+ }
185
+
186
+ // src/core.ts
187
+ var DEFAULT_BASE_URL = "https://api.sneakers.itbanque.com";
188
+ var DEFAULT_TIMEOUT = 6e4;
189
+ var DEFAULT_MAX_RETRIES = 2;
190
+ var API_KEY_ENV = "SOLOMETRICS_API_KEY";
191
+ var BASE_URL_ENV = "SOLOMETRICS_BASE_URL";
192
+ var TERMINAL_JOB_STATUSES = /* @__PURE__ */ new Set(["SUCCESS", "FAILURE", "CANCELLED"]);
193
+ var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "PUT", "PATCH", "DELETE"]);
194
+ var MAX_BACKOFF_MS = 8e3;
195
+ var MAX_RETRY_AFTER_MS = 6e4;
196
+ function readEnv(name) {
197
+ const value = typeof process !== "undefined" ? process.env?.[name] : void 0;
198
+ return value || void 0;
199
+ }
200
+ function formatAmount(amount) {
201
+ if (typeof amount === "boolean") throw new TypeError("amount must be a number or string, not boolean");
202
+ if (typeof amount === "number" && !Number.isFinite(amount)) throw new TypeError(`Invalid amount: ${amount}`);
203
+ return String(amount);
204
+ }
205
+ function formatSize(size) {
206
+ return String(size);
207
+ }
208
+ function formatDate(value) {
209
+ return value instanceof Date ? value.toISOString().slice(0, 10) : value;
210
+ }
211
+ function formatDateTime(value) {
212
+ return value instanceof Date ? value.toISOString() : value;
213
+ }
214
+ function pathSegment(value) {
215
+ return encodeURIComponent(String(value));
216
+ }
217
+ function queryString(value) {
218
+ if (typeof value === "boolean") return value ? "true" : "false";
219
+ if (value instanceof Date) return formatDate(value);
220
+ return String(value);
221
+ }
222
+ function buildURL(baseURL, path, query) {
223
+ const params = new URLSearchParams();
224
+ for (const [key, value] of Object.entries(query ?? {})) {
225
+ if (value === void 0 || value === null) continue;
226
+ if (Array.isArray(value)) {
227
+ for (const item of value) params.append(key, queryString(item));
228
+ } else {
229
+ params.append(key, queryString(value));
230
+ }
231
+ }
232
+ const qs = params.toString();
233
+ return `${baseURL}${path}${qs ? `?${qs}` : ""}`;
234
+ }
235
+ function cleanBody(body) {
236
+ const out = {};
237
+ for (const [key, value] of Object.entries(body)) {
238
+ if (value !== void 0 && value !== null) out[key] = value;
239
+ }
240
+ return out;
241
+ }
242
+ function joinStatuses(statuses) {
243
+ if (statuses === void 0 || statuses === null) return void 0;
244
+ return typeof statuses === "string" ? statuses : statuses.join(",");
245
+ }
246
+ function normalizeBatchDeleteItems(items) {
247
+ const out = [];
248
+ for (const item of items) {
249
+ if (typeof item === "string") {
250
+ out.push({ listingId: item });
251
+ continue;
252
+ }
253
+ let listingId;
254
+ let variantId;
255
+ if (Array.isArray(item)) {
256
+ [listingId, variantId] = item;
257
+ } else if (typeof item === "object" && item !== null) {
258
+ const obj = item;
259
+ listingId = obj.listingId ?? obj.listing_id;
260
+ variantId = obj.variantId ?? obj.variant_id;
261
+ } else {
262
+ throw new TypeError(`Unsupported batch-delete item: ${JSON.stringify(item)}`);
263
+ }
264
+ if (!listingId) throw new TypeError(`Batch-delete item is missing a listing ID: ${JSON.stringify(item)}`);
265
+ const entry = { listingId: String(listingId) };
266
+ if (variantId) entry.variantId = String(variantId);
267
+ out.push(entry);
268
+ }
269
+ if (out.length === 0) throw new TypeError("`items` is empty.");
270
+ return out;
271
+ }
272
+ var encoder = new TextEncoder();
273
+ function isRowsSource(source) {
274
+ return typeof source === "object" && source !== null && !(source instanceof Uint8Array) && !(source instanceof ArrayBuffer) && !(typeof Blob !== "undefined" && source instanceof Blob) && "rows" in source;
275
+ }
276
+ function basename(path) {
277
+ return path.split(/[\\/]/).pop() || path;
278
+ }
279
+ async function collect(chunks) {
280
+ const parts = [];
281
+ let length = 0;
282
+ for await (const chunk of chunks) {
283
+ const bytes = typeof chunk === "string" ? encoder.encode(chunk) : new Uint8Array(chunk);
284
+ parts.push(bytes);
285
+ length += bytes.length;
286
+ }
287
+ const out = new Uint8Array(length);
288
+ let offset = 0;
289
+ for (const part of parts) {
290
+ out.set(part, offset);
291
+ offset += part.length;
292
+ }
293
+ return out;
294
+ }
295
+ async function* readWebStream(stream) {
296
+ const reader = stream.getReader();
297
+ try {
298
+ for (; ; ) {
299
+ const { done, value } = await reader.read();
300
+ if (done) return;
301
+ yield value;
302
+ }
303
+ } finally {
304
+ reader.releaseLock();
305
+ }
306
+ }
307
+ async function prepareCsvUpload(source, defaultName) {
308
+ if (source === void 0 || source === null) {
309
+ throw new TypeError("Pass a CSV file (path, bytes, Blob or stream) or `{ rows }`.");
310
+ }
311
+ let name = defaultName;
312
+ let content;
313
+ if (typeof source === "string") {
314
+ const { readFile } = await import("fs/promises");
315
+ content = new Uint8Array(await readFile(source));
316
+ name = basename(source);
317
+ } else if (source instanceof Uint8Array) {
318
+ content = source;
319
+ } else if (source instanceof ArrayBuffer) {
320
+ content = new Uint8Array(source);
321
+ } else if (typeof Blob !== "undefined" && source instanceof Blob) {
322
+ content = new Uint8Array(await source.arrayBuffer());
323
+ const fileName = source.name;
324
+ if (typeof fileName === "string" && fileName) name = basename(fileName);
325
+ } else if (isRowsSource(source)) {
326
+ content = rowsToCsv(source.rows);
327
+ } else if (typeof source.getReader === "function") {
328
+ content = await collect(readWebStream(source));
329
+ } else if (typeof source[Symbol.asyncIterator] === "function") {
330
+ content = await collect(source);
331
+ const path = source.path;
332
+ if (typeof path === "string" && path) name = basename(path);
333
+ } else {
334
+ throw new TypeError(`Unsupported CSV source: ${Object.prototype.toString.call(source)}`);
335
+ }
336
+ if (!name.toLowerCase().endsWith(".csv")) name = `${name}.csv`;
337
+ return { name, content };
338
+ }
339
+ function csvField(value) {
340
+ if (value === void 0 || value === null) return "";
341
+ const text = value instanceof Date ? value.toISOString() : String(value);
342
+ return /[",\r\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
343
+ }
344
+ function rowsToCsv(rows) {
345
+ const list = Array.from(rows);
346
+ if (list.length === 0) throw new TypeError("`rows` is empty.");
347
+ const header = [];
348
+ const seen = /* @__PURE__ */ new Set();
349
+ for (const row of list) {
350
+ for (const key of Object.keys(row)) {
351
+ if (!seen.has(key)) {
352
+ seen.add(key);
353
+ header.push(key);
354
+ }
355
+ }
356
+ }
357
+ const lines = [header.map(csvField).join(",")];
358
+ for (const row of list) lines.push(header.map((key) => csvField(row[key])).join(","));
359
+ return encoder.encode(`${lines.join("\n")}
360
+ `);
361
+ }
362
+ var CONNECT_ERROR_CODES = /* @__PURE__ */ new Set([
363
+ "ECONNREFUSED",
364
+ "ENOTFOUND",
365
+ "EAI_AGAIN",
366
+ "EHOSTUNREACH",
367
+ "ENETUNREACH",
368
+ "EADDRNOTAVAIL",
369
+ "UND_ERR_CONNECT_TIMEOUT"
370
+ ]);
371
+ function errorCodes(err, depth = 0) {
372
+ if (typeof err !== "object" || err === null || depth > 3) return [];
373
+ const e = err;
374
+ const codes = typeof e.code === "string" ? [e.code] : [];
375
+ if (Array.isArray(e.errors)) for (const inner of e.errors) codes.push(...errorCodes(inner, depth + 1));
376
+ codes.push(...errorCodes(e.cause, depth + 1));
377
+ return codes;
378
+ }
379
+ function classifyNetworkError(err) {
380
+ const codes = errorCodes(err);
381
+ if (codes.length > 0 && codes.every((code) => CONNECT_ERROR_CODES.has(code))) return "connect";
382
+ return "network";
383
+ }
384
+ function shouldRetryStatus(method, status) {
385
+ if (status === 429) return true;
386
+ if (status === 502 || status === 503 || status === 504) return IDEMPOTENT_METHODS.has(method.toUpperCase());
387
+ return false;
388
+ }
389
+ function shouldRetryNetworkError(method, kind) {
390
+ if (kind === "connect") return true;
391
+ return IDEMPOTENT_METHODS.has(method.toUpperCase());
392
+ }
393
+ function retryDelay(attempt, retryAfterSeconds) {
394
+ if (retryAfterSeconds !== null) return Math.min(retryAfterSeconds * 1e3, MAX_RETRY_AFTER_MS);
395
+ const base = Math.min(500 * 2 ** attempt, MAX_BACKOFF_MS);
396
+ return base * (0.75 + Math.random() * 0.5);
397
+ }
398
+ function jobFinished(payload) {
399
+ const status = typeof payload === "object" && payload !== null ? payload.status : void 0;
400
+ return typeof status === "string" && TERMINAL_JOB_STATUSES.has(status.toUpperCase());
401
+ }
402
+ function defaultSleep(ms, signal) {
403
+ return new Promise((resolve, reject) => {
404
+ if (signal?.aborted) {
405
+ reject(new APIUserAbortError({ cause: signal.reason }));
406
+ return;
407
+ }
408
+ const onAbort = () => {
409
+ clearTimeout(timer);
410
+ reject(new APIUserAbortError({ cause: signal?.reason }));
411
+ };
412
+ const timer = setTimeout(
413
+ () => {
414
+ signal?.removeEventListener("abort", onAbort);
415
+ resolve();
416
+ },
417
+ Math.max(0, ms)
418
+ );
419
+ signal?.addEventListener("abort", onAbort, { once: true });
420
+ });
421
+ }
422
+ var timing = {
423
+ sleep: defaultSleep
424
+ };
425
+
426
+ // src/resources/shared.ts
427
+ var APIResource = class {
428
+ _client;
429
+ constructor(client) {
430
+ this._client = client;
431
+ }
432
+ _conn(connectionId) {
433
+ return connectionId ?? this._client.connectionId;
434
+ }
435
+ _get(path, query, options) {
436
+ return this._client._request({ ...requestOptions(options), method: "GET", path, query });
437
+ }
438
+ _send(method, path, init = {}, options) {
439
+ const final = { ...requestOptions(options), method, path, ...init };
440
+ return this._client._request(final);
441
+ }
442
+ async _wait(fetchStatus, taskId, { pollInterval = 5e3, timeout = null, onProgress, signal } = {}) {
443
+ const deadline = timeout === null ? null : performance.now() + timeout;
444
+ for (; ; ) {
445
+ const status = await fetchStatus({ signal });
446
+ if (onProgress) await onProgress(status);
447
+ if (jobFinished(status)) return status;
448
+ if (deadline !== null && performance.now() + pollInterval > deadline) throw new JobTimeoutError(taskId, status);
449
+ await timing.sleep(pollInterval, signal);
450
+ }
451
+ }
452
+ };
453
+ function requestOptions(options) {
454
+ if (!options) return {};
455
+ const out = {};
456
+ if (options.timeout !== void 0) out.timeout = options.timeout;
457
+ if (options.maxRetries !== void 0) out.maxRetries = options.maxRetries;
458
+ if (options.signal !== void 0) out.signal = options.signal;
459
+ if (options.headers !== void 0) out.headers = options.headers;
460
+ return out;
461
+ }
462
+
463
+ // src/resources/goat.ts
464
+ var GoatMarketResource = class extends APIResource {
465
+ /**
466
+ * `GET /v2/goat/market/search` — per-size GOAT prices. Needs a linked GOAT
467
+ * account (`ConflictError` otherwise).
468
+ */
469
+ search(styleId, options = {}) {
470
+ return this._get("/v2/goat/market/search", { style_id: styleId }, options);
471
+ }
472
+ };
473
+ var GoatResource = class extends APIResource {
474
+ market;
475
+ constructor(client) {
476
+ super(client);
477
+ this.market = new GoatMarketResource(client);
478
+ }
479
+ };
480
+
481
+ // src/resources/inventory.ts
482
+ var InventoryResource = class extends APIResource {
483
+ /** `GET /v1/inventory/search` — one page of SKUs with stock per location. */
484
+ search(params = {}) {
485
+ return this._get(
486
+ "/v1/inventory/search",
487
+ {
488
+ q: params.q,
489
+ style_color: params.styleColor,
490
+ brand: params.brand,
491
+ retailer: params.retailer,
492
+ size: params.size === void 0 || params.size === null ? void 0 : formatSize(params.size),
493
+ retailer_sku: params.retailerSku,
494
+ supplier_sku: params.supplierSku,
495
+ limit: params.limit,
496
+ offset: params.offset
497
+ },
498
+ params
499
+ );
500
+ }
501
+ /**
502
+ * Iterate every matching SKU across pages.
503
+ *
504
+ * Stops at the last page. Without provider access for `retailer` the plan's
505
+ * `max_page_number` cap applies, and going past it throws `BadRequestError`.
506
+ */
507
+ async *iterSearch(params = {}) {
508
+ const { pageSize, offset: start, ...rest } = params;
509
+ let offset = start ?? 0;
510
+ for (; ; ) {
511
+ const page = await this.search({ ...rest, limit: pageSize, offset });
512
+ const items = page.items ?? [];
513
+ yield* items;
514
+ if (!page.has_next_page || items.length === 0) return;
515
+ offset += items.length;
516
+ }
517
+ }
518
+ /**
519
+ * `GET /v1/reporting/retailers/{retailer}/skus/{retailerSku}/history` —
520
+ * stock history of one SKU, newest first.
521
+ */
522
+ history(retailer, retailerSku, params = {}) {
523
+ return this._get(
524
+ `/v1/reporting/retailers/${pathSegment(retailer)}/skus/${pathSegment(retailerSku)}/history`,
525
+ { hours: params.hours },
526
+ params
527
+ );
528
+ }
529
+ };
530
+
531
+ // src/resources/stockx/catalog.ts
532
+ var StockXCatalogResource = class extends APIResource {
533
+ /**
534
+ * `GET /v2/stockx/catalog/search` — StockX catalog search (usually by style
535
+ * code), returned as received from StockX.
536
+ */
537
+ search(q, params = {}) {
538
+ return this._get(
539
+ "/v2/stockx/catalog/search",
540
+ {
541
+ q,
542
+ page_number: params.pageNumber,
543
+ page_size: params.pageSize,
544
+ connection_id: this._conn(params.connectionId)
545
+ },
546
+ params
547
+ );
548
+ }
549
+ /**
550
+ * `GET /v2/stockx/catalog/products/{productId}/variants` — every size variant
551
+ * of a product, as received from StockX.
552
+ */
553
+ variants(productId, params = {}) {
554
+ return this._get(
555
+ `/v2/stockx/catalog/products/${pathSegment(productId)}/variants`,
556
+ { style_id: params.styleId, connection_id: this._conn(params.connectionId) },
557
+ params
558
+ );
559
+ }
560
+ };
561
+
562
+ // src/resources/stockx/costs.ts
563
+ var StockXCostsResource = class extends APIResource {
564
+ /** `PATCH /v2/stockx/cost` — create or overwrite the cost for one style/size/variant. Basic plan. */
565
+ set(styleId, size, variantId, cost, params = {}) {
566
+ return this._send(
567
+ "PATCH",
568
+ "/v2/stockx/cost",
569
+ {
570
+ query: { connection_id: this._conn(params.connectionId) },
571
+ body: { styleId, size: formatSize(size), variantId, cost }
572
+ },
573
+ params
574
+ );
575
+ }
576
+ /** `GET /v2/stockx/costs` — cost records, newest first. */
577
+ list(params = {}) {
578
+ return this._get(
579
+ "/v2/stockx/costs",
580
+ {
581
+ page: params.page,
582
+ page_size: params.pageSize,
583
+ style_id: params.styleId,
584
+ connection_id: this._conn(params.connectionId)
585
+ },
586
+ params
587
+ );
588
+ }
589
+ /** Iterate every cost record across pages. */
590
+ async *iterList(params = {}) {
591
+ const { pageSize = 200, ...rest } = params;
592
+ let seen = 0;
593
+ for (let page = 1; ; page++) {
594
+ const data = await this.list({ ...rest, page, pageSize });
595
+ const items = data.items ?? [];
596
+ yield* items;
597
+ seen += items.length;
598
+ if (items.length === 0 || seen >= Number(data.total ?? 0)) return;
599
+ }
600
+ }
601
+ /**
602
+ * `POST /v2/stockx/costs/import` — upsert costs from a CSV with
603
+ * `style_id, size, variant_id, cost`. Synchronous. Advanced plan.
604
+ */
605
+ async importCsv(source, params = {}) {
606
+ const file = await prepareCsvUpload(source, "costs.csv");
607
+ return this._send(
608
+ "POST",
609
+ "/v2/stockx/costs/import",
610
+ { query: { connection_id: this._conn(params.connectionId) }, file },
611
+ params
612
+ );
613
+ }
614
+ /** `PATCH /v2/stockx/costs/{costId}` — edit one record. Basic plan. */
615
+ update(costId, params = {}) {
616
+ return this._send(
617
+ "PATCH",
618
+ `/v2/stockx/costs/${pathSegment(costId)}`,
619
+ {
620
+ query: { connection_id: this._conn(params.connectionId) },
621
+ body: cleanBody({ cost: params.cost, is_active: params.isActive })
622
+ },
623
+ params
624
+ );
625
+ }
626
+ /** `DELETE /v2/stockx/costs` — delete EVERY cost record on the connection. Cannot be undone. Basic plan. */
627
+ clear(params = {}) {
628
+ return this._send(
629
+ "DELETE",
630
+ "/v2/stockx/costs",
631
+ { query: { connection_id: this._conn(params.connectionId) } },
632
+ params
633
+ );
634
+ }
635
+ };
636
+
637
+ // src/resources/stockx/listings.ts
638
+ var StockXListingsResource = class extends APIResource {
639
+ // -------------------------------------------------------------- single items
640
+ /**
641
+ * `POST /v2/stockx/simple-listing` — list by style code and size.
642
+ *
643
+ * The backend resolves the variant. Throws `NotFoundError` if no variant
644
+ * matches and `ConflictError` when keep-listing mode skips it. Requires the
645
+ * basic plan.
646
+ */
647
+ async createBySku(styleId, size, amount, params = {}) {
648
+ return this._send(
649
+ "POST",
650
+ "/v2/stockx/simple-listing",
651
+ {
652
+ query: { connection_id: this._conn(params.connectionId) },
653
+ body: cleanBody({
654
+ styleId,
655
+ size: formatSize(size),
656
+ amount: formatAmount(amount),
657
+ cost: params.cost,
658
+ currencyCode: params.currencyCode,
659
+ expiresAt: params.expiresAt == null ? void 0 : formatDateTime(params.expiresAt),
660
+ active: params.active,
661
+ inventoryType: params.inventoryType
662
+ })
663
+ },
664
+ params
665
+ );
666
+ }
667
+ /**
668
+ * `POST /v2/stockx/listings` — list a specific `variantId`.
669
+ *
670
+ * To store a cost record too, pass `cost`, `styleId` and `size` together (the
671
+ * API silently skips the cost if one is missing, so the SDK throws a
672
+ * `TypeError` instead). Requires the basic plan.
673
+ */
674
+ async create(variantId, amount, params = {}) {
675
+ if (params.cost != null && (params.styleId == null || params.size == null)) {
676
+ throw new TypeError("`cost` is only stored together with `styleId` and `size`; pass all three.");
677
+ }
678
+ return this._send(
679
+ "POST",
680
+ "/v2/stockx/listings",
681
+ {
682
+ query: { connection_id: this._conn(params.connectionId) },
683
+ body: cleanBody({
684
+ variantId,
685
+ amount: formatAmount(amount),
686
+ currencyCode: params.currencyCode,
687
+ expiresAt: params.expiresAt == null ? void 0 : formatDateTime(params.expiresAt),
688
+ active: params.active,
689
+ inventoryType: params.inventoryType,
690
+ cost: params.cost,
691
+ styleId: params.styleId,
692
+ size: params.size == null ? void 0 : formatSize(params.size)
693
+ })
694
+ },
695
+ params
696
+ );
697
+ }
698
+ /** `PATCH /v2/stockx/listings/{listingId}` — reprice or change expiry. Requires the basic plan. */
699
+ async update(listingId, params = {}) {
700
+ return this._send(
701
+ "PATCH",
702
+ `/v2/stockx/listings/${pathSegment(listingId)}`,
703
+ {
704
+ query: { connection_id: this._conn(params.connectionId) },
705
+ body: cleanBody({
706
+ amount: params.amount == null ? void 0 : formatAmount(params.amount),
707
+ currencyCode: params.currencyCode,
708
+ expiresAt: params.expiresAt == null ? void 0 : formatDateTime(params.expiresAt)
709
+ })
710
+ },
711
+ params
712
+ );
713
+ }
714
+ /**
715
+ * `GET /v2/stockx/listings` — your listings, fetched live from StockX.
716
+ * ACTIVE listings carry `cost` and `netPrice`.
717
+ */
718
+ list(params = {}) {
719
+ return this._get(
720
+ "/v2/stockx/listings",
721
+ {
722
+ page_number: params.pageNumber,
723
+ page_size: params.pageSize,
724
+ listing_statuses: joinStatuses(params.listingStatuses),
725
+ sku: params.sku,
726
+ connection_id: this._conn(params.connectionId)
727
+ },
728
+ params
729
+ );
730
+ }
731
+ /** `GET /v2/stockx/listings/{listingId}` — one listing. */
732
+ get(listingId, params = {}) {
733
+ return this._get(
734
+ `/v2/stockx/listings/${pathSegment(listingId)}`,
735
+ { connection_id: this._conn(params.connectionId) },
736
+ params
737
+ );
738
+ }
739
+ /**
740
+ * `GET /v2/stockx/listings/{listingId}/operations/{operationId}` — result of
741
+ * an asynchronous StockX operation.
742
+ */
743
+ getOperation(listingId, operationId, params = {}) {
744
+ return this._get(
745
+ `/v2/stockx/listings/${pathSegment(listingId)}/operations/${pathSegment(operationId)}`,
746
+ { connection_id: this._conn(params.connectionId) },
747
+ params
748
+ );
749
+ }
750
+ /** `PUT /v2/stockx/listings/{listingId}/deactivate` — take off market, keep the listing. Basic plan. */
751
+ deactivate(listingId, params = {}) {
752
+ return this._send(
753
+ "PUT",
754
+ `/v2/stockx/listings/${pathSegment(listingId)}/deactivate`,
755
+ { query: { connection_id: this._conn(params.connectionId) } },
756
+ params
757
+ );
758
+ }
759
+ /**
760
+ * `PUT /v2/stockx/listings/{listingId}/activate` — put back on market,
761
+ * optionally at a new `amount`. Basic plan.
762
+ */
763
+ async activate(listingId, params = {}) {
764
+ return this._send(
765
+ "PUT",
766
+ `/v2/stockx/listings/${pathSegment(listingId)}/activate`,
767
+ {
768
+ query: { connection_id: this._conn(params.connectionId) },
769
+ body: params.amount == null ? void 0 : { amount: formatAmount(params.amount) }
770
+ },
771
+ params
772
+ );
773
+ }
774
+ /**
775
+ * `DELETE /v2/stockx/listings/{listingId}` — permanent. With `variantId` the
776
+ * variant's cost records are deleted too. Basic plan.
777
+ */
778
+ delete(listingId, params = {}) {
779
+ return this._send(
780
+ "DELETE",
781
+ `/v2/stockx/listings/${pathSegment(listingId)}`,
782
+ { query: { variant_id: params.variantId, connection_id: this._conn(params.connectionId) } },
783
+ params
784
+ );
785
+ }
786
+ /**
787
+ * `POST /v2/stockx/batch/delete-listing` — delete several listings in one
788
+ * StockX batch. Advanced plan.
789
+ *
790
+ * `items` may be listing ID strings, `[listingId, variantId]` tuples, or
791
+ * objects with `listingId`/`listing_id` and optional `variantId`/`variant_id`.
792
+ */
793
+ async batchDelete(items, params = {}) {
794
+ const normalized = normalizeBatchDeleteItems(items);
795
+ return this._send(
796
+ "POST",
797
+ "/v2/stockx/batch/delete-listing",
798
+ { query: { connection_id: this._conn(params.connectionId) }, body: { items: normalized } },
799
+ params
800
+ );
801
+ }
802
+ // -------------------------------------------------------------- CSV upload
803
+ /**
804
+ * `POST /v2/stockx/listings/upload` — start a bulk listing job.
805
+ *
806
+ * Pass a CSV (path, bytes, Blob or stream) or `{ rows }` with
807
+ * `sku, size, quantity, amount, cost`. Returns a `taskId`; poll with
808
+ * {@link uploadStatus} or {@link waitForUpload}. Advanced plan.
809
+ */
810
+ async upload(source, params = {}) {
811
+ const file = await prepareCsvUpload(source, "listings.csv");
812
+ return this._send("POST", "/v2/stockx/listings/upload", { query: { batch_size: params.batchSize }, file }, params);
813
+ }
814
+ /**
815
+ * `POST /v2/stockx/listings/upload/validate` — count valid, invalid and
816
+ * duplicate rows without listing anything.
817
+ */
818
+ async validateUpload(source, options = {}) {
819
+ const file = await prepareCsvUpload(source, "listings.csv");
820
+ return this._send("POST", "/v2/stockx/listings/upload/validate", { file }, options);
821
+ }
822
+ /** `GET /v2/stockx/listings/upload/{taskId}` */
823
+ uploadStatus(taskId, options = {}) {
824
+ return this._get(`/v2/stockx/listings/upload/${pathSegment(taskId)}`, void 0, options);
825
+ }
826
+ /**
827
+ * Poll {@link uploadStatus} until the job is SUCCESS, FAILURE or CANCELLED,
828
+ * and return the final status. Throws `JobTimeoutError` after `timeout` ms.
829
+ */
830
+ waitForUpload(taskId, options = {}) {
831
+ return this._wait((o) => this.uploadStatus(taskId, o), taskId, options);
832
+ }
833
+ /** `GET /v2/stockx/listings/upload/unmatched` — uploaded rows that could not be matched to a variant. */
834
+ unmatched(options = {}) {
835
+ return this._get("/v2/stockx/listings/upload/unmatched", void 0, options);
836
+ }
837
+ // -------------------------------------------------------------- CSV unlist
838
+ /**
839
+ * `POST /v2/stockx/listings/unlist` — start a bulk delist job.
840
+ *
841
+ * Rows carry `listing_id` (optionally `variant_id`), or `sku` (or `style_id`)
842
+ * plus `size` to match ACTIVE listings. Advanced plan.
843
+ */
844
+ async unlist(source, options = {}) {
845
+ const file = await prepareCsvUpload(source, "unlist.csv");
846
+ return this._send("POST", "/v2/stockx/listings/unlist", { file }, options);
847
+ }
848
+ /** `GET /v2/stockx/listings/unlist/{taskId}` */
849
+ unlistStatus(taskId, options = {}) {
850
+ return this._get(`/v2/stockx/listings/unlist/${pathSegment(taskId)}`, void 0, options);
851
+ }
852
+ /** Poll {@link unlistStatus} until the job finishes. */
853
+ waitForUnlist(taskId, options = {}) {
854
+ return this._wait((o) => this.unlistStatus(taskId, o), taskId, options);
855
+ }
856
+ /** `POST /v2/stockx/listings/unlist/cancel` — cancel your running unlist job. */
857
+ cancelUnlist(options = {}) {
858
+ return this._send("POST", "/v2/stockx/listings/unlist/cancel", {}, options);
859
+ }
860
+ // -------------------------------------------------------------- clear all
861
+ /**
862
+ * `POST /v2/stockx/listings/clear-all` — delete EVERY active listing in the
863
+ * account's `clear_all_scope`. Cannot be undone. Advanced plan.
864
+ */
865
+ clearAll(options = {}) {
866
+ return this._send("POST", "/v2/stockx/listings/clear-all", {}, options);
867
+ }
868
+ /** `GET /v2/stockx/listings/clear-all/{taskId}` */
869
+ clearAllStatus(taskId, options = {}) {
870
+ return this._get(`/v2/stockx/listings/clear-all/${pathSegment(taskId)}`, void 0, options);
871
+ }
872
+ /** Poll {@link clearAllStatus} until the job finishes. */
873
+ waitForClearAll(taskId, options = {}) {
874
+ return this._wait((o) => this.clearAllStatus(taskId, o), taskId, options);
875
+ }
876
+ };
877
+
878
+ // src/resources/stockx/market.ts
879
+ var StockXMarketResource = class extends APIResource {
880
+ /**
881
+ * `GET /v2/stockx/market/search` — lowest ask / highest bid per size.
882
+ *
883
+ * Served from cache when fresh; `force: true` skips the cache and spends
884
+ * StockX quota. Check `fetched_at` for freshness.
885
+ */
886
+ search(styleId, params = {}) {
887
+ return this._get(
888
+ "/v2/stockx/market/search",
889
+ {
890
+ style_id: styleId,
891
+ size: params.size === void 0 || params.size === null ? void 0 : formatSize(params.size),
892
+ force: params.force
893
+ },
894
+ params
895
+ );
896
+ }
897
+ /** `GET /v2/stockx/market/hot` — style codes with recently cached quotes. */
898
+ hot(params = {}) {
899
+ return this._get("/v2/stockx/market/hot", { limit: params.limit, page: params.page }, params);
900
+ }
901
+ /** Iterate every recently quoted style code across pages. */
902
+ async *iterHot(params = {}) {
903
+ const { pageSize = 100, ...options } = params;
904
+ for (let page = 1; ; page++) {
905
+ const data = await this.hot({ ...options, limit: pageSize, page });
906
+ yield* data.items ?? [];
907
+ if (!data.has_next) return;
908
+ }
909
+ }
910
+ /**
911
+ * `GET /v2/stockx/catalog/products/{productId}/variants/{variantId}/market-data`
912
+ *
913
+ * Live quote for one size. Always calls StockX. Returns StockX's market data
914
+ * object as received.
915
+ */
916
+ variant(productId, variantId, params = {}) {
917
+ return this._get(
918
+ `/v2/stockx/catalog/products/${pathSegment(productId)}/variants/${pathSegment(variantId)}/market-data`,
919
+ { currency_code: params.currencyCode, connection_id: this._conn(params.connectionId) },
920
+ params
921
+ );
922
+ }
923
+ };
924
+
925
+ // src/resources/stockx/orders.ts
926
+ var date = (value) => value == null ? void 0 : formatDate(value);
927
+ var StockXOrdersResource = class extends APIResource {
928
+ /**
929
+ * `POST /v2/stockx/orders/sync` — start a background sync from StockX. There
930
+ * is no status endpoint; query again later.
931
+ */
932
+ sync(options = {}) {
933
+ return this._send("POST", "/v2/stockx/orders/sync", {}, options);
934
+ }
935
+ /** `GET /v2/stockx/orders` — synced orders, newest first. */
936
+ list(params = {}) {
937
+ return this._get(
938
+ "/v2/stockx/orders",
939
+ {
940
+ page: params.page,
941
+ page_size: params.pageSize,
942
+ order_status: params.orderStatus,
943
+ from_date: date(params.fromDate),
944
+ to_date: date(params.toDate),
945
+ search: params.search
946
+ },
947
+ params
948
+ );
949
+ }
950
+ /** Iterate every matching order across pages. */
951
+ async *iterList(params = {}) {
952
+ const { pageSize = 200, ...rest } = params;
953
+ let seen = 0;
954
+ for (let page = 1; ; page++) {
955
+ const data = await this.list({ ...rest, page, pageSize });
956
+ const orders = data.orders ?? [];
957
+ yield* orders;
958
+ seen += orders.length;
959
+ if (orders.length === 0 || seen >= Number(data.total ?? 0)) return;
960
+ }
961
+ }
962
+ /**
963
+ * `GET /v2/stockx/orders/analytics` — summary, top products/brands, sales
964
+ * trend and status breakdown.
965
+ */
966
+ analytics(params = {}) {
967
+ return this._get(
968
+ "/v2/stockx/orders/analytics",
969
+ { from_date: date(params.fromDate), to_date: date(params.toDate), granularity: params.granularity },
970
+ params
971
+ );
972
+ }
973
+ };
974
+
975
+ // src/resources/stockx/index.ts
976
+ var StockXResource = class extends APIResource {
977
+ market;
978
+ catalog;
979
+ listings;
980
+ costs;
981
+ orders;
982
+ constructor(client) {
983
+ super(client);
984
+ this.market = new StockXMarketResource(client);
985
+ this.catalog = new StockXCatalogResource(client);
986
+ this.listings = new StockXListingsResource(client);
987
+ this.costs = new StockXCostsResource(client);
988
+ this.orders = new StockXOrdersResource(client);
989
+ }
990
+ };
991
+
992
+ // src/version.ts
993
+ var VERSION = "0.1.0";
994
+
995
+ // src/client.ts
996
+ var MAX_TIMER_MS = 2 ** 31 - 1;
997
+ var Client = class _Client {
998
+ baseURL;
999
+ timeout;
1000
+ maxRetries;
1001
+ connectionId;
1002
+ inventory;
1003
+ stockx;
1004
+ goat;
1005
+ #apiKey;
1006
+ #customHeaders;
1007
+ #fetch;
1008
+ constructor(apiKeyOrOptions = {}, maybeOptions = {}) {
1009
+ const options = typeof apiKeyOrOptions === "string" ? { ...maybeOptions, apiKey: apiKeyOrOptions } : apiKeyOrOptions;
1010
+ const apiKey = options.apiKey || readEnv(API_KEY_ENV);
1011
+ if (!apiKey) {
1012
+ throw new SolometricsError(
1013
+ `No API key provided. Pass { apiKey: '...' } or set the ${API_KEY_ENV} environment variable.`
1014
+ );
1015
+ }
1016
+ this.#apiKey = apiKey;
1017
+ this.baseURL = (options.baseURL || readEnv(BASE_URL_ENV) || DEFAULT_BASE_URL).replace(/\/+$/, "");
1018
+ this.timeout = options.timeout === void 0 ? DEFAULT_TIMEOUT : options.timeout;
1019
+ this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
1020
+ this.connectionId = options.connectionId ?? null;
1021
+ this.#customHeaders = { ...options.defaultHeaders };
1022
+ if (options.fetch) {
1023
+ this.#fetch = options.fetch;
1024
+ } else if (typeof globalThis.fetch === "function") {
1025
+ this.#fetch = (url, init) => globalThis.fetch(url, init);
1026
+ } else {
1027
+ throw new SolometricsError("`fetch` is not available; use Node.js 18+ or pass { fetch } in the client options.");
1028
+ }
1029
+ this.inventory = new InventoryResource(this);
1030
+ this.stockx = new StockXResource(this);
1031
+ this.goat = new GoatResource(this);
1032
+ }
1033
+ get apiKey() {
1034
+ return this.#apiKey;
1035
+ }
1036
+ /**
1037
+ * Return a copy with some options changed. The copy shares `fetch` and the
1038
+ * default headers. Pass `connectionId: null` to clear the default connection.
1039
+ */
1040
+ withOptions(options) {
1041
+ return new _Client({
1042
+ apiKey: options.apiKey || this.#apiKey,
1043
+ baseURL: this.baseURL,
1044
+ timeout: options.timeout === void 0 ? this.timeout : options.timeout,
1045
+ maxRetries: options.maxRetries ?? this.maxRetries,
1046
+ connectionId: options.connectionId === void 0 ? this.connectionId : options.connectionId,
1047
+ defaultHeaders: this.#customHeaders,
1048
+ fetch: this.#fetch
1049
+ });
1050
+ }
1051
+ // ------------------------------------------------------------------ low-level requests
1052
+ /** Low-level GET for endpoints the SDK does not wrap yet. */
1053
+ get(path, options = {}) {
1054
+ return this._request({ ...options, method: "GET", path });
1055
+ }
1056
+ post(path, options = {}) {
1057
+ return this._request({ ...options, method: "POST", path });
1058
+ }
1059
+ put(path, options = {}) {
1060
+ return this._request({ ...options, method: "PUT", path });
1061
+ }
1062
+ patch(path, options = {}) {
1063
+ return this._request({ ...options, method: "PATCH", path });
1064
+ }
1065
+ delete(path, options = {}) {
1066
+ return this._request({ ...options, method: "DELETE", path });
1067
+ }
1068
+ /** @internal */
1069
+ async _request(options) {
1070
+ const maxRetries = options.maxRetries ?? this.maxRetries;
1071
+ const timeout = options.timeout === void 0 ? this.timeout : options.timeout;
1072
+ const url = buildURL(this.baseURL, options.path, options.query);
1073
+ const userSignal = options.signal ?? void 0;
1074
+ for (let attempt = 0; ; attempt++) {
1075
+ if (userSignal?.aborted) throw new APIUserAbortError({ cause: userSignal.reason });
1076
+ const controller = new AbortController();
1077
+ let timedOut = false;
1078
+ const timer = timeout !== null && Number.isFinite(timeout) ? setTimeout(
1079
+ () => {
1080
+ timedOut = true;
1081
+ controller.abort();
1082
+ },
1083
+ Math.min(Math.max(timeout, 0), MAX_TIMER_MS)
1084
+ ) : void 0;
1085
+ const onUserAbort = () => controller.abort(userSignal?.reason);
1086
+ userSignal?.addEventListener("abort", onUserAbort, { once: true });
1087
+ let response;
1088
+ let text;
1089
+ try {
1090
+ response = await this.#fetch(url, {
1091
+ method: options.method,
1092
+ headers: this.#headers(options),
1093
+ body: this.#body(options),
1094
+ signal: controller.signal
1095
+ });
1096
+ text = await response.text();
1097
+ } catch (err) {
1098
+ if (userSignal?.aborted) throw new APIUserAbortError({ cause: err });
1099
+ const kind = timedOut ? "timeout" : classifyNetworkError(err);
1100
+ if (attempt < maxRetries && shouldRetryNetworkError(options.method, kind)) {
1101
+ await timing.sleep(retryDelay(attempt, null), userSignal);
1102
+ continue;
1103
+ }
1104
+ if (kind === "timeout") throw new APITimeoutError({ cause: err });
1105
+ throw new APIConnectionError(connectionErrorMessage(err), { cause: err });
1106
+ } finally {
1107
+ clearTimeout(timer);
1108
+ userSignal?.removeEventListener("abort", onUserAbort);
1109
+ }
1110
+ if (response.ok) return parseBody(response.status, text);
1111
+ if (attempt < maxRetries && shouldRetryStatus(options.method, response.status)) {
1112
+ const retryAfter = parseRetryAfter(response.headers);
1113
+ await timing.sleep(retryDelay(attempt, retryAfter), userSignal);
1114
+ continue;
1115
+ }
1116
+ throw makeStatusError(response.status, response.statusText, response.headers, text);
1117
+ }
1118
+ }
1119
+ #headers(options) {
1120
+ const headers = {
1121
+ "X-API-Key": this.#apiKey,
1122
+ Accept: "application/json",
1123
+ "User-Agent": `solometrics-node/${VERSION}`
1124
+ };
1125
+ if (options.body !== void 0 && !options.file) headers["Content-Type"] = "application/json";
1126
+ return { ...headers, ...this.#customHeaders, ...options.headers };
1127
+ }
1128
+ /** Built per attempt, so a multipart body can be resent on retry. */
1129
+ #body(options) {
1130
+ if (options.file) {
1131
+ const form = new FormData();
1132
+ form.append(
1133
+ "file",
1134
+ new Blob([options.file.content], { type: "text/csv" }),
1135
+ options.file.name
1136
+ );
1137
+ return form;
1138
+ }
1139
+ return options.body === void 0 ? void 0 : JSON.stringify(options.body);
1140
+ }
1141
+ toString() {
1142
+ return `Client { baseURL: '${this.baseURL}' }`;
1143
+ }
1144
+ [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
1145
+ return this.toString();
1146
+ }
1147
+ };
1148
+ function parseBody(status, text) {
1149
+ if (status === 204 || text === "") return null;
1150
+ try {
1151
+ return JSON.parse(text);
1152
+ } catch {
1153
+ return text;
1154
+ }
1155
+ }
1156
+ function connectionErrorMessage(err) {
1157
+ if (err instanceof Error) {
1158
+ const cause = err.cause instanceof Error ? `: ${err.cause.message}` : "";
1159
+ return `${err.message}${cause}` || "Connection error.";
1160
+ }
1161
+ return "Connection error.";
1162
+ }
1163
+ //# sourceMappingURL=index.cjs.map