@lonca/trendyol 0.10.0 → 0.11.1
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 +18 -33
- package/dist/{client-0omWgpd_.d.cts → client-CCA1P7sb.d.cts} +117 -18
- package/dist/{client-0omWgpd_.d.ts → client-CCA1P7sb.d.ts} +117 -18
- package/dist/index.cjs +104 -107
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +105 -108
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +104 -107
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +105 -108
- package/dist/testing.js.map +1 -1
- package/package.json +2 -2
package/dist/testing.cjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var core = require('@lonca/core');
|
|
4
|
-
var crypto = require('crypto');
|
|
5
4
|
|
|
6
5
|
// src/resources/brands.ts
|
|
7
6
|
var DEFAULT_PAGE_SIZE = 1e3;
|
|
@@ -41,7 +40,7 @@ var BrandsResource = class {
|
|
|
41
40
|
* `createProducts` and don't want to page through the full `list()`
|
|
42
41
|
* (1000 brands per page).
|
|
43
42
|
*
|
|
44
|
-
* **
|
|
43
|
+
* **Wire fact (verified STAGE 2026-05-25):** Trendyol's
|
|
45
44
|
* doc claims this is a case-sensitive *exact* match, but live behaviour
|
|
46
45
|
* is **substring + case-insensitive** — `search('Trendyol')` returns
|
|
47
46
|
* 17 hits including `TRENDYOLMILLA`, `trendyol vavist`, `Trendyol Üyelik`.
|
|
@@ -1066,6 +1065,7 @@ var LocationsResource = class {
|
|
|
1066
1065
|
};
|
|
1067
1066
|
var MAX_PAGE_SIZE = 200;
|
|
1068
1067
|
var DEFAULT_PAGE_SIZE2 = 50;
|
|
1068
|
+
var MAX_OFFSET_RECORDS = 1e4;
|
|
1069
1069
|
function toIso3(epochMs) {
|
|
1070
1070
|
if (typeof epochMs !== "number" || !Number.isFinite(epochMs) || epochMs === 0) {
|
|
1071
1071
|
return void 0;
|
|
@@ -1239,6 +1239,11 @@ var OrdersResource = class {
|
|
|
1239
1239
|
async list(params = {}) {
|
|
1240
1240
|
const size = Math.min(params.limit ?? DEFAULT_PAGE_SIZE2, MAX_PAGE_SIZE);
|
|
1241
1241
|
const page = params.cursor ? Number.parseInt(params.cursor, 10) : 0;
|
|
1242
|
+
if ((page + 1) * size > MAX_OFFSET_RECORDS) {
|
|
1243
|
+
throw new core.ValidationError({
|
|
1244
|
+
message: `getShipmentPackages can reach at most ${MAX_OFFSET_RECORDS} records (page ${page} \xD7 size ${size} exceeds it); use listStream() for full scans`
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1242
1247
|
const query = { page, size };
|
|
1243
1248
|
if (params.status) query.status = params.status;
|
|
1244
1249
|
if (params.orderNumber) query.orderNumber = params.orderNumber;
|
|
@@ -1253,7 +1258,8 @@ var OrdersResource = class {
|
|
|
1253
1258
|
const items = (data.content ?? []).map(normalizePackage2);
|
|
1254
1259
|
const totalPages = typeof data.totalPages === "number" ? data.totalPages : 0;
|
|
1255
1260
|
const result = { items };
|
|
1256
|
-
|
|
1261
|
+
const nextPageWithinCap = (page + 2) * size <= MAX_OFFSET_RECORDS;
|
|
1262
|
+
if (page + 1 < totalPages && nextPageWithinCap) {
|
|
1257
1263
|
result.nextCursor = String(page + 1);
|
|
1258
1264
|
}
|
|
1259
1265
|
return result;
|
|
@@ -1692,6 +1698,7 @@ var OrdersResource = class {
|
|
|
1692
1698
|
};
|
|
1693
1699
|
var DEFAULT_PAGE_SIZE3 = 50;
|
|
1694
1700
|
var MAX_PAGE_SIZE2 = 1e3;
|
|
1701
|
+
var MAX_INVENTORY_PAGE_SIZE = 100;
|
|
1695
1702
|
var MAX_BUYBOX_BARCODES = 10;
|
|
1696
1703
|
function normalizeRef(ref) {
|
|
1697
1704
|
return {
|
|
@@ -1757,6 +1764,30 @@ function normalizeProduct(node) {
|
|
|
1757
1764
|
if (node.lastModifiedBy !== void 0) out.lastModifiedBy = node.lastModifiedBy;
|
|
1758
1765
|
return out;
|
|
1759
1766
|
}
|
|
1767
|
+
function normalizeStockPriceVariant(node) {
|
|
1768
|
+
const out = {
|
|
1769
|
+
variantId: node.variantId !== void 0 ? String(node.variantId) : "",
|
|
1770
|
+
barcode: node.barcode ?? "",
|
|
1771
|
+
raw: node
|
|
1772
|
+
};
|
|
1773
|
+
if (typeof node.salePrice === "number") out.salePrice = node.salePrice;
|
|
1774
|
+
if (typeof node.listPrice === "number") out.listPrice = node.listPrice;
|
|
1775
|
+
if (typeof node.quantity === "number") out.quantity = node.quantity;
|
|
1776
|
+
if (node.stockCode !== void 0) out.stockCode = node.stockCode;
|
|
1777
|
+
if (node.stockLastModifiedDate !== void 0 && node.stockLastModifiedDate !== null) {
|
|
1778
|
+
const iso = toIso4(node.stockLastModifiedDate);
|
|
1779
|
+
if (iso) out.stockLastModifiedAt = iso;
|
|
1780
|
+
}
|
|
1781
|
+
return out;
|
|
1782
|
+
}
|
|
1783
|
+
function normalizeStockPrice(node) {
|
|
1784
|
+
return {
|
|
1785
|
+
contentId: node.contentId !== void 0 ? String(node.contentId) : "",
|
|
1786
|
+
productMainId: node.productMainId ?? "",
|
|
1787
|
+
variants: (node.variants ?? []).map(normalizeStockPriceVariant),
|
|
1788
|
+
raw: node
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1760
1791
|
function normalizeUnapprovedRejectReason(r) {
|
|
1761
1792
|
const out = {};
|
|
1762
1793
|
if (r.rejectReason !== void 0) out.rejectReason = r.rejectReason;
|
|
@@ -1947,6 +1978,63 @@ var ProductsResource = class {
|
|
|
1947
1978
|
}
|
|
1948
1979
|
return page;
|
|
1949
1980
|
}
|
|
1981
|
+
/**
|
|
1982
|
+
* List **stock and price** for approved products — Trendyol's lightweight
|
|
1983
|
+
* `inventory-and-price` filter. A slim alternative to {@link list} when you
|
|
1984
|
+
* only need pricing + stock: the response carries `contentId`,
|
|
1985
|
+
* `productMainId`, and a `variants[]` array with `barcode`, `salePrice`,
|
|
1986
|
+
* `listPrice`, `quantity`, `stockCode`, and `stockLastModifiedAt` — nothing
|
|
1987
|
+
* else.
|
|
1988
|
+
*
|
|
1989
|
+
* Filter by `barcode`, `contentId`, `stockCode`, `productMainId`, or listing
|
|
1990
|
+
* `status`. Sort with `orderByDirection` (`SellerCreatedDate`). `size` caps
|
|
1991
|
+
* at **100** here (tighter than `list`'s 1000).
|
|
1992
|
+
*
|
|
1993
|
+
* Pagination follows the same convention as {@link list}: pass our opaque
|
|
1994
|
+
* `cursor` from the previous response and we forward it as `nextPageToken`
|
|
1995
|
+
* (required once the dataset exceeds 10,000 items).
|
|
1996
|
+
*
|
|
1997
|
+
* @example
|
|
1998
|
+
* ```ts
|
|
1999
|
+
* import { paginate } from '@lonca/core';
|
|
2000
|
+
* for await (const p of paginate((q) =>
|
|
2001
|
+
* client.products.listInventoryAndPrice({ ...q, status: 'onSale' }),
|
|
2002
|
+
* )) {
|
|
2003
|
+
* for (const v of p.variants) {
|
|
2004
|
+
* console.log(v.barcode, v.quantity, v.salePrice);
|
|
2005
|
+
* }
|
|
2006
|
+
* }
|
|
2007
|
+
* ```
|
|
2008
|
+
*/
|
|
2009
|
+
async listInventoryAndPrice(params = {}) {
|
|
2010
|
+
const size = Math.min(params.limit ?? DEFAULT_PAGE_SIZE3, MAX_INVENTORY_PAGE_SIZE);
|
|
2011
|
+
const query = { size };
|
|
2012
|
+
if (params.cursor) {
|
|
2013
|
+
query.nextPageToken = params.cursor;
|
|
2014
|
+
} else {
|
|
2015
|
+
query.page = 0;
|
|
2016
|
+
}
|
|
2017
|
+
if (params.barcode) query.barcode = params.barcode;
|
|
2018
|
+
if (params.contentId) query.contentId = params.contentId;
|
|
2019
|
+
if (params.stockCode) query.stockCode = params.stockCode;
|
|
2020
|
+
if (params.productMainId) query.productMainId = params.productMainId;
|
|
2021
|
+
if (params.status) query.status = params.status;
|
|
2022
|
+
if (params.orderByDirection) query.orderByDirection = params.orderByDirection;
|
|
2023
|
+
const headers = params.storeFrontCode ? { storeFrontCode: params.storeFrontCode } : void 0;
|
|
2024
|
+
const data = await this.transport.request({
|
|
2025
|
+
method: "GET",
|
|
2026
|
+
path: `/integration/product/sellers/${this.transport.sellerId}/products/approved/inventory-and-price`,
|
|
2027
|
+
query,
|
|
2028
|
+
headers,
|
|
2029
|
+
rateLimiter: this.filterLimiter
|
|
2030
|
+
});
|
|
2031
|
+
const items = (data.content ?? []).map(normalizeStockPrice);
|
|
2032
|
+
const page = { items };
|
|
2033
|
+
if (data.nextPageToken) {
|
|
2034
|
+
page.nextCursor = data.nextPageToken;
|
|
2035
|
+
}
|
|
2036
|
+
return page;
|
|
2037
|
+
}
|
|
1950
2038
|
/**
|
|
1951
2039
|
* Poll a batch request returned by an async write (e.g. `createProducts`,
|
|
1952
2040
|
* `updatePriceAndInventory`).
|
|
@@ -2693,14 +2781,6 @@ function extractErrorsArray(body) {
|
|
|
2693
2781
|
}
|
|
2694
2782
|
return void 0;
|
|
2695
2783
|
}
|
|
2696
|
-
function parseRetryAfter(header) {
|
|
2697
|
-
if (!header) return void 0;
|
|
2698
|
-
const seconds = Number(header);
|
|
2699
|
-
if (!Number.isNaN(seconds)) return Math.max(0, seconds * 1e3);
|
|
2700
|
-
const epoch = Date.parse(header);
|
|
2701
|
-
if (!Number.isNaN(epoch)) return Math.max(0, epoch - Date.now());
|
|
2702
|
-
return void 0;
|
|
2703
|
-
}
|
|
2704
2784
|
|
|
2705
2785
|
// src/transport.ts
|
|
2706
2786
|
var BASE_URLS = {
|
|
@@ -2711,95 +2791,26 @@ var TrendyolTransport = class {
|
|
|
2711
2791
|
constructor(config) {
|
|
2712
2792
|
this.config = config;
|
|
2713
2793
|
this.baseUrl = BASE_URLS[config.env];
|
|
2714
|
-
this.
|
|
2715
|
-
|
|
2716
|
-
|
|
2794
|
+
this.requester = core.createRequester({
|
|
2795
|
+
fetch: config.fetch ?? fetch,
|
|
2796
|
+
logger: config.logger,
|
|
2797
|
+
timeoutMs: config.timeoutMs ?? 3e4,
|
|
2798
|
+
label: "Trendyol",
|
|
2799
|
+
logPrefix: "trendyol",
|
|
2800
|
+
buildUrl: (opts) => this.buildUrl(opts.path, opts.query),
|
|
2801
|
+
buildHeaders: (correlationId) => this.buildHeaders(correlationId),
|
|
2802
|
+
mapHttpError
|
|
2803
|
+
});
|
|
2717
2804
|
}
|
|
2718
2805
|
config;
|
|
2719
2806
|
baseUrl;
|
|
2720
|
-
|
|
2721
|
-
timeoutMs;
|
|
2722
|
-
fetchImpl;
|
|
2807
|
+
requester;
|
|
2723
2808
|
/** Seller ID this transport is configured with. Resources read it for path-building. */
|
|
2724
2809
|
get sellerId() {
|
|
2725
2810
|
return this.config.sellerId;
|
|
2726
2811
|
}
|
|
2727
|
-
|
|
2728
|
-
return
|
|
2729
|
-
async (attempt) => {
|
|
2730
|
-
if (opts.rateLimiter) await opts.rateLimiter.acquire(opts.signal);
|
|
2731
|
-
const url = this.buildUrl(opts.path, opts.query);
|
|
2732
|
-
const correlationId = crypto.randomUUID();
|
|
2733
|
-
const headers = this.buildHeaders(correlationId);
|
|
2734
|
-
const init = {
|
|
2735
|
-
method: opts.method,
|
|
2736
|
-
headers,
|
|
2737
|
-
signal: this.composeSignal(opts.signal)
|
|
2738
|
-
};
|
|
2739
|
-
if (opts.body !== void 0 && opts.method !== "GET") {
|
|
2740
|
-
if (opts.body instanceof FormData) {
|
|
2741
|
-
init.body = opts.body;
|
|
2742
|
-
delete headers["Content-Type"];
|
|
2743
|
-
} else {
|
|
2744
|
-
init.body = JSON.stringify(opts.body);
|
|
2745
|
-
}
|
|
2746
|
-
}
|
|
2747
|
-
this.logger.debug("trendyol.request", {
|
|
2748
|
-
method: opts.method,
|
|
2749
|
-
url,
|
|
2750
|
-
correlationId,
|
|
2751
|
-
attempt
|
|
2752
|
-
});
|
|
2753
|
-
let response;
|
|
2754
|
-
try {
|
|
2755
|
-
response = await this.fetchImpl(url, init);
|
|
2756
|
-
} catch (err) {
|
|
2757
|
-
if (err instanceof Error && err.name === "AbortError") {
|
|
2758
|
-
throw new core.TimeoutError({
|
|
2759
|
-
message: `Trendyol request timed out after ${this.timeoutMs}ms`,
|
|
2760
|
-
cause: err
|
|
2761
|
-
});
|
|
2762
|
-
}
|
|
2763
|
-
throw new core.NetworkError({
|
|
2764
|
-
message: "Trendyol network failure",
|
|
2765
|
-
cause: err
|
|
2766
|
-
});
|
|
2767
|
-
}
|
|
2768
|
-
if (!response.ok) {
|
|
2769
|
-
const body = await safeJson(response);
|
|
2770
|
-
const retryAfterMs = parseRetryAfter(response.headers.get("retry-after"));
|
|
2771
|
-
const error = mapHttpError(response.status, body, retryAfterMs);
|
|
2772
|
-
this.logger.warn("trendyol.error", {
|
|
2773
|
-
method: opts.method,
|
|
2774
|
-
url,
|
|
2775
|
-
correlationId,
|
|
2776
|
-
status: response.status,
|
|
2777
|
-
code: error.code,
|
|
2778
|
-
retryable: error.retryable
|
|
2779
|
-
});
|
|
2780
|
-
throw error;
|
|
2781
|
-
}
|
|
2782
|
-
this.logger.debug("trendyol.response", {
|
|
2783
|
-
correlationId,
|
|
2784
|
-
status: response.status
|
|
2785
|
-
});
|
|
2786
|
-
if (response.status === 204) return void 0;
|
|
2787
|
-
return await safeJson(response);
|
|
2788
|
-
},
|
|
2789
|
-
{
|
|
2790
|
-
signal: opts.signal,
|
|
2791
|
-
onRetry: (err, attempt, delay2) => {
|
|
2792
|
-
if (err instanceof core.LoncaError) {
|
|
2793
|
-
this.logger.warn("trendyol.retry", {
|
|
2794
|
-
attempt,
|
|
2795
|
-
delayMs: delay2,
|
|
2796
|
-
code: err.code,
|
|
2797
|
-
status: err.status
|
|
2798
|
-
});
|
|
2799
|
-
}
|
|
2800
|
-
}
|
|
2801
|
-
}
|
|
2802
|
-
);
|
|
2812
|
+
request(opts) {
|
|
2813
|
+
return this.requester(opts);
|
|
2803
2814
|
}
|
|
2804
2815
|
buildUrl(path, query) {
|
|
2805
2816
|
const url = new URL(path, this.baseUrl);
|
|
@@ -2822,21 +2833,7 @@ var TrendyolTransport = class {
|
|
|
2822
2833
|
Accept: "application/json"
|
|
2823
2834
|
};
|
|
2824
2835
|
}
|
|
2825
|
-
composeSignal(external) {
|
|
2826
|
-
const timeoutSignal = AbortSignal.timeout(this.timeoutMs);
|
|
2827
|
-
if (!external) return timeoutSignal;
|
|
2828
|
-
return AbortSignal.any([external, timeoutSignal]);
|
|
2829
|
-
}
|
|
2830
2836
|
};
|
|
2831
|
-
async function safeJson(response) {
|
|
2832
|
-
const text = await response.text();
|
|
2833
|
-
if (!text) return void 0;
|
|
2834
|
-
try {
|
|
2835
|
-
return JSON.parse(text);
|
|
2836
|
-
} catch {
|
|
2837
|
-
return text;
|
|
2838
|
-
}
|
|
2839
|
-
}
|
|
2840
2837
|
|
|
2841
2838
|
// src/client.ts
|
|
2842
2839
|
function buildClient(transport) {
|