@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.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TokenBucketRateLimiter, ValidationError, noopLogger, retry,
|
|
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
|
|
@@ -737,11 +737,37 @@ var FinanceResource = class {
|
|
|
737
737
|
}
|
|
738
738
|
};
|
|
739
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
|
+
}
|
|
740
759
|
var InventoryResource = class {
|
|
741
|
-
|
|
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) {
|
|
742
766
|
this.transport = transport;
|
|
767
|
+
this.getBatchStatus = getBatchStatus;
|
|
743
768
|
}
|
|
744
769
|
transport;
|
|
770
|
+
getBatchStatus;
|
|
745
771
|
/**
|
|
746
772
|
* Update price and/or stock for one or more SKUs (by barcode).
|
|
747
773
|
*
|
|
@@ -755,6 +781,9 @@ var InventoryResource = class {
|
|
|
755
781
|
* ```
|
|
756
782
|
*
|
|
757
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).
|
|
758
787
|
*/
|
|
759
788
|
async update(items) {
|
|
760
789
|
if (items.length === 0) {
|
|
@@ -770,9 +799,70 @@ var InventoryResource = class {
|
|
|
770
799
|
path: `/integration/inventory/sellers/${this.transport.sellerId}/products/price-and-inventory`,
|
|
771
800
|
body: { items }
|
|
772
801
|
});
|
|
773
|
-
|
|
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;
|
|
774
839
|
}
|
|
775
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
|
+
}
|
|
776
866
|
var InvoicesResource = class {
|
|
777
867
|
constructor(transport, limiter) {
|
|
778
868
|
this.transport = transport;
|
|
@@ -2506,6 +2596,16 @@ var WebhooksResource = class {
|
|
|
2506
2596
|
}
|
|
2507
2597
|
};
|
|
2508
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
|
+
|
|
2509
2609
|
// src/auth.ts
|
|
2510
2610
|
function buildAuthHeader(apiKey, apiSecret) {
|
|
2511
2611
|
const token = Buffer.from(`${apiKey}:${apiSecret}`, "utf8").toString("base64");
|
|
@@ -2516,27 +2616,31 @@ function buildUserAgent(sellerId, integratorName) {
|
|
|
2516
2616
|
}
|
|
2517
2617
|
function mapHttpError(status, body, retryAfterMs) {
|
|
2518
2618
|
const data = { body };
|
|
2619
|
+
const issues = normalizeErrorIssues(body);
|
|
2519
2620
|
switch (status) {
|
|
2520
2621
|
case 401:
|
|
2521
2622
|
return new AuthError({
|
|
2522
2623
|
message: "Trendyol authentication failed (check apiKey / apiSecret)",
|
|
2523
2624
|
status,
|
|
2524
|
-
data
|
|
2625
|
+
data,
|
|
2626
|
+
issues
|
|
2525
2627
|
});
|
|
2526
2628
|
case 403:
|
|
2527
2629
|
return new ValidationError({
|
|
2528
2630
|
message: "Trendyol forbidden (check User-Agent header or endpoint path)",
|
|
2529
2631
|
status,
|
|
2530
|
-
data
|
|
2632
|
+
data,
|
|
2633
|
+
issues
|
|
2531
2634
|
});
|
|
2532
2635
|
case 404:
|
|
2533
|
-
return new NotFoundError({ message: "Trendyol resource not found", status, data });
|
|
2636
|
+
return new NotFoundError({ message: "Trendyol resource not found", status, data, issues });
|
|
2534
2637
|
case 429:
|
|
2535
2638
|
return new RateLimitError({
|
|
2536
2639
|
message: "Trendyol rate limit exceeded",
|
|
2537
2640
|
status,
|
|
2538
2641
|
retryAfterMs,
|
|
2539
|
-
data
|
|
2642
|
+
data,
|
|
2643
|
+
issues
|
|
2540
2644
|
});
|
|
2541
2645
|
case 500:
|
|
2542
2646
|
case 502:
|
|
@@ -2545,7 +2649,8 @@ function mapHttpError(status, body, retryAfterMs) {
|
|
|
2545
2649
|
return new ServerError({
|
|
2546
2650
|
message: `Trendyol server error (${status})`,
|
|
2547
2651
|
status,
|
|
2548
|
-
data
|
|
2652
|
+
data,
|
|
2653
|
+
issues
|
|
2549
2654
|
});
|
|
2550
2655
|
default:
|
|
2551
2656
|
return new LoncaError({
|
|
@@ -2553,10 +2658,42 @@ function mapHttpError(status, body, retryAfterMs) {
|
|
|
2553
2658
|
message: `Trendyol unexpected response (${status})`,
|
|
2554
2659
|
status,
|
|
2555
2660
|
data,
|
|
2661
|
+
issues,
|
|
2556
2662
|
retryable: false
|
|
2557
2663
|
});
|
|
2558
2664
|
}
|
|
2559
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
|
+
}
|
|
2560
2697
|
function parseRetryAfter(header) {
|
|
2561
2698
|
if (!header) return void 0;
|
|
2562
2699
|
const seconds = Number(header);
|
|
@@ -2652,11 +2789,11 @@ var TrendyolTransport = class {
|
|
|
2652
2789
|
},
|
|
2653
2790
|
{
|
|
2654
2791
|
signal: opts.signal,
|
|
2655
|
-
onRetry: (err, attempt,
|
|
2792
|
+
onRetry: (err, attempt, delay2) => {
|
|
2656
2793
|
if (err instanceof LoncaError) {
|
|
2657
2794
|
this.logger.warn("trendyol.retry", {
|
|
2658
2795
|
attempt,
|
|
2659
|
-
delayMs:
|
|
2796
|
+
delayMs: delay2,
|
|
2660
2797
|
code: err.code,
|
|
2661
2798
|
status: err.status
|
|
2662
2799
|
});
|
|
@@ -2714,12 +2851,16 @@ function createTrendyolClient(opts) {
|
|
|
2714
2851
|
logger: opts.logger,
|
|
2715
2852
|
timeoutMs: opts.timeoutMs
|
|
2716
2853
|
});
|
|
2854
|
+
return buildClient(transport);
|
|
2855
|
+
}
|
|
2856
|
+
function buildClient(transport) {
|
|
2857
|
+
const products = new ProductsResource(transport);
|
|
2717
2858
|
return {
|
|
2718
2859
|
brands: new BrandsResource(transport),
|
|
2719
2860
|
categories: new CategoriesResource(transport),
|
|
2720
2861
|
suppliers: new SuppliersResource(transport),
|
|
2721
|
-
products
|
|
2722
|
-
inventory: new InventoryResource(transport),
|
|
2862
|
+
products,
|
|
2863
|
+
inventory: new InventoryResource(transport, (id) => products.getBatchStatus(id)),
|
|
2723
2864
|
orders: new OrdersResource(transport),
|
|
2724
2865
|
claims: new ClaimsResource(transport),
|
|
2725
2866
|
webhooks: new WebhooksResource(transport),
|
|
@@ -2730,7 +2871,8 @@ function createTrendyolClient(opts) {
|
|
|
2730
2871
|
testOrders: new TestOrdersResource(transport),
|
|
2731
2872
|
locations: new LocationsResource(transport),
|
|
2732
2873
|
exportCenter: new ExportCenterResource(transport),
|
|
2733
|
-
videos: new VideosResource(transport)
|
|
2874
|
+
videos: new VideosResource(transport),
|
|
2875
|
+
capabilities: trendyolCapabilities
|
|
2734
2876
|
};
|
|
2735
2877
|
}
|
|
2736
2878
|
function parseWebhookEvent(rawBody) {
|
|
@@ -2767,7 +2909,29 @@ function parseWebhookEvent(rawBody) {
|
|
|
2767
2909
|
raw: body
|
|
2768
2910
|
};
|
|
2769
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);
|
|
2770
2934
|
|
|
2771
|
-
export { BrandsResource, CategoriesResource, ClaimsResource, ExportCenterResource, FinanceResource, InventoryResource, InvoicesResource, LabelsResource, LocationsResource, OrdersResource, ProductsResource, QuestionsResource, SuppliersResource, TestOrdersResource, VideosResource, 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 };
|
|
2772
2936
|
//# sourceMappingURL=index.js.map
|
|
2773
2937
|
//# sourceMappingURL=index.js.map
|