@lonca/trendyol 0.8.0 → 0.10.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.js CHANGED
@@ -1,4 +1,4 @@
1
- import { TokenBucketRateLimiter, ValidationError, noopLogger, retry, TimeoutError, NetworkError, LoncaError, ServerError, RateLimitError, NotFoundError, AuthError } from '@lonca/core';
1
+ import { createStatusNormalizer, TokenBucketRateLimiter, ValidationError, TimeoutError, ServerError, noopLogger, retry, NetworkError, LoncaError, RateLimitError, NotFoundError, AuthError } from '@lonca/core';
2
2
  import { randomUUID } from 'crypto';
3
3
 
4
4
  // src/resources/brands.ts
@@ -395,6 +395,269 @@ var ClaimsResource = class {
395
395
  return rows.map((row) => ({ raw: row }));
396
396
  }
397
397
  };
398
+ var MAX_BATCH_ITEMS = 5e3;
399
+ var ExportCenterResource = class {
400
+ constructor(transport, limiter) {
401
+ this.transport = transport;
402
+ this.limiter = limiter ?? new TokenBucketRateLimiter({ capacity: 1e3, intervalMs: 6e4 });
403
+ }
404
+ transport;
405
+ limiter;
406
+ // ─── Products ────────────────────────────────────────────────────────────
407
+ /**
408
+ * List Export Center-approved products. Uses Trendyol's `pageKey`
409
+ * pagination — the first call leaves `pageKey` empty; subsequent
410
+ * calls pass the `x-paging-key` value from the previous response.
411
+ */
412
+ async listProducts(params = {}) {
413
+ const query = {};
414
+ if (params.barcodes && params.barcodes.length > 0) query.barcodes = params.barcodes.join(",");
415
+ if (params.pageKey) query.pageKey = params.pageKey;
416
+ if (params.size !== void 0) query.size = params.size;
417
+ const data = await this.transport.request({
418
+ method: "GET",
419
+ path: `/integration/ecgw/v2/${this.transport.sellerId}/products`,
420
+ query: Object.keys(query).length ? query : void 0,
421
+ rateLimiter: this.limiter
422
+ });
423
+ const rows = pickArray(data);
424
+ return rows.map((r) => ({ raw: r ?? {} }));
425
+ }
426
+ /**
427
+ * Create Export Center products. Maximum 5000 per call. Returns a
428
+ * `batchId` you can poll via `getBatchStatus(batchId)`.
429
+ *
430
+ * @throws {ValidationError} when `products` is empty / oversized.
431
+ */
432
+ async createProducts(products) {
433
+ this.assertBatch(products, "createProducts");
434
+ const data = await this.transport.request({
435
+ method: "POST",
436
+ path: `/integration/ecgw/v2/${this.transport.sellerId}/products`,
437
+ body: { products },
438
+ rateLimiter: this.limiter
439
+ });
440
+ return normalizeBatchResponse(data);
441
+ }
442
+ /**
443
+ * Update Export Center prices. **Trendyol allows one price update per
444
+ * barcode per day.** Returns a `batchId`.
445
+ *
446
+ * @throws {ValidationError} when `priceInfos` is empty / oversized.
447
+ */
448
+ async updatePrices(priceInfos) {
449
+ this.assertBatch(priceInfos, "updatePrices");
450
+ const data = await this.transport.request({
451
+ method: "POST",
452
+ path: `/integration/ecgw/v1/${this.transport.sellerId}/prices`,
453
+ body: { priceInfos },
454
+ rateLimiter: this.limiter
455
+ });
456
+ return normalizeBatchResponse(data);
457
+ }
458
+ /**
459
+ * Update Export Center stocks. Returns a `batchId`. Sellers using
460
+ * Trendyol's shared inventory cannot use this endpoint (per portal docs).
461
+ *
462
+ * @throws {ValidationError} when `items` is empty / oversized.
463
+ */
464
+ async updateStocks(items) {
465
+ this.assertBatch(items, "updateStocks");
466
+ const data = await this.transport.request({
467
+ method: "POST",
468
+ path: `/integration/ecgw/v1/${this.transport.sellerId}/stocks`,
469
+ body: { items },
470
+ rateLimiter: this.limiter
471
+ });
472
+ return normalizeBatchResponse(data);
473
+ }
474
+ // ─── Batch status ────────────────────────────────────────────────────────
475
+ /**
476
+ * Look up the status of a previously-submitted batch. Trendyol retains
477
+ * batch records for **24 hours** only — older `batchId`s return `404`.
478
+ */
479
+ async getBatchStatus(batchId) {
480
+ if (!batchId) {
481
+ throw new ValidationError({ message: "exportCenter.getBatchStatus: batchId is required" });
482
+ }
483
+ const data = await this.transport.request({
484
+ method: "GET",
485
+ path: `/integration/ecgw/v1/${this.transport.sellerId}/check-status`,
486
+ query: { batchId },
487
+ rateLimiter: this.limiter
488
+ });
489
+ return normalizeBatchStatus(data);
490
+ }
491
+ // ─── Packages ────────────────────────────────────────────────────────────
492
+ /** List daily Export Center packages (V2 — query-based filters). */
493
+ async listPackagesV2(params = {}) {
494
+ const data = await this.transport.request({
495
+ method: "GET",
496
+ path: `/integration/ecgw/v2/${this.transport.sellerId}/packages`,
497
+ query: {
498
+ trackingNumber: params.trackingNumber,
499
+ status: params.status,
500
+ creationStartDate: params.creationStartDate,
501
+ creationEndDate: params.creationEndDate,
502
+ size: params.size,
503
+ boutiqueId: params.boutiqueId
504
+ },
505
+ rateLimiter: this.limiter
506
+ });
507
+ return pickArray(data).map(normalizePackage);
508
+ }
509
+ /** List Export Center packages (V3 — consolidated, page-based). */
510
+ async listPackagesV3(params = {}) {
511
+ const data = await this.transport.request({
512
+ method: "GET",
513
+ path: `/integration/ecgw/v3/${this.transport.sellerId}/packages`,
514
+ query: {
515
+ status: params.status,
516
+ page: params.offset !== void 0 ? params.offset : void 0,
517
+ size: params.limit,
518
+ creationStartDate: params.creationStartDate,
519
+ creationEndDate: params.creationEndDate
520
+ },
521
+ rateLimiter: this.limiter
522
+ });
523
+ return pickArray(data).map(normalizePackage);
524
+ }
525
+ /** Get the line items inside an Export Center package. */
526
+ async getPackageItems(params) {
527
+ if (!params || !params.packageId) {
528
+ throw new ValidationError({
529
+ message: "exportCenter.getPackageItems: packageId is required"
530
+ });
531
+ }
532
+ const data = await this.transport.request({
533
+ method: "GET",
534
+ path: `/integration/ecgw/v2/${this.transport.sellerId}/packages/items`,
535
+ query: {
536
+ packageId: params.packageId,
537
+ status: params.status,
538
+ page: params.offset,
539
+ size: params.limit
540
+ },
541
+ rateLimiter: this.limiter
542
+ });
543
+ return pickArray(data).map((r) => ({ raw: r ?? {} }));
544
+ }
545
+ // ─── Lookup ──────────────────────────────────────────────────────────────
546
+ /** Get the required attributes for an Export Center category. */
547
+ async getCategoryAttributes(categoryId) {
548
+ if (categoryId === void 0 || categoryId === null || categoryId === "") {
549
+ throw new ValidationError({
550
+ message: "exportCenter.getCategoryAttributes: categoryId is required"
551
+ });
552
+ }
553
+ const data = await this.transport.request({
554
+ method: "GET",
555
+ path: `/integration/ecgw/v1/${this.transport.sellerId}/lookup/product-categories/${encodeURIComponent(String(categoryId))}/attributes`,
556
+ rateLimiter: this.limiter
557
+ });
558
+ return pickArray(data).map((r) => {
559
+ const row = r ?? {};
560
+ const out = { raw: row };
561
+ if (typeof row.attributeId === "number" || typeof row.attributeId === "string") {
562
+ out.attributeId = row.attributeId;
563
+ }
564
+ if (typeof row.attributeName === "string") out.attributeName = row.attributeName;
565
+ if (typeof row.required === "boolean") out.required = row.required;
566
+ if (Array.isArray(row.values)) out.values = row.values;
567
+ return out;
568
+ });
569
+ }
570
+ /** Get the care-instruction lookup values used by `createProducts`. */
571
+ async getCareInstructions() {
572
+ const data = await this.transport.request({
573
+ method: "GET",
574
+ path: `/integration/ecgw/v1/${this.transport.sellerId}/lookup/care-instructions`,
575
+ rateLimiter: this.limiter
576
+ });
577
+ return pickArray(data).map(normalizeNamedRef);
578
+ }
579
+ /** Get the material-composition lookup values used by `createProducts`. */
580
+ async getCompositions() {
581
+ const data = await this.transport.request({
582
+ method: "GET",
583
+ path: `/integration/ecgw/v1/${this.transport.sellerId}/lookup/compositions`,
584
+ rateLimiter: this.limiter
585
+ });
586
+ return pickArray(data).map(normalizeNamedRef);
587
+ }
588
+ /** Get the country-of-origin lookup values used by `createProducts`. */
589
+ async getOrigins() {
590
+ const data = await this.transport.request({
591
+ method: "GET",
592
+ path: `/integration/ecgw/v1/${this.transport.sellerId}/lookup/origins`,
593
+ rateLimiter: this.limiter
594
+ });
595
+ return pickArray(data).map((r) => {
596
+ const row = r ?? {};
597
+ const out = { raw: row };
598
+ if (typeof row.id === "number" || typeof row.id === "string") out.id = row.id;
599
+ if (typeof row.name === "string") out.name = row.name;
600
+ if (typeof row.countryCode === "string") out.countryCode = row.countryCode;
601
+ return out;
602
+ });
603
+ }
604
+ // ─── Helpers ─────────────────────────────────────────────────────────────
605
+ assertBatch(items, methodLabel) {
606
+ if (!Array.isArray(items) || items.length === 0) {
607
+ throw new ValidationError({
608
+ message: `exportCenter.${methodLabel}: items must not be empty`
609
+ });
610
+ }
611
+ if (items.length > MAX_BATCH_ITEMS) {
612
+ throw new ValidationError({
613
+ message: `exportCenter.${methodLabel}: max ${MAX_BATCH_ITEMS} items per call (got ${items.length})`
614
+ });
615
+ }
616
+ }
617
+ };
618
+ function pickArray(data) {
619
+ if (Array.isArray(data)) return data;
620
+ if (data && typeof data === "object") {
621
+ const obj = data;
622
+ if (Array.isArray(obj.content)) return obj.content;
623
+ if (Array.isArray(obj.items)) return obj.items;
624
+ if (Array.isArray(obj.data)) return obj.data;
625
+ if (Array.isArray(obj.products)) return obj.products;
626
+ if (Array.isArray(obj.packages)) return obj.packages;
627
+ }
628
+ return [];
629
+ }
630
+ function normalizeBatchResponse(data) {
631
+ const obj = data && typeof data === "object" ? data : {};
632
+ return {
633
+ batchId: String(obj.batchId ?? obj.batchRequestId ?? obj.id ?? ""),
634
+ raw: obj
635
+ };
636
+ }
637
+ function normalizeBatchStatus(data) {
638
+ const obj = data && typeof data === "object" ? data : {};
639
+ const out = { raw: obj };
640
+ if (typeof obj.batchId === "string") out.batchId = obj.batchId;
641
+ if (typeof obj.status === "string") out.status = obj.status;
642
+ if (typeof obj.itemCount === "number") out.itemCount = obj.itemCount;
643
+ if (typeof obj.failedItemCount === "number") out.failedItemCount = obj.failedItemCount;
644
+ if (Array.isArray(obj.items)) out.items = obj.items;
645
+ return out;
646
+ }
647
+ function normalizePackage(row) {
648
+ const r = row ?? {};
649
+ const out = { raw: r };
650
+ if (typeof r.packageNumber === "string") out.packageNumber = r.packageNumber;
651
+ if (typeof r.status === "string") out.status = r.status;
652
+ return out;
653
+ }
654
+ function normalizeNamedRef(row) {
655
+ const r = row ?? {};
656
+ const out = { raw: r };
657
+ if (typeof r.id === "number" || typeof r.id === "string") out.id = r.id;
658
+ if (typeof r.name === "string") out.name = r.name;
659
+ return out;
660
+ }
398
661
  function toIso2(epochMs) {
399
662
  if (typeof epochMs !== "number" || !Number.isFinite(epochMs) || epochMs === 0) {
400
663
  return void 0;
@@ -440,11 +703,14 @@ var FinanceResource = class {
440
703
  transport;
441
704
  limiter;
442
705
  async getSettlements(params = {}) {
443
- return this.queryPage(`/integration/sellers/${this.transport.sellerId}/settlements`, params);
706
+ return this.queryPage(
707
+ `/integration/finance/che/sellers/${this.transport.sellerId}/settlements`,
708
+ params
709
+ );
444
710
  }
445
711
  async getOtherFinancials(params = {}) {
446
712
  return this.queryPage(
447
- `/integration/sellers/${this.transport.sellerId}/otherfinancials`,
713
+ `/integration/finance/che/sellers/${this.transport.sellerId}/otherfinancials`,
448
714
  params
449
715
  );
450
716
  }
@@ -471,11 +737,37 @@ var FinanceResource = class {
471
737
  }
472
738
  };
473
739
  var MAX_ITEMS_PER_REQUEST = 1e3;
740
+ async function pollBatchStatus(getStatus, batchRequestId, opts = {}) {
741
+ const pollIntervalMs = opts.pollIntervalMs ?? 2e3;
742
+ const timeoutMs = opts.timeoutMs ?? 12e4;
743
+ const deadline = Date.now() + timeoutMs;
744
+ for (; ; ) {
745
+ opts.signal?.throwIfAborted();
746
+ const result = await getStatus(batchRequestId);
747
+ if (result.status === "COMPLETED" || result.status === "FAILED") {
748
+ return result;
749
+ }
750
+ if (Date.now() >= deadline) {
751
+ throw new TimeoutError({
752
+ message: `Trendyol batch ${batchRequestId} did not settle within ${timeoutMs}ms (last status: ${result.status})`,
753
+ data: { batchRequestId, lastStatus: result.status, lastResult: result }
754
+ });
755
+ }
756
+ await delay(pollIntervalMs, opts.signal);
757
+ }
758
+ }
474
759
  var InventoryResource = class {
475
- constructor(transport) {
760
+ /**
761
+ * @param transport Trendyol transport.
762
+ * @param getBatchStatus Batch-status poller (wired by `createTrendyolClient`
763
+ * to `products.getBatchStatus`). Required only for `updateAndWait`.
764
+ */
765
+ constructor(transport, getBatchStatus) {
476
766
  this.transport = transport;
767
+ this.getBatchStatus = getBatchStatus;
477
768
  }
478
769
  transport;
770
+ getBatchStatus;
479
771
  /**
480
772
  * Update price and/or stock for one or more SKUs (by barcode).
481
773
  *
@@ -489,6 +781,9 @@ var InventoryResource = class {
489
781
  * ```
490
782
  *
491
783
  * @throws {ValidationError} when `items` is empty or longer than 1000.
784
+ * @throws {ServerError} when Trendyol accepts the request but returns no
785
+ * `batchRequestId` (an unpollable response — surfaced loudly instead of
786
+ * handing back an empty id).
492
787
  */
493
788
  async update(items) {
494
789
  if (items.length === 0) {
@@ -504,9 +799,70 @@ var InventoryResource = class {
504
799
  path: `/integration/inventory/sellers/${this.transport.sellerId}/products/price-and-inventory`,
505
800
  body: { items }
506
801
  });
507
- return { batchRequestId: data.batchRequestId ?? "" };
802
+ if (!data.batchRequestId) {
803
+ throw new ServerError({
804
+ message: "Trendyol updatePriceAndInventory returned no batchRequestId",
805
+ data: { response: data }
806
+ });
807
+ }
808
+ return { batchRequestId: data.batchRequestId };
809
+ }
810
+ /**
811
+ * Submit price/stock updates and wait for them to settle.
812
+ *
813
+ * Splits `items` into chunks of ≤1000, submits each via {@link update}, and
814
+ * polls each `batchRequestId` to a terminal state. Returns one
815
+ * `BatchRequestResult` per chunk (read `failedItemCount` / `items[]` for
816
+ * per-barcode outcomes). Compose `@lonca/core`'s `retry` around this for
817
+ * transient-error resilience.
818
+ *
819
+ * @throws {ValidationError} when `items` is empty.
820
+ * @throws {TimeoutError} when any chunk does not settle within `timeoutMs`;
821
+ * `error.data.batchRequestId` identifies the stuck chunk.
822
+ */
823
+ async updateAndWait(items, opts = {}) {
824
+ if (items.length === 0) {
825
+ throw new ValidationError({ message: "updateAndWait: items must not be empty" });
826
+ }
827
+ const getStatus = this.getBatchStatus;
828
+ if (!getStatus) {
829
+ throw new Error(
830
+ "updateAndWait requires a batch-status poller; obtain the client via createTrendyolClient()"
831
+ );
832
+ }
833
+ const results = [];
834
+ for (const batch of chunk(items, MAX_ITEMS_PER_REQUEST)) {
835
+ const { batchRequestId } = await this.update(batch);
836
+ results.push(await pollBatchStatus(getStatus, batchRequestId, opts));
837
+ }
838
+ return results;
508
839
  }
509
840
  };
841
+ function chunk(items, size) {
842
+ if (items.length <= size) return [items];
843
+ const chunks = [];
844
+ for (let i = 0; i < items.length; i += size) {
845
+ chunks.push(items.slice(i, i + size));
846
+ }
847
+ return chunks;
848
+ }
849
+ function delay(ms, signal) {
850
+ return new Promise((resolve, reject) => {
851
+ if (signal?.aborted) {
852
+ reject(signal.reason ?? new Error("Aborted"));
853
+ return;
854
+ }
855
+ const onAbort = () => {
856
+ clearTimeout(timer);
857
+ reject(signal?.reason ?? new Error("Aborted"));
858
+ };
859
+ const timer = setTimeout(() => {
860
+ signal?.removeEventListener("abort", onAbort);
861
+ resolve();
862
+ }, ms);
863
+ signal?.addEventListener("abort", onAbort, { once: true });
864
+ });
865
+ }
510
866
  var InvoicesResource = class {
511
867
  constructor(transport, limiter) {
512
868
  this.transport = transport;
@@ -792,9 +1148,9 @@ function normalizeCustomer(node) {
792
1148
  return out;
793
1149
  }
794
1150
  function normalizeShipmentPackage(rawNode) {
795
- return normalizePackage(rawNode ?? {});
1151
+ return normalizePackage2(rawNode ?? {});
796
1152
  }
797
- function normalizePackage(node) {
1153
+ function normalizePackage2(node) {
798
1154
  const pkgId = node.shipmentPackageId ?? node.id;
799
1155
  const out = {
800
1156
  id: pkgId !== void 0 ? String(pkgId) : "",
@@ -895,7 +1251,7 @@ var OrdersResource = class {
895
1251
  query,
896
1252
  rateLimiter: this.limiter
897
1253
  });
898
- const items = (data.content ?? []).map(normalizePackage);
1254
+ const items = (data.content ?? []).map(normalizePackage2);
899
1255
  const totalPages = typeof data.totalPages === "number" ? data.totalPages : 0;
900
1256
  const result = { items };
901
1257
  if (page + 1 < totalPages) {
@@ -1192,7 +1548,7 @@ var OrdersResource = class {
1192
1548
  query,
1193
1549
  rateLimiter: this.limiter
1194
1550
  });
1195
- const items = (data.content ?? []).map(normalizePackage);
1551
+ const items = (data.content ?? []).map(normalizePackage2);
1196
1552
  const result = { items };
1197
1553
  if (data.hasMore && data.nextCursor) {
1198
1554
  result.nextCursor = data.nextCursor;
@@ -2075,6 +2431,55 @@ var TestOrdersResource = class {
2075
2431
  });
2076
2432
  }
2077
2433
  };
2434
+ var VideosResource = class {
2435
+ constructor(transport, options = {}) {
2436
+ this.transport = transport;
2437
+ this.createLimiter = options.createLimiter ?? new TokenBucketRateLimiter({ capacity: 200, intervalMs: 6e4 });
2438
+ this.listLimiter = options.listLimiter ?? new TokenBucketRateLimiter({ capacity: 1e3, intervalMs: 6e4 });
2439
+ }
2440
+ transport;
2441
+ createLimiter;
2442
+ listLimiter;
2443
+ /**
2444
+ * Queue a video for upload. Trendyol downloads from the URL in the
2445
+ * body asynchronously; poll `list()` (filtered by id) for status.
2446
+ *
2447
+ * @throws {ValidationError} when `input` is empty / not an object.
2448
+ */
2449
+ async create(input) {
2450
+ if (!input || typeof input !== "object") {
2451
+ throw new ValidationError({ message: "videos.create: input is required" });
2452
+ }
2453
+ return this.transport.request({
2454
+ method: "POST",
2455
+ path: `/integration/video/sellers/${this.transport.sellerId}/videos`,
2456
+ body: input,
2457
+ rateLimiter: this.createLimiter
2458
+ });
2459
+ }
2460
+ /** List the seller's integration videos (optionally filtered by id / status). */
2461
+ async list(params = {}) {
2462
+ const data = await this.transport.request({
2463
+ method: "GET",
2464
+ path: `/integration/video/sellers/${this.transport.sellerId}/videos`,
2465
+ query: {
2466
+ id: params.id,
2467
+ sellerIntegrationStatus: params.sellerIntegrationStatus,
2468
+ page: params.offset,
2469
+ size: params.limit
2470
+ },
2471
+ rateLimiter: this.listLimiter
2472
+ });
2473
+ const rows = Array.isArray(data) ? data : Array.isArray(data?.content) ? data.content : Array.isArray(data?.items) ? data.items : [];
2474
+ return rows.map((r) => {
2475
+ const row = r ?? {};
2476
+ const out = { raw: row };
2477
+ if (typeof row.id === "string") out.id = row.id;
2478
+ if (typeof row.status === "string") out.status = row.status;
2479
+ return out;
2480
+ });
2481
+ }
2482
+ };
2078
2483
  function normalizeWebhook(node) {
2079
2484
  const out = {
2080
2485
  id: node.id !== void 0 ? String(node.id) : "",
@@ -2191,6 +2596,16 @@ var WebhooksResource = class {
2191
2596
  }
2192
2597
  };
2193
2598
 
2599
+ // src/capabilities.ts
2600
+ var trendyolCapabilities = {
2601
+ /** Trendyol has no time-bounded / scheduled pricing (`pricings[]`). */
2602
+ scheduledPricing: false,
2603
+ /** `inventory.update` accepts stock-only items (`quantity` with no price). */
2604
+ stockOnlyBatch: true,
2605
+ /** Products expose `updatedAt`, so last-write-wins guards are supported. */
2606
+ listingUpdatedAt: true
2607
+ };
2608
+
2194
2609
  // src/auth.ts
2195
2610
  function buildAuthHeader(apiKey, apiSecret) {
2196
2611
  const token = Buffer.from(`${apiKey}:${apiSecret}`, "utf8").toString("base64");
@@ -2201,27 +2616,31 @@ function buildUserAgent(sellerId, integratorName) {
2201
2616
  }
2202
2617
  function mapHttpError(status, body, retryAfterMs) {
2203
2618
  const data = { body };
2619
+ const issues = normalizeErrorIssues(body);
2204
2620
  switch (status) {
2205
2621
  case 401:
2206
2622
  return new AuthError({
2207
2623
  message: "Trendyol authentication failed (check apiKey / apiSecret)",
2208
2624
  status,
2209
- data
2625
+ data,
2626
+ issues
2210
2627
  });
2211
2628
  case 403:
2212
2629
  return new ValidationError({
2213
2630
  message: "Trendyol forbidden (check User-Agent header or endpoint path)",
2214
2631
  status,
2215
- data
2632
+ data,
2633
+ issues
2216
2634
  });
2217
2635
  case 404:
2218
- return new NotFoundError({ message: "Trendyol resource not found", status, data });
2636
+ return new NotFoundError({ message: "Trendyol resource not found", status, data, issues });
2219
2637
  case 429:
2220
2638
  return new RateLimitError({
2221
2639
  message: "Trendyol rate limit exceeded",
2222
2640
  status,
2223
2641
  retryAfterMs,
2224
- data
2642
+ data,
2643
+ issues
2225
2644
  });
2226
2645
  case 500:
2227
2646
  case 502:
@@ -2230,7 +2649,8 @@ function mapHttpError(status, body, retryAfterMs) {
2230
2649
  return new ServerError({
2231
2650
  message: `Trendyol server error (${status})`,
2232
2651
  status,
2233
- data
2652
+ data,
2653
+ issues
2234
2654
  });
2235
2655
  default:
2236
2656
  return new LoncaError({
@@ -2238,10 +2658,42 @@ function mapHttpError(status, body, retryAfterMs) {
2238
2658
  message: `Trendyol unexpected response (${status})`,
2239
2659
  status,
2240
2660
  data,
2661
+ issues,
2241
2662
  retryable: false
2242
2663
  });
2243
2664
  }
2244
2665
  }
2666
+ function normalizeErrorIssues(body) {
2667
+ const errors = extractErrorsArray(body);
2668
+ if (!errors) return [];
2669
+ const issues = [];
2670
+ for (const entry of errors) {
2671
+ if (typeof entry === "string") {
2672
+ issues.push({ message: entry });
2673
+ continue;
2674
+ }
2675
+ if (entry && typeof entry === "object") {
2676
+ const e = entry;
2677
+ if (typeof e.message !== "string") continue;
2678
+ const issue = { message: e.message };
2679
+ if (typeof e.field === "string") issue.field = e.field;
2680
+ if (typeof e.code === "string") issue.code = e.code;
2681
+ issues.push(issue);
2682
+ }
2683
+ }
2684
+ return issues;
2685
+ }
2686
+ function extractErrorsArray(body) {
2687
+ if (!body || typeof body !== "object") return void 0;
2688
+ const b = body;
2689
+ if (Array.isArray(b.errors)) return b.errors;
2690
+ const nested = b.body;
2691
+ if (nested && typeof nested === "object") {
2692
+ const n2 = nested;
2693
+ if (Array.isArray(n2.errors)) return n2.errors;
2694
+ }
2695
+ return void 0;
2696
+ }
2245
2697
  function parseRetryAfter(header) {
2246
2698
  if (!header) return void 0;
2247
2699
  const seconds = Number(header);
@@ -2337,11 +2789,11 @@ var TrendyolTransport = class {
2337
2789
  },
2338
2790
  {
2339
2791
  signal: opts.signal,
2340
- onRetry: (err, attempt, delay) => {
2792
+ onRetry: (err, attempt, delay2) => {
2341
2793
  if (err instanceof LoncaError) {
2342
2794
  this.logger.warn("trendyol.retry", {
2343
2795
  attempt,
2344
- delayMs: delay,
2796
+ delayMs: delay2,
2345
2797
  code: err.code,
2346
2798
  status: err.status
2347
2799
  });
@@ -2399,12 +2851,16 @@ function createTrendyolClient(opts) {
2399
2851
  logger: opts.logger,
2400
2852
  timeoutMs: opts.timeoutMs
2401
2853
  });
2854
+ return buildClient(transport);
2855
+ }
2856
+ function buildClient(transport) {
2857
+ const products = new ProductsResource(transport);
2402
2858
  return {
2403
2859
  brands: new BrandsResource(transport),
2404
2860
  categories: new CategoriesResource(transport),
2405
2861
  suppliers: new SuppliersResource(transport),
2406
- products: new ProductsResource(transport),
2407
- inventory: new InventoryResource(transport),
2862
+ products,
2863
+ inventory: new InventoryResource(transport, (id) => products.getBatchStatus(id)),
2408
2864
  orders: new OrdersResource(transport),
2409
2865
  claims: new ClaimsResource(transport),
2410
2866
  webhooks: new WebhooksResource(transport),
@@ -2413,7 +2869,10 @@ function createTrendyolClient(opts) {
2413
2869
  finance: new FinanceResource(transport),
2414
2870
  labels: new LabelsResource(transport),
2415
2871
  testOrders: new TestOrdersResource(transport),
2416
- locations: new LocationsResource(transport)
2872
+ locations: new LocationsResource(transport),
2873
+ exportCenter: new ExportCenterResource(transport),
2874
+ videos: new VideosResource(transport),
2875
+ capabilities: trendyolCapabilities
2417
2876
  };
2418
2877
  }
2419
2878
  function parseWebhookEvent(rawBody) {
@@ -2450,7 +2909,29 @@ function parseWebhookEvent(rawBody) {
2450
2909
  raw: body
2451
2910
  };
2452
2911
  }
2912
+ var statusMap = {
2913
+ Created: "created",
2914
+ Awaiting: "created",
2915
+ // new order awaiting seller acceptance
2916
+ Verified: "created",
2917
+ // accepted/verified, before picking
2918
+ Picking: "picking",
2919
+ UnPacked: "picking",
2920
+ // back in fulfillment after being unpacked
2921
+ Invoiced: "invoiced",
2922
+ Shipped: "shipped",
2923
+ AtCollectionPoint: "shipped",
2924
+ // in transit, awaiting pickup
2925
+ UnDelivered: "shipped",
2926
+ // delivery attempt failed; still in carrier's hands
2927
+ Delivered: "delivered",
2928
+ Cancelled: "cancelled",
2929
+ UnSupplied: "cancelled",
2930
+ // seller couldn't supply → effectively cancelled
2931
+ Returned: "returned"
2932
+ };
2933
+ var normalizeStatus = createStatusNormalizer(statusMap);
2453
2934
 
2454
- export { BrandsResource, CategoriesResource, ClaimsResource, FinanceResource, InventoryResource, InvoicesResource, LabelsResource, LocationsResource, OrdersResource, ProductsResource, QuestionsResource, SuppliersResource, TestOrdersResource, WebhooksResource, createTrendyolClient, normalizeShipmentPackage, parseWebhookEvent };
2935
+ export { BrandsResource, CategoriesResource, ClaimsResource, ExportCenterResource, FinanceResource, InventoryResource, InvoicesResource, LabelsResource, LocationsResource, OrdersResource, ProductsResource, QuestionsResource, SuppliersResource, TestOrdersResource, VideosResource, WebhooksResource, createTrendyolClient, normalizeShipmentPackage, normalizeStatus, parseWebhookEvent, pollBatchStatus, statusMap, trendyolCapabilities };
2455
2936
  //# sourceMappingURL=index.js.map
2456
2937
  //# sourceMappingURL=index.js.map