@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/README.md +4 -0
- package/dist/client-0omWgpd_.d.cts +2526 -0
- package/dist/client-0omWgpd_.d.ts +2526 -0
- package/dist/index.cjs +180 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -2452
- package/dist/index.d.ts +24 -2452
- package/dist/index.js +178 -14
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +2919 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +46 -0
- package/dist/testing.d.ts +46 -0
- package/dist/testing.js +2917 -0
- package/dist/testing.js.map +1 -0
- package/package.json +8 -3
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
|
-
|
|
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
|
-
|
|
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,
|
|
2794
|
+
onRetry: (err, attempt, delay2) => {
|
|
2658
2795
|
if (err instanceof core.LoncaError) {
|
|
2659
2796
|
this.logger.warn("trendyol.retry", {
|
|
2660
2797
|
attempt,
|
|
2661
|
-
delayMs:
|
|
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
|
|
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
|