@lonca/trendyol 0.9.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.cjs CHANGED
@@ -739,11 +739,37 @@ var FinanceResource = class {
739
739
  }
740
740
  };
741
741
  var MAX_ITEMS_PER_REQUEST = 1e3;
742
+ async function pollBatchStatus(getStatus, batchRequestId, opts = {}) {
743
+ const pollIntervalMs = opts.pollIntervalMs ?? 2e3;
744
+ const timeoutMs = opts.timeoutMs ?? 12e4;
745
+ const deadline = Date.now() + timeoutMs;
746
+ for (; ; ) {
747
+ opts.signal?.throwIfAborted();
748
+ const result = await getStatus(batchRequestId);
749
+ if (result.status === "COMPLETED" || result.status === "FAILED") {
750
+ return result;
751
+ }
752
+ if (Date.now() >= deadline) {
753
+ throw new core.TimeoutError({
754
+ message: `Trendyol batch ${batchRequestId} did not settle within ${timeoutMs}ms (last status: ${result.status})`,
755
+ data: { batchRequestId, lastStatus: result.status, lastResult: result }
756
+ });
757
+ }
758
+ await delay(pollIntervalMs, opts.signal);
759
+ }
760
+ }
742
761
  var InventoryResource = class {
743
- constructor(transport) {
762
+ /**
763
+ * @param transport Trendyol transport.
764
+ * @param getBatchStatus Batch-status poller (wired by `createTrendyolClient`
765
+ * to `products.getBatchStatus`). Required only for `updateAndWait`.
766
+ */
767
+ constructor(transport, getBatchStatus) {
744
768
  this.transport = transport;
769
+ this.getBatchStatus = getBatchStatus;
745
770
  }
746
771
  transport;
772
+ getBatchStatus;
747
773
  /**
748
774
  * Update price and/or stock for one or more SKUs (by barcode).
749
775
  *
@@ -757,6 +783,9 @@ var InventoryResource = class {
757
783
  * ```
758
784
  *
759
785
  * @throws {ValidationError} when `items` is empty or longer than 1000.
786
+ * @throws {ServerError} when Trendyol accepts the request but returns no
787
+ * `batchRequestId` (an unpollable response — surfaced loudly instead of
788
+ * handing back an empty id).
760
789
  */
761
790
  async update(items) {
762
791
  if (items.length === 0) {
@@ -772,9 +801,70 @@ var InventoryResource = class {
772
801
  path: `/integration/inventory/sellers/${this.transport.sellerId}/products/price-and-inventory`,
773
802
  body: { items }
774
803
  });
775
- return { batchRequestId: data.batchRequestId ?? "" };
804
+ if (!data.batchRequestId) {
805
+ throw new core.ServerError({
806
+ message: "Trendyol updatePriceAndInventory returned no batchRequestId",
807
+ data: { response: data }
808
+ });
809
+ }
810
+ return { batchRequestId: data.batchRequestId };
811
+ }
812
+ /**
813
+ * Submit price/stock updates and wait for them to settle.
814
+ *
815
+ * Splits `items` into chunks of ≤1000, submits each via {@link update}, and
816
+ * polls each `batchRequestId` to a terminal state. Returns one
817
+ * `BatchRequestResult` per chunk (read `failedItemCount` / `items[]` for
818
+ * per-barcode outcomes). Compose `@lonca/core`'s `retry` around this for
819
+ * transient-error resilience.
820
+ *
821
+ * @throws {ValidationError} when `items` is empty.
822
+ * @throws {TimeoutError} when any chunk does not settle within `timeoutMs`;
823
+ * `error.data.batchRequestId` identifies the stuck chunk.
824
+ */
825
+ async updateAndWait(items, opts = {}) {
826
+ if (items.length === 0) {
827
+ throw new core.ValidationError({ message: "updateAndWait: items must not be empty" });
828
+ }
829
+ const getStatus = this.getBatchStatus;
830
+ if (!getStatus) {
831
+ throw new Error(
832
+ "updateAndWait requires a batch-status poller; obtain the client via createTrendyolClient()"
833
+ );
834
+ }
835
+ const results = [];
836
+ for (const batch of chunk(items, MAX_ITEMS_PER_REQUEST)) {
837
+ const { batchRequestId } = await this.update(batch);
838
+ results.push(await pollBatchStatus(getStatus, batchRequestId, opts));
839
+ }
840
+ return results;
776
841
  }
777
842
  };
843
+ function chunk(items, size) {
844
+ if (items.length <= size) return [items];
845
+ const chunks = [];
846
+ for (let i = 0; i < items.length; i += size) {
847
+ chunks.push(items.slice(i, i + size));
848
+ }
849
+ return chunks;
850
+ }
851
+ function delay(ms, signal) {
852
+ return new Promise((resolve, reject) => {
853
+ if (signal?.aborted) {
854
+ reject(signal.reason ?? new Error("Aborted"));
855
+ return;
856
+ }
857
+ const onAbort = () => {
858
+ clearTimeout(timer);
859
+ reject(signal?.reason ?? new Error("Aborted"));
860
+ };
861
+ const timer = setTimeout(() => {
862
+ signal?.removeEventListener("abort", onAbort);
863
+ resolve();
864
+ }, ms);
865
+ signal?.addEventListener("abort", onAbort, { once: true });
866
+ });
867
+ }
778
868
  var InvoicesResource = class {
779
869
  constructor(transport, limiter) {
780
870
  this.transport = transport;
@@ -2508,6 +2598,16 @@ var WebhooksResource = class {
2508
2598
  }
2509
2599
  };
2510
2600
 
2601
+ // src/capabilities.ts
2602
+ var trendyolCapabilities = {
2603
+ /** Trendyol has no time-bounded / scheduled pricing (`pricings[]`). */
2604
+ scheduledPricing: false,
2605
+ /** `inventory.update` accepts stock-only items (`quantity` with no price). */
2606
+ stockOnlyBatch: true,
2607
+ /** Products expose `updatedAt`, so last-write-wins guards are supported. */
2608
+ listingUpdatedAt: true
2609
+ };
2610
+
2511
2611
  // src/auth.ts
2512
2612
  function buildAuthHeader(apiKey, apiSecret) {
2513
2613
  const token = Buffer.from(`${apiKey}:${apiSecret}`, "utf8").toString("base64");
@@ -2518,27 +2618,31 @@ function buildUserAgent(sellerId, integratorName) {
2518
2618
  }
2519
2619
  function mapHttpError(status, body, retryAfterMs) {
2520
2620
  const data = { body };
2621
+ const issues = normalizeErrorIssues(body);
2521
2622
  switch (status) {
2522
2623
  case 401:
2523
2624
  return new core.AuthError({
2524
2625
  message: "Trendyol authentication failed (check apiKey / apiSecret)",
2525
2626
  status,
2526
- data
2627
+ data,
2628
+ issues
2527
2629
  });
2528
2630
  case 403:
2529
2631
  return new core.ValidationError({
2530
2632
  message: "Trendyol forbidden (check User-Agent header or endpoint path)",
2531
2633
  status,
2532
- data
2634
+ data,
2635
+ issues
2533
2636
  });
2534
2637
  case 404:
2535
- return new core.NotFoundError({ message: "Trendyol resource not found", status, data });
2638
+ return new core.NotFoundError({ message: "Trendyol resource not found", status, data, issues });
2536
2639
  case 429:
2537
2640
  return new core.RateLimitError({
2538
2641
  message: "Trendyol rate limit exceeded",
2539
2642
  status,
2540
2643
  retryAfterMs,
2541
- data
2644
+ data,
2645
+ issues
2542
2646
  });
2543
2647
  case 500:
2544
2648
  case 502:
@@ -2547,7 +2651,8 @@ function mapHttpError(status, body, retryAfterMs) {
2547
2651
  return new core.ServerError({
2548
2652
  message: `Trendyol server error (${status})`,
2549
2653
  status,
2550
- data
2654
+ data,
2655
+ issues
2551
2656
  });
2552
2657
  default:
2553
2658
  return new core.LoncaError({
@@ -2555,10 +2660,42 @@ function mapHttpError(status, body, retryAfterMs) {
2555
2660
  message: `Trendyol unexpected response (${status})`,
2556
2661
  status,
2557
2662
  data,
2663
+ issues,
2558
2664
  retryable: false
2559
2665
  });
2560
2666
  }
2561
2667
  }
2668
+ function normalizeErrorIssues(body) {
2669
+ const errors = extractErrorsArray(body);
2670
+ if (!errors) return [];
2671
+ const issues = [];
2672
+ for (const entry of errors) {
2673
+ if (typeof entry === "string") {
2674
+ issues.push({ message: entry });
2675
+ continue;
2676
+ }
2677
+ if (entry && typeof entry === "object") {
2678
+ const e = entry;
2679
+ if (typeof e.message !== "string") continue;
2680
+ const issue = { message: e.message };
2681
+ if (typeof e.field === "string") issue.field = e.field;
2682
+ if (typeof e.code === "string") issue.code = e.code;
2683
+ issues.push(issue);
2684
+ }
2685
+ }
2686
+ return issues;
2687
+ }
2688
+ function extractErrorsArray(body) {
2689
+ if (!body || typeof body !== "object") return void 0;
2690
+ const b = body;
2691
+ if (Array.isArray(b.errors)) return b.errors;
2692
+ const nested = b.body;
2693
+ if (nested && typeof nested === "object") {
2694
+ const n2 = nested;
2695
+ if (Array.isArray(n2.errors)) return n2.errors;
2696
+ }
2697
+ return void 0;
2698
+ }
2562
2699
  function parseRetryAfter(header) {
2563
2700
  if (!header) return void 0;
2564
2701
  const seconds = Number(header);
@@ -2654,11 +2791,11 @@ var TrendyolTransport = class {
2654
2791
  },
2655
2792
  {
2656
2793
  signal: opts.signal,
2657
- onRetry: (err, attempt, delay) => {
2794
+ onRetry: (err, attempt, delay2) => {
2658
2795
  if (err instanceof core.LoncaError) {
2659
2796
  this.logger.warn("trendyol.retry", {
2660
2797
  attempt,
2661
- delayMs: delay,
2798
+ delayMs: delay2,
2662
2799
  code: err.code,
2663
2800
  status: err.status
2664
2801
  });
@@ -2716,12 +2853,16 @@ function createTrendyolClient(opts) {
2716
2853
  logger: opts.logger,
2717
2854
  timeoutMs: opts.timeoutMs
2718
2855
  });
2856
+ return buildClient(transport);
2857
+ }
2858
+ function buildClient(transport) {
2859
+ const products = new ProductsResource(transport);
2719
2860
  return {
2720
2861
  brands: new BrandsResource(transport),
2721
2862
  categories: new CategoriesResource(transport),
2722
2863
  suppliers: new SuppliersResource(transport),
2723
- products: new ProductsResource(transport),
2724
- inventory: new InventoryResource(transport),
2864
+ products,
2865
+ inventory: new InventoryResource(transport, (id) => products.getBatchStatus(id)),
2725
2866
  orders: new OrdersResource(transport),
2726
2867
  claims: new ClaimsResource(transport),
2727
2868
  webhooks: new WebhooksResource(transport),
@@ -2732,7 +2873,8 @@ function createTrendyolClient(opts) {
2732
2873
  testOrders: new TestOrdersResource(transport),
2733
2874
  locations: new LocationsResource(transport),
2734
2875
  exportCenter: new ExportCenterResource(transport),
2735
- videos: new VideosResource(transport)
2876
+ videos: new VideosResource(transport),
2877
+ capabilities: trendyolCapabilities
2736
2878
  };
2737
2879
  }
2738
2880
  function parseWebhookEvent(rawBody) {
@@ -2769,6 +2911,28 @@ function parseWebhookEvent(rawBody) {
2769
2911
  raw: body
2770
2912
  };
2771
2913
  }
2914
+ var statusMap = {
2915
+ Created: "created",
2916
+ Awaiting: "created",
2917
+ // new order awaiting seller acceptance
2918
+ Verified: "created",
2919
+ // accepted/verified, before picking
2920
+ Picking: "picking",
2921
+ UnPacked: "picking",
2922
+ // back in fulfillment after being unpacked
2923
+ Invoiced: "invoiced",
2924
+ Shipped: "shipped",
2925
+ AtCollectionPoint: "shipped",
2926
+ // in transit, awaiting pickup
2927
+ UnDelivered: "shipped",
2928
+ // delivery attempt failed; still in carrier's hands
2929
+ Delivered: "delivered",
2930
+ Cancelled: "cancelled",
2931
+ UnSupplied: "cancelled",
2932
+ // seller couldn't supply → effectively cancelled
2933
+ Returned: "returned"
2934
+ };
2935
+ var normalizeStatus = core.createStatusNormalizer(statusMap);
2772
2936
 
2773
2937
  exports.BrandsResource = BrandsResource;
2774
2938
  exports.CategoriesResource = CategoriesResource;
@@ -2788,6 +2952,10 @@ exports.VideosResource = VideosResource;
2788
2952
  exports.WebhooksResource = WebhooksResource;
2789
2953
  exports.createTrendyolClient = createTrendyolClient;
2790
2954
  exports.normalizeShipmentPackage = normalizeShipmentPackage;
2955
+ exports.normalizeStatus = normalizeStatus;
2791
2956
  exports.parseWebhookEvent = parseWebhookEvent;
2957
+ exports.pollBatchStatus = pollBatchStatus;
2958
+ exports.statusMap = statusMap;
2959
+ exports.trendyolCapabilities = trendyolCapabilities;
2792
2960
  //# sourceMappingURL=index.cjs.map
2793
2961
  //# sourceMappingURL=index.cjs.map