@lonca/trendyol 0.3.0 → 0.5.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 CHANGED
@@ -1,15 +1,37 @@
1
1
  # @lonca/trendyol
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@lonca/trendyol.svg)](https://www.npmjs.com/package/@lonca/trendyol)
4
+
3
5
  Type-safe TypeScript SDK for the [Trendyol Marketplace API](https://developers.trendyol.com).
4
6
 
5
- > ⚠️ **Alpha** — Only the `brands` resource is implemented in this initial scaffold. More endpoints (orders, products, inventory, webhooks) land in follow-up releases.
7
+ > **`0.3.0` — bottom-up Phase 2 complete.** The full product surface is now wire-verified: brands · categories · suppliers · products (read + write + lifecycle) · inventory · orders.
8
+
9
+ ## Coverage
10
+
11
+ Each entry is a method on the client. Endpoints behind `★` are discovery-first wire-verified against live Trendyol STAGE — the SDK normalizes any spec/wire mismatch.
12
+
13
+ | Resource | Methods |
14
+ | ---------------- | -------------------------------------------------------------------------------------------------------------------------- |
15
+ | `brands` | `list()`, `search(name)` ★ |
16
+ | `categories` | `list()`, `getAttributes(id)`, `getAttributeValues(catId, attrId)` ★, `getByBarcodes(barcodes)` (AutoFT) |
17
+ | `suppliers` | `getAddresses({forceRefresh?})` (1-hour cache; rate-limited 1 req/hour on Trendyol) |
18
+ | `products` | `list({...})` ★, `listUnapproved({...})` ★, `getBase(barcode)`, `getBuyboxInfo(barcodes)`, `getBatchStatus(id)` |
19
+ | `products` write | `create(items)`, `updateContent(items)`, `updateVariants(items)`, `updateUnapproved(items)` ★, `updateDeliveryInfo(items)` |
20
+ | `products` life | `delete(barcodes)`, `archive(barcodes)`, `unarchive(barcodes)`, `unlock(barcodes)` |
21
+ | `inventory` | `update(items)` (stock + price, async batch) ★ |
22
+ | `orders` | `list({...})` (shipment packages) ★ |
23
+
24
+ Trendyol's product API has 12 V2 read/write/lifecycle endpoints — all 12 are covered. Categories V2 (tree + attributes + values + barcode→category lookup) is complete. V1 endpoints are intentionally skipped (Trendyol sunsets them August 2026). Order sub-endpoints (`updatePackageStatus`, `splitShipmentPackage`, returns, claims, webhooks) land in subsequent phases.
6
25
 
7
26
  ## Install
8
27
 
9
28
  ```bash
10
- pnpm add @lonca/trendyol
29
+ pnpm add @lonca/trendyol @lonca/core
30
+ # or npm install / yarn add
11
31
  ```
12
32
 
33
+ `@lonca/core` is a peer dependency (provides `paginate`, `CursorPage`, error classes, the token-bucket limiter).
34
+
13
35
  ## Quick start
14
36
 
15
37
  ```ts
@@ -24,24 +46,151 @@ const client = createTrendyolClient({
24
46
  integratorName: 'MyCompany', // optional; defaults to 'SelfIntegration'
25
47
  });
26
48
 
27
- for await (const brand of paginate((p) => client.brands.list(p))) {
28
- console.log(brand.id, brand.name);
49
+ // Iterate every product page-by-page.
50
+ for await (const product of paginate((p) => client.products.list(p))) {
51
+ for (const variant of product.variants) {
52
+ console.log(variant.barcode, product.title, variant.stock ?? '?');
53
+ }
29
54
  }
30
55
  ```
31
56
 
32
- ## Authentication
57
+ ## End-to-end: create a product
33
58
 
34
- Trendyol uses HTTP Basic Auth. Get your `sellerId`, `apiKey`, and `apiSecret` from the [Trendyol Partner Panel → Account Info → Integration Information](https://partner.trendyol.com/account/info?tab=integrationInformation) (master-user only).
59
+ The chain `brand → category → category attributes (+ values) → addresses → create → poll → verify` is the canonical "create a real listing" flow.
60
+
61
+ ```ts
62
+ import { createTrendyolClient } from '@lonca/trendyol';
35
63
 
36
- **Production vs Stage** have different credentials. Stage also requires IP whitelisting — register your CI/server IP with Trendyol support.
64
+ const client = createTrendyolClient({ ... });
65
+
66
+ // 1. Resolve brand + category IDs.
67
+ const [brand] = await client.brands.search('TRENDYOLMİLLA');
68
+ const tree = await client.categories.list();
69
+ const category = findLeaf(tree, /Elbise/); // your own walker
70
+
71
+ // 2. Fetch required attributes + a value (Renk = Kırmızı).
72
+ const attrs = await client.categories.getAttributes(category.id);
73
+ const renk = attrs.find((a) => a.name === 'Renk')!;
74
+ const renkValues = await client.categories.getAttributeValues(category.id, renk.id);
75
+ const kirmizi = renkValues.items.find((v) => v.name === 'Kırmızı')!;
76
+
77
+ // 3. Resolve shipment / returning warehouse IDs.
78
+ const addresses = await client.suppliers.getAddresses();
79
+ const shipment = addresses.find((a) => a.isShipmentAddress)!;
80
+ const returning = addresses.find((a) => a.isReturningAddress)!;
81
+
82
+ // 4. Submit the create (async batch).
83
+ const { batchRequestId } = await client.products.create([
84
+ {
85
+ barcode: 'MY-SKU-001',
86
+ title: 'Kırmızı Elbise',
87
+ productMainId: 'MY-MAIN-001',
88
+ brandId: Number(brand.id),
89
+ categoryId: Number(category.id),
90
+ quantity: 10,
91
+ stockCode: 'MY-SC-001',
92
+ dimensionalWeight: 1,
93
+ description: '<p>...</p>',
94
+ listPrice: 299.9,
95
+ salePrice: 199.9,
96
+ images: [{ url: 'https://cdn.example.com/dress.jpg' }],
97
+ vatRate: 20,
98
+ attributes: [{ attributeId: Number(renk.id), attributeValueIds: [Number(kirmizi.id)] }],
99
+ shipmentAddressId: Number(shipment.id),
100
+ returningAddressId: Number(returning.id),
101
+ },
102
+ ]);
103
+
104
+ // 5. Poll until Trendyol finishes content review.
105
+ let result;
106
+ do {
107
+ await new Promise((r) => setTimeout(r, 2000));
108
+ result = await client.products.getBatchStatus(batchRequestId);
109
+ } while (result.items[0]?.status === 'PROCESSING');
110
+
111
+ // 6. Detect approval (or surface a rejection reason).
112
+ if (result.items[0]?.status === 'SUCCESS') {
113
+ const base = await client.products.getBase('MY-SKU-001');
114
+ console.log('Approved:', base.approved, 'contentId:', base.contentId);
115
+ }
116
+ ```
37
117
 
38
- ## Built-in robustness
118
+ ## Per-resource cheat sheet
39
119
 
40
- - **Retry with exponential backoff** on 429 (respects `Retry-After`) and 5xx
41
- - **Per-endpoint rate limiting** (token bucket) sized to Trendyol's documented limits
42
- - **Structured errors** via `@lonca/core` (`AuthError`, `RateLimitError`, `NotFoundError`, `ServerError`, `ValidationError`, `NetworkError`, `TimeoutError`)
43
- - **Correlation ID** auto-generated per request (`x-correlationid` header) for Trendyol-side log tracing
44
- - **`AbortSignal` support** for cancellation throughout
120
+ ```ts
121
+ // brands
122
+ await client.brands.list({ limit: 1000 });
123
+ await client.brands.search('TRENDYOLMİLLA'); // substring + case-insensitive
124
+
125
+ // categories
126
+ const tree = await client.categories.list();
127
+ const attrs = await client.categories.getAttributes(catId);
128
+ const values = await client.categories.getAttributeValues(catId, attrId);
129
+ await client.categories.getByBarcodes(['BC1', 'BC2']); // requires AutoFT enrollment
130
+
131
+ // suppliers (cached 1h)
132
+ await client.suppliers.getAddresses();
133
+ await client.suppliers.getAddresses({ forceRefresh: true });
134
+
135
+ // products — read
136
+ await client.products.list({ barcode: 'BC1' });
137
+ await client.products.listUnapproved({ limit: 50 });
138
+ await client.products.getBase('BC1');
139
+ await client.products.getBuyboxInfo(['BC1', 'BC2']); // max 10 per call
140
+ await client.products.getBatchStatus(batchRequestId);
141
+
142
+ // products — write (all return { batchRequestId }; max 1000 items)
143
+ await client.products.create([...]);
144
+ await client.products.updateContent([{ contentId: 123, title: '...' }]);
145
+ await client.products.updateVariants([{ barcode: 'BC1', stockCode: 'NEW' }]);
146
+ await client.products.updateUnapproved([{ barcode: 'BC1', title: '...', /* fuller payload */ }]);
147
+ await client.products.updateDeliveryInfo([{ barcode: 'BC1', deliveryOptions: { deliveryDuration: 3 } }]);
148
+
149
+ // products — lifecycle
150
+ await client.products.delete(['BC1']); // separately rate-limited (100/min)
151
+ await client.products.archive(['BC1']); // PUT archived=true
152
+ await client.products.unarchive(['BC1']); // PUT archived=false
153
+ await client.products.unlock(['BC1']); // restore after Trendyol price-lock
154
+
155
+ // inventory — async batch
156
+ await client.inventory.update([{ barcode: 'BC1', quantity: 50, salePrice: 199.9, listPrice: 299.9 }]);
157
+
158
+ // orders — shipment packages
159
+ for await (const pkg of paginate((p) => client.orders.list(p))) {
160
+ console.log(pkg.id, pkg.status, pkg.lines.length);
161
+ }
162
+ ```
163
+
164
+ ## Async batch + polling
165
+
166
+ Every write endpoint (`products.create`, `updateContent`, `updateVariants`, `updateUnapproved`, `updateDeliveryInfo`, `delete`, `archive`, `unarchive`, `unlock`, `inventory.update`) is **asynchronous**: Trendyol accepts the batch and returns a `{ batchRequestId }`. Poll the result with:
167
+
168
+ ```ts
169
+ const status = await client.products.getBatchStatus(batchRequestId);
170
+ // status.status: 'PROCESSING' | 'COMPLETED' | 'FAILED'
171
+ // status.items[].status: per-item outcome
172
+ ```
173
+
174
+ **Important:** Trendyol's overall batch `status` can lag at `PROCESSING` even after each `items[].status` has settled. Trust the per-item status, or re-read the affected products via `list({ barcode })` / `getBase(barcode)` to verify the change landed. Batch results are retained for **4 hours** on Trendyol's side.
175
+
176
+ ## Discovery-first wire fixes
177
+
178
+ `@lonca/trendyol` was built by hitting the live Trendyol STAGE for every endpoint before writing types. Several places where the official OpenAPI spec disagrees with the live wire are normalized automatically:
179
+
180
+ - `categories.getAttributeValues`: spec says `attributeValueName`, wire returns `attributeValue` → SDK normalizes to `{ id, name }`
181
+ - `products.listUnapproved`: spec says `media: [{url}]`, wire returns `images: [{url}]` → SDK exposes `images: string[]`
182
+ - `products.getBuyboxInfo`: wire returns extra `secondBuyboxPrice` / `thirdBuyboxPrice` fields beyond spec → both surfaced
183
+ - `products.updateUnapproved`: spec marks only `barcode` required, but live endpoint returns HTTP 500 (`TypeError`) when too many optional fields are omitted → documented in JSDoc
184
+ - `brands.search`: docs claim case-sensitive exact match, live is substring + case-insensitive → documented in JSDoc
185
+ - `getBatchRequestResult`: returns `PROCESSING + empty items` for unknown batch IDs (not 404)
186
+
187
+ Each fix is pinned by a regression mock test using the exact STAGE shape.
188
+
189
+ ## Authentication
190
+
191
+ Trendyol uses HTTP Basic Auth. Get your `sellerId`, `apiKey`, and `apiSecret` from the [Trendyol Partner Panel → Account Info → Integration Information](https://partner.trendyol.com/account/info?tab=integrationInformation) (master-user only).
192
+
193
+ **Production vs Stage have different credentials.** Stage also requires IP whitelisting — register your CI/server IP with Trendyol support (0850 258 58 00). The SDK auto-sends the 5 mandatory headers (`Authorization`, `x-clientip`, `x-correlationid`, `x-agentname`, `User-Agent`).
45
194
 
46
195
  ## Environments
47
196
 
@@ -50,9 +199,18 @@ Trendyol uses HTTP Basic Auth. Get your `sellerId`, `apiKey`, and `apiSecret` fr
50
199
  | `prod` | `https://apigw.trendyol.com` | No IP whitelist |
51
200
  | `stage` | `https://stageapigw.trendyol.com` | IP whitelist required — call Trendyol support |
52
201
 
202
+ ## Built-in robustness
203
+
204
+ - **Retry with exponential backoff** on 429 (respects `Retry-After`) and 5xx
205
+ - **Per-endpoint rate limiting** (token bucket) sized to Trendyol's documented limits — separate buckets for filter (2000/min), batch read (1000/min), buybox (1000/min), writes (1000/min), and delete (100/min)
206
+ - **Structured errors** via `@lonca/core` (`AuthError`, `RateLimitError`, `NotFoundError`, `ServerError`, `ValidationError`, `NetworkError`, `TimeoutError`)
207
+ - **Client-side validation** before the network: empty batches, oversized batches (>1000 items), >10 buybox barcodes throw `ValidationError`
208
+ - **Correlation ID** auto-generated per request for Trendyol-side log tracing
209
+ - **`AbortSignal` support** throughout
210
+
53
211
  ## Stability
54
212
 
55
- `0.x` — alpha. Public APIs may change between minor versions until `1.0.0`.
213
+ `0.x` — alpha. The product surface is feature-complete and STAGE-verified, but public types may still adjust between minor versions until `1.0.0`.
56
214
 
57
215
  ## License
58
216