@lonca/trendyol 0.8.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 +32 -26
- package/dist/client-0omWgpd_.d.cts +2526 -0
- package/dist/client-0omWgpd_.d.ts +2526 -0
- package/dist/index.cjs +505 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -2183
- package/dist/index.d.ts +24 -2183
- package/dist/index.js +501 -20
- 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
|
|
@@ -395,6 +395,269 @@ var ClaimsResource = class {
|
|
|
395
395
|
return rows.map((row) => ({ raw: row }));
|
|
396
396
|
}
|
|
397
397
|
};
|
|
398
|
+
var MAX_BATCH_ITEMS = 5e3;
|
|
399
|
+
var ExportCenterResource = class {
|
|
400
|
+
constructor(transport, limiter) {
|
|
401
|
+
this.transport = transport;
|
|
402
|
+
this.limiter = limiter ?? new TokenBucketRateLimiter({ capacity: 1e3, intervalMs: 6e4 });
|
|
403
|
+
}
|
|
404
|
+
transport;
|
|
405
|
+
limiter;
|
|
406
|
+
// ─── Products ────────────────────────────────────────────────────────────
|
|
407
|
+
/**
|
|
408
|
+
* List Export Center-approved products. Uses Trendyol's `pageKey`
|
|
409
|
+
* pagination — the first call leaves `pageKey` empty; subsequent
|
|
410
|
+
* calls pass the `x-paging-key` value from the previous response.
|
|
411
|
+
*/
|
|
412
|
+
async listProducts(params = {}) {
|
|
413
|
+
const query = {};
|
|
414
|
+
if (params.barcodes && params.barcodes.length > 0) query.barcodes = params.barcodes.join(",");
|
|
415
|
+
if (params.pageKey) query.pageKey = params.pageKey;
|
|
416
|
+
if (params.size !== void 0) query.size = params.size;
|
|
417
|
+
const data = await this.transport.request({
|
|
418
|
+
method: "GET",
|
|
419
|
+
path: `/integration/ecgw/v2/${this.transport.sellerId}/products`,
|
|
420
|
+
query: Object.keys(query).length ? query : void 0,
|
|
421
|
+
rateLimiter: this.limiter
|
|
422
|
+
});
|
|
423
|
+
const rows = pickArray(data);
|
|
424
|
+
return rows.map((r) => ({ raw: r ?? {} }));
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Create Export Center products. Maximum 5000 per call. Returns a
|
|
428
|
+
* `batchId` you can poll via `getBatchStatus(batchId)`.
|
|
429
|
+
*
|
|
430
|
+
* @throws {ValidationError} when `products` is empty / oversized.
|
|
431
|
+
*/
|
|
432
|
+
async createProducts(products) {
|
|
433
|
+
this.assertBatch(products, "createProducts");
|
|
434
|
+
const data = await this.transport.request({
|
|
435
|
+
method: "POST",
|
|
436
|
+
path: `/integration/ecgw/v2/${this.transport.sellerId}/products`,
|
|
437
|
+
body: { products },
|
|
438
|
+
rateLimiter: this.limiter
|
|
439
|
+
});
|
|
440
|
+
return normalizeBatchResponse(data);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Update Export Center prices. **Trendyol allows one price update per
|
|
444
|
+
* barcode per day.** Returns a `batchId`.
|
|
445
|
+
*
|
|
446
|
+
* @throws {ValidationError} when `priceInfos` is empty / oversized.
|
|
447
|
+
*/
|
|
448
|
+
async updatePrices(priceInfos) {
|
|
449
|
+
this.assertBatch(priceInfos, "updatePrices");
|
|
450
|
+
const data = await this.transport.request({
|
|
451
|
+
method: "POST",
|
|
452
|
+
path: `/integration/ecgw/v1/${this.transport.sellerId}/prices`,
|
|
453
|
+
body: { priceInfos },
|
|
454
|
+
rateLimiter: this.limiter
|
|
455
|
+
});
|
|
456
|
+
return normalizeBatchResponse(data);
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Update Export Center stocks. Returns a `batchId`. Sellers using
|
|
460
|
+
* Trendyol's shared inventory cannot use this endpoint (per portal docs).
|
|
461
|
+
*
|
|
462
|
+
* @throws {ValidationError} when `items` is empty / oversized.
|
|
463
|
+
*/
|
|
464
|
+
async updateStocks(items) {
|
|
465
|
+
this.assertBatch(items, "updateStocks");
|
|
466
|
+
const data = await this.transport.request({
|
|
467
|
+
method: "POST",
|
|
468
|
+
path: `/integration/ecgw/v1/${this.transport.sellerId}/stocks`,
|
|
469
|
+
body: { items },
|
|
470
|
+
rateLimiter: this.limiter
|
|
471
|
+
});
|
|
472
|
+
return normalizeBatchResponse(data);
|
|
473
|
+
}
|
|
474
|
+
// ─── Batch status ────────────────────────────────────────────────────────
|
|
475
|
+
/**
|
|
476
|
+
* Look up the status of a previously-submitted batch. Trendyol retains
|
|
477
|
+
* batch records for **24 hours** only — older `batchId`s return `404`.
|
|
478
|
+
*/
|
|
479
|
+
async getBatchStatus(batchId) {
|
|
480
|
+
if (!batchId) {
|
|
481
|
+
throw new ValidationError({ message: "exportCenter.getBatchStatus: batchId is required" });
|
|
482
|
+
}
|
|
483
|
+
const data = await this.transport.request({
|
|
484
|
+
method: "GET",
|
|
485
|
+
path: `/integration/ecgw/v1/${this.transport.sellerId}/check-status`,
|
|
486
|
+
query: { batchId },
|
|
487
|
+
rateLimiter: this.limiter
|
|
488
|
+
});
|
|
489
|
+
return normalizeBatchStatus(data);
|
|
490
|
+
}
|
|
491
|
+
// ─── Packages ────────────────────────────────────────────────────────────
|
|
492
|
+
/** List daily Export Center packages (V2 — query-based filters). */
|
|
493
|
+
async listPackagesV2(params = {}) {
|
|
494
|
+
const data = await this.transport.request({
|
|
495
|
+
method: "GET",
|
|
496
|
+
path: `/integration/ecgw/v2/${this.transport.sellerId}/packages`,
|
|
497
|
+
query: {
|
|
498
|
+
trackingNumber: params.trackingNumber,
|
|
499
|
+
status: params.status,
|
|
500
|
+
creationStartDate: params.creationStartDate,
|
|
501
|
+
creationEndDate: params.creationEndDate,
|
|
502
|
+
size: params.size,
|
|
503
|
+
boutiqueId: params.boutiqueId
|
|
504
|
+
},
|
|
505
|
+
rateLimiter: this.limiter
|
|
506
|
+
});
|
|
507
|
+
return pickArray(data).map(normalizePackage);
|
|
508
|
+
}
|
|
509
|
+
/** List Export Center packages (V3 — consolidated, page-based). */
|
|
510
|
+
async listPackagesV3(params = {}) {
|
|
511
|
+
const data = await this.transport.request({
|
|
512
|
+
method: "GET",
|
|
513
|
+
path: `/integration/ecgw/v3/${this.transport.sellerId}/packages`,
|
|
514
|
+
query: {
|
|
515
|
+
status: params.status,
|
|
516
|
+
page: params.offset !== void 0 ? params.offset : void 0,
|
|
517
|
+
size: params.limit,
|
|
518
|
+
creationStartDate: params.creationStartDate,
|
|
519
|
+
creationEndDate: params.creationEndDate
|
|
520
|
+
},
|
|
521
|
+
rateLimiter: this.limiter
|
|
522
|
+
});
|
|
523
|
+
return pickArray(data).map(normalizePackage);
|
|
524
|
+
}
|
|
525
|
+
/** Get the line items inside an Export Center package. */
|
|
526
|
+
async getPackageItems(params) {
|
|
527
|
+
if (!params || !params.packageId) {
|
|
528
|
+
throw new ValidationError({
|
|
529
|
+
message: "exportCenter.getPackageItems: packageId is required"
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
const data = await this.transport.request({
|
|
533
|
+
method: "GET",
|
|
534
|
+
path: `/integration/ecgw/v2/${this.transport.sellerId}/packages/items`,
|
|
535
|
+
query: {
|
|
536
|
+
packageId: params.packageId,
|
|
537
|
+
status: params.status,
|
|
538
|
+
page: params.offset,
|
|
539
|
+
size: params.limit
|
|
540
|
+
},
|
|
541
|
+
rateLimiter: this.limiter
|
|
542
|
+
});
|
|
543
|
+
return pickArray(data).map((r) => ({ raw: r ?? {} }));
|
|
544
|
+
}
|
|
545
|
+
// ─── Lookup ──────────────────────────────────────────────────────────────
|
|
546
|
+
/** Get the required attributes for an Export Center category. */
|
|
547
|
+
async getCategoryAttributes(categoryId) {
|
|
548
|
+
if (categoryId === void 0 || categoryId === null || categoryId === "") {
|
|
549
|
+
throw new ValidationError({
|
|
550
|
+
message: "exportCenter.getCategoryAttributes: categoryId is required"
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
const data = await this.transport.request({
|
|
554
|
+
method: "GET",
|
|
555
|
+
path: `/integration/ecgw/v1/${this.transport.sellerId}/lookup/product-categories/${encodeURIComponent(String(categoryId))}/attributes`,
|
|
556
|
+
rateLimiter: this.limiter
|
|
557
|
+
});
|
|
558
|
+
return pickArray(data).map((r) => {
|
|
559
|
+
const row = r ?? {};
|
|
560
|
+
const out = { raw: row };
|
|
561
|
+
if (typeof row.attributeId === "number" || typeof row.attributeId === "string") {
|
|
562
|
+
out.attributeId = row.attributeId;
|
|
563
|
+
}
|
|
564
|
+
if (typeof row.attributeName === "string") out.attributeName = row.attributeName;
|
|
565
|
+
if (typeof row.required === "boolean") out.required = row.required;
|
|
566
|
+
if (Array.isArray(row.values)) out.values = row.values;
|
|
567
|
+
return out;
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
/** Get the care-instruction lookup values used by `createProducts`. */
|
|
571
|
+
async getCareInstructions() {
|
|
572
|
+
const data = await this.transport.request({
|
|
573
|
+
method: "GET",
|
|
574
|
+
path: `/integration/ecgw/v1/${this.transport.sellerId}/lookup/care-instructions`,
|
|
575
|
+
rateLimiter: this.limiter
|
|
576
|
+
});
|
|
577
|
+
return pickArray(data).map(normalizeNamedRef);
|
|
578
|
+
}
|
|
579
|
+
/** Get the material-composition lookup values used by `createProducts`. */
|
|
580
|
+
async getCompositions() {
|
|
581
|
+
const data = await this.transport.request({
|
|
582
|
+
method: "GET",
|
|
583
|
+
path: `/integration/ecgw/v1/${this.transport.sellerId}/lookup/compositions`,
|
|
584
|
+
rateLimiter: this.limiter
|
|
585
|
+
});
|
|
586
|
+
return pickArray(data).map(normalizeNamedRef);
|
|
587
|
+
}
|
|
588
|
+
/** Get the country-of-origin lookup values used by `createProducts`. */
|
|
589
|
+
async getOrigins() {
|
|
590
|
+
const data = await this.transport.request({
|
|
591
|
+
method: "GET",
|
|
592
|
+
path: `/integration/ecgw/v1/${this.transport.sellerId}/lookup/origins`,
|
|
593
|
+
rateLimiter: this.limiter
|
|
594
|
+
});
|
|
595
|
+
return pickArray(data).map((r) => {
|
|
596
|
+
const row = r ?? {};
|
|
597
|
+
const out = { raw: row };
|
|
598
|
+
if (typeof row.id === "number" || typeof row.id === "string") out.id = row.id;
|
|
599
|
+
if (typeof row.name === "string") out.name = row.name;
|
|
600
|
+
if (typeof row.countryCode === "string") out.countryCode = row.countryCode;
|
|
601
|
+
return out;
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
605
|
+
assertBatch(items, methodLabel) {
|
|
606
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
607
|
+
throw new ValidationError({
|
|
608
|
+
message: `exportCenter.${methodLabel}: items must not be empty`
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
if (items.length > MAX_BATCH_ITEMS) {
|
|
612
|
+
throw new ValidationError({
|
|
613
|
+
message: `exportCenter.${methodLabel}: max ${MAX_BATCH_ITEMS} items per call (got ${items.length})`
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
function pickArray(data) {
|
|
619
|
+
if (Array.isArray(data)) return data;
|
|
620
|
+
if (data && typeof data === "object") {
|
|
621
|
+
const obj = data;
|
|
622
|
+
if (Array.isArray(obj.content)) return obj.content;
|
|
623
|
+
if (Array.isArray(obj.items)) return obj.items;
|
|
624
|
+
if (Array.isArray(obj.data)) return obj.data;
|
|
625
|
+
if (Array.isArray(obj.products)) return obj.products;
|
|
626
|
+
if (Array.isArray(obj.packages)) return obj.packages;
|
|
627
|
+
}
|
|
628
|
+
return [];
|
|
629
|
+
}
|
|
630
|
+
function normalizeBatchResponse(data) {
|
|
631
|
+
const obj = data && typeof data === "object" ? data : {};
|
|
632
|
+
return {
|
|
633
|
+
batchId: String(obj.batchId ?? obj.batchRequestId ?? obj.id ?? ""),
|
|
634
|
+
raw: obj
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
function normalizeBatchStatus(data) {
|
|
638
|
+
const obj = data && typeof data === "object" ? data : {};
|
|
639
|
+
const out = { raw: obj };
|
|
640
|
+
if (typeof obj.batchId === "string") out.batchId = obj.batchId;
|
|
641
|
+
if (typeof obj.status === "string") out.status = obj.status;
|
|
642
|
+
if (typeof obj.itemCount === "number") out.itemCount = obj.itemCount;
|
|
643
|
+
if (typeof obj.failedItemCount === "number") out.failedItemCount = obj.failedItemCount;
|
|
644
|
+
if (Array.isArray(obj.items)) out.items = obj.items;
|
|
645
|
+
return out;
|
|
646
|
+
}
|
|
647
|
+
function normalizePackage(row) {
|
|
648
|
+
const r = row ?? {};
|
|
649
|
+
const out = { raw: r };
|
|
650
|
+
if (typeof r.packageNumber === "string") out.packageNumber = r.packageNumber;
|
|
651
|
+
if (typeof r.status === "string") out.status = r.status;
|
|
652
|
+
return out;
|
|
653
|
+
}
|
|
654
|
+
function normalizeNamedRef(row) {
|
|
655
|
+
const r = row ?? {};
|
|
656
|
+
const out = { raw: r };
|
|
657
|
+
if (typeof r.id === "number" || typeof r.id === "string") out.id = r.id;
|
|
658
|
+
if (typeof r.name === "string") out.name = r.name;
|
|
659
|
+
return out;
|
|
660
|
+
}
|
|
398
661
|
function toIso2(epochMs) {
|
|
399
662
|
if (typeof epochMs !== "number" || !Number.isFinite(epochMs) || epochMs === 0) {
|
|
400
663
|
return void 0;
|
|
@@ -440,11 +703,14 @@ var FinanceResource = class {
|
|
|
440
703
|
transport;
|
|
441
704
|
limiter;
|
|
442
705
|
async getSettlements(params = {}) {
|
|
443
|
-
return this.queryPage(
|
|
706
|
+
return this.queryPage(
|
|
707
|
+
`/integration/finance/che/sellers/${this.transport.sellerId}/settlements`,
|
|
708
|
+
params
|
|
709
|
+
);
|
|
444
710
|
}
|
|
445
711
|
async getOtherFinancials(params = {}) {
|
|
446
712
|
return this.queryPage(
|
|
447
|
-
`/integration/sellers/${this.transport.sellerId}/otherfinancials`,
|
|
713
|
+
`/integration/finance/che/sellers/${this.transport.sellerId}/otherfinancials`,
|
|
448
714
|
params
|
|
449
715
|
);
|
|
450
716
|
}
|
|
@@ -471,11 +737,37 @@ var FinanceResource = class {
|
|
|
471
737
|
}
|
|
472
738
|
};
|
|
473
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
|
+
}
|
|
474
759
|
var InventoryResource = class {
|
|
475
|
-
|
|
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) {
|
|
476
766
|
this.transport = transport;
|
|
767
|
+
this.getBatchStatus = getBatchStatus;
|
|
477
768
|
}
|
|
478
769
|
transport;
|
|
770
|
+
getBatchStatus;
|
|
479
771
|
/**
|
|
480
772
|
* Update price and/or stock for one or more SKUs (by barcode).
|
|
481
773
|
*
|
|
@@ -489,6 +781,9 @@ var InventoryResource = class {
|
|
|
489
781
|
* ```
|
|
490
782
|
*
|
|
491
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).
|
|
492
787
|
*/
|
|
493
788
|
async update(items) {
|
|
494
789
|
if (items.length === 0) {
|
|
@@ -504,9 +799,70 @@ var InventoryResource = class {
|
|
|
504
799
|
path: `/integration/inventory/sellers/${this.transport.sellerId}/products/price-and-inventory`,
|
|
505
800
|
body: { items }
|
|
506
801
|
});
|
|
507
|
-
|
|
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;
|
|
508
839
|
}
|
|
509
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
|
+
}
|
|
510
866
|
var InvoicesResource = class {
|
|
511
867
|
constructor(transport, limiter) {
|
|
512
868
|
this.transport = transport;
|
|
@@ -792,9 +1148,9 @@ function normalizeCustomer(node) {
|
|
|
792
1148
|
return out;
|
|
793
1149
|
}
|
|
794
1150
|
function normalizeShipmentPackage(rawNode) {
|
|
795
|
-
return
|
|
1151
|
+
return normalizePackage2(rawNode ?? {});
|
|
796
1152
|
}
|
|
797
|
-
function
|
|
1153
|
+
function normalizePackage2(node) {
|
|
798
1154
|
const pkgId = node.shipmentPackageId ?? node.id;
|
|
799
1155
|
const out = {
|
|
800
1156
|
id: pkgId !== void 0 ? String(pkgId) : "",
|
|
@@ -895,7 +1251,7 @@ var OrdersResource = class {
|
|
|
895
1251
|
query,
|
|
896
1252
|
rateLimiter: this.limiter
|
|
897
1253
|
});
|
|
898
|
-
const items = (data.content ?? []).map(
|
|
1254
|
+
const items = (data.content ?? []).map(normalizePackage2);
|
|
899
1255
|
const totalPages = typeof data.totalPages === "number" ? data.totalPages : 0;
|
|
900
1256
|
const result = { items };
|
|
901
1257
|
if (page + 1 < totalPages) {
|
|
@@ -1192,7 +1548,7 @@ var OrdersResource = class {
|
|
|
1192
1548
|
query,
|
|
1193
1549
|
rateLimiter: this.limiter
|
|
1194
1550
|
});
|
|
1195
|
-
const items = (data.content ?? []).map(
|
|
1551
|
+
const items = (data.content ?? []).map(normalizePackage2);
|
|
1196
1552
|
const result = { items };
|
|
1197
1553
|
if (data.hasMore && data.nextCursor) {
|
|
1198
1554
|
result.nextCursor = data.nextCursor;
|
|
@@ -2075,6 +2431,55 @@ var TestOrdersResource = class {
|
|
|
2075
2431
|
});
|
|
2076
2432
|
}
|
|
2077
2433
|
};
|
|
2434
|
+
var VideosResource = class {
|
|
2435
|
+
constructor(transport, options = {}) {
|
|
2436
|
+
this.transport = transport;
|
|
2437
|
+
this.createLimiter = options.createLimiter ?? new TokenBucketRateLimiter({ capacity: 200, intervalMs: 6e4 });
|
|
2438
|
+
this.listLimiter = options.listLimiter ?? new TokenBucketRateLimiter({ capacity: 1e3, intervalMs: 6e4 });
|
|
2439
|
+
}
|
|
2440
|
+
transport;
|
|
2441
|
+
createLimiter;
|
|
2442
|
+
listLimiter;
|
|
2443
|
+
/**
|
|
2444
|
+
* Queue a video for upload. Trendyol downloads from the URL in the
|
|
2445
|
+
* body asynchronously; poll `list()` (filtered by id) for status.
|
|
2446
|
+
*
|
|
2447
|
+
* @throws {ValidationError} when `input` is empty / not an object.
|
|
2448
|
+
*/
|
|
2449
|
+
async create(input) {
|
|
2450
|
+
if (!input || typeof input !== "object") {
|
|
2451
|
+
throw new ValidationError({ message: "videos.create: input is required" });
|
|
2452
|
+
}
|
|
2453
|
+
return this.transport.request({
|
|
2454
|
+
method: "POST",
|
|
2455
|
+
path: `/integration/video/sellers/${this.transport.sellerId}/videos`,
|
|
2456
|
+
body: input,
|
|
2457
|
+
rateLimiter: this.createLimiter
|
|
2458
|
+
});
|
|
2459
|
+
}
|
|
2460
|
+
/** List the seller's integration videos (optionally filtered by id / status). */
|
|
2461
|
+
async list(params = {}) {
|
|
2462
|
+
const data = await this.transport.request({
|
|
2463
|
+
method: "GET",
|
|
2464
|
+
path: `/integration/video/sellers/${this.transport.sellerId}/videos`,
|
|
2465
|
+
query: {
|
|
2466
|
+
id: params.id,
|
|
2467
|
+
sellerIntegrationStatus: params.sellerIntegrationStatus,
|
|
2468
|
+
page: params.offset,
|
|
2469
|
+
size: params.limit
|
|
2470
|
+
},
|
|
2471
|
+
rateLimiter: this.listLimiter
|
|
2472
|
+
});
|
|
2473
|
+
const rows = Array.isArray(data) ? data : Array.isArray(data?.content) ? data.content : Array.isArray(data?.items) ? data.items : [];
|
|
2474
|
+
return rows.map((r) => {
|
|
2475
|
+
const row = r ?? {};
|
|
2476
|
+
const out = { raw: row };
|
|
2477
|
+
if (typeof row.id === "string") out.id = row.id;
|
|
2478
|
+
if (typeof row.status === "string") out.status = row.status;
|
|
2479
|
+
return out;
|
|
2480
|
+
});
|
|
2481
|
+
}
|
|
2482
|
+
};
|
|
2078
2483
|
function normalizeWebhook(node) {
|
|
2079
2484
|
const out = {
|
|
2080
2485
|
id: node.id !== void 0 ? String(node.id) : "",
|
|
@@ -2191,6 +2596,16 @@ var WebhooksResource = class {
|
|
|
2191
2596
|
}
|
|
2192
2597
|
};
|
|
2193
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
|
+
|
|
2194
2609
|
// src/auth.ts
|
|
2195
2610
|
function buildAuthHeader(apiKey, apiSecret) {
|
|
2196
2611
|
const token = Buffer.from(`${apiKey}:${apiSecret}`, "utf8").toString("base64");
|
|
@@ -2201,27 +2616,31 @@ function buildUserAgent(sellerId, integratorName) {
|
|
|
2201
2616
|
}
|
|
2202
2617
|
function mapHttpError(status, body, retryAfterMs) {
|
|
2203
2618
|
const data = { body };
|
|
2619
|
+
const issues = normalizeErrorIssues(body);
|
|
2204
2620
|
switch (status) {
|
|
2205
2621
|
case 401:
|
|
2206
2622
|
return new AuthError({
|
|
2207
2623
|
message: "Trendyol authentication failed (check apiKey / apiSecret)",
|
|
2208
2624
|
status,
|
|
2209
|
-
data
|
|
2625
|
+
data,
|
|
2626
|
+
issues
|
|
2210
2627
|
});
|
|
2211
2628
|
case 403:
|
|
2212
2629
|
return new ValidationError({
|
|
2213
2630
|
message: "Trendyol forbidden (check User-Agent header or endpoint path)",
|
|
2214
2631
|
status,
|
|
2215
|
-
data
|
|
2632
|
+
data,
|
|
2633
|
+
issues
|
|
2216
2634
|
});
|
|
2217
2635
|
case 404:
|
|
2218
|
-
return new NotFoundError({ message: "Trendyol resource not found", status, data });
|
|
2636
|
+
return new NotFoundError({ message: "Trendyol resource not found", status, data, issues });
|
|
2219
2637
|
case 429:
|
|
2220
2638
|
return new RateLimitError({
|
|
2221
2639
|
message: "Trendyol rate limit exceeded",
|
|
2222
2640
|
status,
|
|
2223
2641
|
retryAfterMs,
|
|
2224
|
-
data
|
|
2642
|
+
data,
|
|
2643
|
+
issues
|
|
2225
2644
|
});
|
|
2226
2645
|
case 500:
|
|
2227
2646
|
case 502:
|
|
@@ -2230,7 +2649,8 @@ function mapHttpError(status, body, retryAfterMs) {
|
|
|
2230
2649
|
return new ServerError({
|
|
2231
2650
|
message: `Trendyol server error (${status})`,
|
|
2232
2651
|
status,
|
|
2233
|
-
data
|
|
2652
|
+
data,
|
|
2653
|
+
issues
|
|
2234
2654
|
});
|
|
2235
2655
|
default:
|
|
2236
2656
|
return new LoncaError({
|
|
@@ -2238,10 +2658,42 @@ function mapHttpError(status, body, retryAfterMs) {
|
|
|
2238
2658
|
message: `Trendyol unexpected response (${status})`,
|
|
2239
2659
|
status,
|
|
2240
2660
|
data,
|
|
2661
|
+
issues,
|
|
2241
2662
|
retryable: false
|
|
2242
2663
|
});
|
|
2243
2664
|
}
|
|
2244
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
|
+
}
|
|
2245
2697
|
function parseRetryAfter(header) {
|
|
2246
2698
|
if (!header) return void 0;
|
|
2247
2699
|
const seconds = Number(header);
|
|
@@ -2337,11 +2789,11 @@ var TrendyolTransport = class {
|
|
|
2337
2789
|
},
|
|
2338
2790
|
{
|
|
2339
2791
|
signal: opts.signal,
|
|
2340
|
-
onRetry: (err, attempt,
|
|
2792
|
+
onRetry: (err, attempt, delay2) => {
|
|
2341
2793
|
if (err instanceof LoncaError) {
|
|
2342
2794
|
this.logger.warn("trendyol.retry", {
|
|
2343
2795
|
attempt,
|
|
2344
|
-
delayMs:
|
|
2796
|
+
delayMs: delay2,
|
|
2345
2797
|
code: err.code,
|
|
2346
2798
|
status: err.status
|
|
2347
2799
|
});
|
|
@@ -2399,12 +2851,16 @@ function createTrendyolClient(opts) {
|
|
|
2399
2851
|
logger: opts.logger,
|
|
2400
2852
|
timeoutMs: opts.timeoutMs
|
|
2401
2853
|
});
|
|
2854
|
+
return buildClient(transport);
|
|
2855
|
+
}
|
|
2856
|
+
function buildClient(transport) {
|
|
2857
|
+
const products = new ProductsResource(transport);
|
|
2402
2858
|
return {
|
|
2403
2859
|
brands: new BrandsResource(transport),
|
|
2404
2860
|
categories: new CategoriesResource(transport),
|
|
2405
2861
|
suppliers: new SuppliersResource(transport),
|
|
2406
|
-
products
|
|
2407
|
-
inventory: new InventoryResource(transport),
|
|
2862
|
+
products,
|
|
2863
|
+
inventory: new InventoryResource(transport, (id) => products.getBatchStatus(id)),
|
|
2408
2864
|
orders: new OrdersResource(transport),
|
|
2409
2865
|
claims: new ClaimsResource(transport),
|
|
2410
2866
|
webhooks: new WebhooksResource(transport),
|
|
@@ -2413,7 +2869,10 @@ function createTrendyolClient(opts) {
|
|
|
2413
2869
|
finance: new FinanceResource(transport),
|
|
2414
2870
|
labels: new LabelsResource(transport),
|
|
2415
2871
|
testOrders: new TestOrdersResource(transport),
|
|
2416
|
-
locations: new LocationsResource(transport)
|
|
2872
|
+
locations: new LocationsResource(transport),
|
|
2873
|
+
exportCenter: new ExportCenterResource(transport),
|
|
2874
|
+
videos: new VideosResource(transport),
|
|
2875
|
+
capabilities: trendyolCapabilities
|
|
2417
2876
|
};
|
|
2418
2877
|
}
|
|
2419
2878
|
function parseWebhookEvent(rawBody) {
|
|
@@ -2450,7 +2909,29 @@ function parseWebhookEvent(rawBody) {
|
|
|
2450
2909
|
raw: body
|
|
2451
2910
|
};
|
|
2452
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);
|
|
2453
2934
|
|
|
2454
|
-
export { BrandsResource, CategoriesResource, ClaimsResource, FinanceResource, InventoryResource, InvoicesResource, LabelsResource, LocationsResource, OrdersResource, ProductsResource, QuestionsResource, SuppliersResource, TestOrdersResource, 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 };
|
|
2455
2936
|
//# sourceMappingURL=index.js.map
|
|
2456
2937
|
//# sourceMappingURL=index.js.map
|