shipflow 0.1.1 → 0.3.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 +179 -37
- package/dist/carriers/aramex/adapter.d.ts +7 -0
- package/dist/carriers/aramex/adapter.d.ts.map +1 -1
- package/dist/carriers/aramex/index.js +58 -23
- package/dist/carriers/aramex/index.js.map +4 -4
- package/dist/carriers/aramex/mappers.d.ts +8 -0
- package/dist/carriers/aramex/mappers.d.ts.map +1 -1
- package/dist/carriers/aymakan/adapter.d.ts +5 -0
- package/dist/carriers/aymakan/adapter.d.ts.map +1 -1
- package/dist/carriers/aymakan/index.js +96 -30
- package/dist/carriers/aymakan/index.js.map +4 -4
- package/dist/carriers/aymakan/mappers.d.ts +5 -0
- package/dist/carriers/aymakan/mappers.d.ts.map +1 -1
- package/dist/carriers/smsaexpress/index.js +22 -16
- package/dist/carriers/smsaexpress/index.js.map +3 -3
- package/dist/carriers/smsaexpress/mappers.d.ts.map +1 -1
- package/dist/core/errors.d.ts +19 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/http.d.ts +24 -3
- package/dist/core/http.d.ts.map +1 -1
- package/dist/core/retry.d.ts +42 -0
- package/dist/core/retry.d.ts.map +1 -0
- package/dist/core/schemas.d.ts +196 -812
- package/dist/core/schemas.d.ts.map +1 -1
- package/dist/index-qnxj8bct.js +1067 -0
- package/dist/index-qnxj8bct.js.map +15 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +2 -2
- package/package.json +4 -4
- package/dist/index-qjtxhwzv.js +0 -4602
- package/dist/index-qjtxhwzv.js.map +0 -22
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
WebhookVerificationError,
|
|
8
8
|
validateCreateShipmentInput,
|
|
9
9
|
validatePickupRequest
|
|
10
|
-
} from "../../index-
|
|
10
|
+
} from "../../index-qnxj8bct.js";
|
|
11
11
|
|
|
12
12
|
// src/carriers/aymakan/services.ts
|
|
13
13
|
var AymakanService = {
|
|
@@ -43,6 +43,33 @@ function mapAymakanStatus(statusCode) {
|
|
|
43
43
|
const mapped = AymakanStatusCodes[statusCode];
|
|
44
44
|
return mapped;
|
|
45
45
|
}
|
|
46
|
+
var AymakanStatusLabels = {
|
|
47
|
+
"awb created at origin": "created",
|
|
48
|
+
"shipment created": "created",
|
|
49
|
+
"collected from collection point": "picked_up",
|
|
50
|
+
collected: "picked_up",
|
|
51
|
+
"picked up": "picked_up",
|
|
52
|
+
"received at hub": "at_warehouse",
|
|
53
|
+
"received at warehouse": "at_warehouse",
|
|
54
|
+
"out for delivery": "out_for_delivery",
|
|
55
|
+
"out for final destination": "out_for_delivery",
|
|
56
|
+
delivered: "delivered",
|
|
57
|
+
"shipment is delivered": "delivered",
|
|
58
|
+
"transferred to destination city": "in_transit",
|
|
59
|
+
"in transit": "in_transit",
|
|
60
|
+
"not delivered": "exception",
|
|
61
|
+
"failed attempt": "exception",
|
|
62
|
+
pending: "pending",
|
|
63
|
+
"future delivery": "pending",
|
|
64
|
+
returned: "returned",
|
|
65
|
+
"returned to shipper": "returned",
|
|
66
|
+
"return to origin": "returned",
|
|
67
|
+
cancelled: "cancelled",
|
|
68
|
+
canceled: "cancelled"
|
|
69
|
+
};
|
|
70
|
+
function mapAymakanStatusLabel(label) {
|
|
71
|
+
return AymakanStatusLabels[label.trim().toLowerCase()];
|
|
72
|
+
}
|
|
46
73
|
function mapNationalAddress(addr) {
|
|
47
74
|
if (!addr)
|
|
48
75
|
return;
|
|
@@ -70,17 +97,19 @@ function mapCreateShipmentRequest(input) {
|
|
|
70
97
|
const totalPieces = input.parcels.reduce((sum, p) => sum + p.pieces, 0);
|
|
71
98
|
const totalWeight = input.parcels.reduce((sum, p) => sum + (p.weight.unit === "lb" ? p.weight.value * 0.453592 : p.weight.value), 0);
|
|
72
99
|
const totalItems = input.parcels.reduce((sum, p) => sum + (p.itemsCount ?? p.pieces), 0);
|
|
100
|
+
const codEnabled = input.cod?.enabled === true;
|
|
101
|
+
const declaredValueCurrency = input.declaredValue?.currency ?? "SAR";
|
|
73
102
|
return {
|
|
74
103
|
requested_by: input.options?.requestedBy ?? input.shipper.name,
|
|
75
|
-
declared_value: input.declaredValue?.amount ??
|
|
76
|
-
declared_value_currency:
|
|
104
|
+
declared_value: input.declaredValue?.amount ?? 0,
|
|
105
|
+
declared_value_currency: declaredValueCurrency,
|
|
77
106
|
reference: input.reference,
|
|
78
107
|
customer_tracking: input.options?.customerTracking,
|
|
79
108
|
service_type: resolveServiceType(input.serviceType),
|
|
80
|
-
is_cod:
|
|
81
|
-
cod_amount:
|
|
109
|
+
is_cod: codEnabled ? 1 : 0,
|
|
110
|
+
cod_amount: codEnabled ? input.cod?.amount : undefined,
|
|
82
111
|
fulfilment_customer_name: input.options?.fulfilmentCustomerName,
|
|
83
|
-
currency: input.cod?.currency ?? "SAR",
|
|
112
|
+
currency: codEnabled ? input.cod?.currency ?? "SAR" : declaredValueCurrency,
|
|
84
113
|
delivery_name: input.consignee.name,
|
|
85
114
|
delivery_email: input.consignee.email,
|
|
86
115
|
delivery_city: input.consignee.city,
|
|
@@ -152,7 +181,7 @@ function mapShipmentResponse(data) {
|
|
|
152
181
|
statusLabel: data.status_label,
|
|
153
182
|
labelUrl: data.label || undefined,
|
|
154
183
|
pdfLabelUrl: data.pdf_label || undefined,
|
|
155
|
-
codAmount: codAmount && !Number.isNaN(codAmount) ? codAmount : undefined,
|
|
184
|
+
codAmount: codAmount != null && !Number.isNaN(codAmount) && codAmount !== 0 ? codAmount : undefined,
|
|
156
185
|
declaredValue: data.declared_value,
|
|
157
186
|
currency: data.currency,
|
|
158
187
|
createdAt: new Date(data.created_at),
|
|
@@ -181,7 +210,7 @@ function mapTrackingResult(data) {
|
|
|
181
210
|
if (latestEvent && latestEvent.status !== "unknown") {
|
|
182
211
|
status = latestEvent.status;
|
|
183
212
|
} else {
|
|
184
|
-
status = mapAymakanStatus(data.status) ?? latestEvent?.status ?? "unknown";
|
|
213
|
+
status = mapAymakanStatus(data.status) ?? mapAymakanStatusLabel(data.status) ?? latestEvent?.status ?? "unknown";
|
|
185
214
|
}
|
|
186
215
|
return {
|
|
187
216
|
trackingNumber: data.tracking_number,
|
|
@@ -283,11 +312,29 @@ function parseAymakanWebhook(payload, options) {
|
|
|
283
312
|
// src/carriers/aymakan/adapter.ts
|
|
284
313
|
var AYMAKAN_SANDBOX_URL = "https://dev-api.aymakan.com.sa/v2";
|
|
285
314
|
var AYMAKAN_PRODUCTION_URL = "https://api.aymakan.net/v2";
|
|
315
|
+
function aymakanErrorExtractor(json) {
|
|
316
|
+
if (!json || typeof json !== "object") {
|
|
317
|
+
return { hasError: false };
|
|
318
|
+
}
|
|
319
|
+
const obj = json;
|
|
320
|
+
const hasValidationErrors = !!obj.errors && typeof obj.errors === "object" && Object.keys(obj.errors).length > 0;
|
|
321
|
+
const hasError = obj.error === true || obj.success === false || hasValidationErrors;
|
|
322
|
+
if (!hasError) {
|
|
323
|
+
return { hasError: false };
|
|
324
|
+
}
|
|
325
|
+
const message = typeof obj.message === "string" && obj.message || typeof obj.response === "string" && obj.response || typeof obj.error === "string" && obj.error || undefined;
|
|
326
|
+
return {
|
|
327
|
+
hasError: true,
|
|
328
|
+
message,
|
|
329
|
+
errors: hasValidationErrors ? obj.errors : undefined
|
|
330
|
+
};
|
|
331
|
+
}
|
|
286
332
|
|
|
287
333
|
class AymakanAdapter extends BaseCarrierAdapter {
|
|
288
334
|
name = "aymakan";
|
|
289
335
|
supportedCountries = ["SA", "AE", "BH", "KW", "OM", "QA"];
|
|
290
336
|
http;
|
|
337
|
+
errorOpts = { errorExtractor: aymakanErrorExtractor };
|
|
291
338
|
citiesCache = null;
|
|
292
339
|
citiesCacheTime = 0;
|
|
293
340
|
static CITIES_CACHE_TTL = 60 * 60 * 1000;
|
|
@@ -362,7 +409,7 @@ class AymakanAdapter extends BaseCarrierAdapter {
|
|
|
362
409
|
await this.ensureCitiesLoaded();
|
|
363
410
|
const resolved = this.resolveCitiesInInput(input);
|
|
364
411
|
const request = mapCreateShipmentRequest(resolved);
|
|
365
|
-
const response = await this.http.post("/shipping/create", request);
|
|
412
|
+
const response = await this.http.post("/shipping/create", request, this.errorOpts);
|
|
366
413
|
if (!response.success) {
|
|
367
414
|
throw new APIError("Failed to create shipment", {
|
|
368
415
|
carrier: "aymakan",
|
|
@@ -372,29 +419,34 @@ class AymakanAdapter extends BaseCarrierAdapter {
|
|
|
372
419
|
return mapShipmentResponse(response.shipping);
|
|
373
420
|
}
|
|
374
421
|
async createBulkShipments(inputs) {
|
|
422
|
+
if (inputs.length === 0)
|
|
423
|
+
return [];
|
|
375
424
|
if (inputs.length > 30) {
|
|
376
425
|
throw new ValidationError("Aymakan bulk create accepts at most 30 shipments per request", { raw: { count: inputs.length } });
|
|
377
426
|
}
|
|
378
427
|
inputs.forEach(validateCreateShipmentInput);
|
|
379
428
|
await this.ensureCitiesLoaded();
|
|
380
429
|
const requests = inputs.map((i) => this.resolveCitiesInInput(i)).map(mapCreateShipmentRequest);
|
|
381
|
-
const response = await this.http.post("/shipping/create/bulk_shipping", { shipments: requests });
|
|
430
|
+
const response = await this.http.post("/shipping/create/bulk_shipping", { shipments: requests }, this.errorOpts);
|
|
382
431
|
if (!response.success) {
|
|
383
432
|
throw new APIError("Failed to create bulk shipments", {
|
|
384
433
|
carrier: "aymakan",
|
|
385
434
|
raw: response
|
|
386
435
|
});
|
|
387
436
|
}
|
|
437
|
+
if (!Array.isArray(response.shipments)) {
|
|
438
|
+
throw new APIError("Aymakan bulk create response is missing the shipments array", { carrier: "aymakan", raw: response });
|
|
439
|
+
}
|
|
388
440
|
return response.shipments.map(mapShipmentResponse);
|
|
389
441
|
}
|
|
390
442
|
async cancelShipment(trackingNumber) {
|
|
391
443
|
const response = await this.http.post("/shipping/cancel", {
|
|
392
444
|
tracking: trackingNumber
|
|
393
|
-
});
|
|
445
|
+
}, this.errorOpts);
|
|
394
446
|
return response.success === true;
|
|
395
447
|
}
|
|
396
448
|
async cancelByReference(reference) {
|
|
397
|
-
const response = await this.http.post("/shipping/cancel_by_reference", { reference });
|
|
449
|
+
const response = await this.http.post("/shipping/cancel_by_reference", { reference }, this.errorOpts);
|
|
398
450
|
return response.success === true;
|
|
399
451
|
}
|
|
400
452
|
async updateDeliveryAddress(trackingNumber, address) {
|
|
@@ -422,27 +474,34 @@ class AymakanAdapter extends BaseCarrierAdapter {
|
|
|
422
474
|
}
|
|
423
475
|
async trackMultiple(trackingNumbers) {
|
|
424
476
|
const ids = trackingNumbers.map(encodeURIComponent).join(",");
|
|
425
|
-
const response = await this.http.get(`/shipping/track/${ids}
|
|
477
|
+
const response = await this.http.get(`/shipping/track/${ids}`, this.errorOpts);
|
|
426
478
|
if (!response.success) {
|
|
427
479
|
throw new APIError("Failed to track shipments", {
|
|
428
480
|
carrier: "aymakan",
|
|
429
481
|
raw: response
|
|
430
482
|
});
|
|
431
483
|
}
|
|
484
|
+
if (!Array.isArray(response.data?.shipments)) {
|
|
485
|
+
throw new APIError("Aymakan track response is missing data.shipments", {
|
|
486
|
+
carrier: "aymakan",
|
|
487
|
+
raw: response
|
|
488
|
+
});
|
|
489
|
+
}
|
|
432
490
|
return response.data.shipments.map(mapTrackingResult);
|
|
433
491
|
}
|
|
434
492
|
async trackByReference(reference) {
|
|
435
|
-
const response = await this.http.get(`/shipping/by_reference/${encodeURIComponent(reference)}
|
|
436
|
-
|
|
493
|
+
const response = await this.http.get(`/shipping/by_reference/${encodeURIComponent(reference)}`, this.errorOpts);
|
|
494
|
+
const shipment = response.data?.shipments?.[0];
|
|
495
|
+
if (!response.success || !shipment) {
|
|
437
496
|
throw new APIError("Shipment not found", {
|
|
438
497
|
carrier: "aymakan",
|
|
439
498
|
raw: response
|
|
440
499
|
});
|
|
441
500
|
}
|
|
442
|
-
return mapTrackingResult(
|
|
501
|
+
return mapTrackingResult(shipment);
|
|
443
502
|
}
|
|
444
503
|
async getLabel(trackingNumber, _format) {
|
|
445
|
-
const response = await this.http.get(`/shipping/awb/tracking/${encodeURIComponent(trackingNumber)}
|
|
504
|
+
const response = await this.http.get(`/shipping/awb/tracking/${encodeURIComponent(trackingNumber)}`, this.errorOpts);
|
|
446
505
|
if (!response.success || !response.data?.awb_url) {
|
|
447
506
|
const msg = response.message ?? response.response ?? "Failed to get label";
|
|
448
507
|
throw new APIError(msg, { carrier: "aymakan", raw: response });
|
|
@@ -451,7 +510,7 @@ class AymakanAdapter extends BaseCarrierAdapter {
|
|
|
451
510
|
}
|
|
452
511
|
async getBulkLabels(trackingNumbers) {
|
|
453
512
|
const ids = trackingNumbers.map(encodeURIComponent).join(",");
|
|
454
|
-
const response = await this.http.get(`/shipping/bulk_awb/trackings/${ids}
|
|
513
|
+
const response = await this.http.get(`/shipping/bulk_awb/trackings/${ids}`, this.errorOpts);
|
|
455
514
|
if (!response.success || !response.data?.bulk_awb_url) {
|
|
456
515
|
const msg = response.message ?? response.response ?? "Failed to get bulk labels";
|
|
457
516
|
throw new APIError(msg, { carrier: "aymakan", raw: response });
|
|
@@ -459,7 +518,7 @@ class AymakanAdapter extends BaseCarrierAdapter {
|
|
|
459
518
|
return response.data.bulk_awb_url;
|
|
460
519
|
}
|
|
461
520
|
async getPickupCities() {
|
|
462
|
-
const response = await this.http.get("/pickup_request/cities");
|
|
521
|
+
const response = await this.http.get("/pickup_request/cities", this.errorOpts);
|
|
463
522
|
if (!response.success) {
|
|
464
523
|
throw new APIError("Failed to get pickup cities", {
|
|
465
524
|
carrier: "aymakan",
|
|
@@ -484,7 +543,7 @@ class AymakanAdapter extends BaseCarrierAdapter {
|
|
|
484
543
|
await this.ensureCitiesLoaded();
|
|
485
544
|
const resolvedInput = { ...input, city: this.resolveCity(input.city) };
|
|
486
545
|
const request = mapPickupRequest(resolvedInput);
|
|
487
|
-
const response = await this.http.post("/pickup_request/create", request);
|
|
546
|
+
const response = await this.http.post("/pickup_request/create", request, this.errorOpts);
|
|
488
547
|
if (!response.success) {
|
|
489
548
|
throw new APIError("Failed to create pickup", {
|
|
490
549
|
carrier: "aymakan",
|
|
@@ -494,18 +553,22 @@ class AymakanAdapter extends BaseCarrierAdapter {
|
|
|
494
553
|
return mapPickupResponse(response.data);
|
|
495
554
|
}
|
|
496
555
|
async cancelPickup(pickupId) {
|
|
497
|
-
const response = await this.http.post("/pickup_request/cancel", { pickup_request: Number(pickupId) });
|
|
556
|
+
const response = await this.http.post("/pickup_request/cancel", { pickup_request: Number(pickupId) }, this.errorOpts);
|
|
498
557
|
return response.success === true;
|
|
499
558
|
}
|
|
500
559
|
async getPickupRequests() {
|
|
501
|
-
const response = await this.http.get("/pickup_request/list");
|
|
560
|
+
const response = await this.http.get("/pickup_request/list", this.errorOpts);
|
|
502
561
|
if (!response.success) {
|
|
503
562
|
throw new APIError("Failed to get pickup requests", {
|
|
504
563
|
carrier: "aymakan",
|
|
505
564
|
raw: response
|
|
506
565
|
});
|
|
507
566
|
}
|
|
508
|
-
|
|
567
|
+
const list = response.data?.pickupRequests?.data;
|
|
568
|
+
if (!Array.isArray(list)) {
|
|
569
|
+
throw new APIError("Aymakan pickup list response is missing data.pickupRequests.data", { carrier: "aymakan", raw: response });
|
|
570
|
+
}
|
|
571
|
+
return list.map(mapPickupResponse);
|
|
509
572
|
}
|
|
510
573
|
async getCities() {
|
|
511
574
|
const response = await this.http.get("/cities");
|
|
@@ -536,8 +599,8 @@ class AymakanAdapter extends BaseCarrierAdapter {
|
|
|
536
599
|
}
|
|
537
600
|
async createCustomerAddress(address) {
|
|
538
601
|
const request = mapCustomerAddressRequest(address);
|
|
539
|
-
const response = await this.http.post("/address/create", request);
|
|
540
|
-
if (!response.success) {
|
|
602
|
+
const response = await this.http.post("/address/create", request, this.errorOpts);
|
|
603
|
+
if (!response.success || !response.data?.address) {
|
|
541
604
|
throw new APIError("Failed to create address", {
|
|
542
605
|
carrier: "aymakan",
|
|
543
606
|
raw: response
|
|
@@ -559,13 +622,16 @@ class AymakanAdapter extends BaseCarrierAdapter {
|
|
|
559
622
|
};
|
|
560
623
|
}
|
|
561
624
|
async getCustomerAddresses() {
|
|
562
|
-
const response = await this.http.get("/address/list");
|
|
625
|
+
const response = await this.http.get("/address/list", this.errorOpts);
|
|
563
626
|
if (!response.success) {
|
|
564
627
|
throw new APIError("Failed to get customer addresses", {
|
|
565
628
|
carrier: "aymakan",
|
|
566
629
|
raw: response
|
|
567
630
|
});
|
|
568
631
|
}
|
|
632
|
+
if (!Array.isArray(response.data?.address)) {
|
|
633
|
+
throw new APIError("Aymakan address list response is missing data.address", { carrier: "aymakan", raw: response });
|
|
634
|
+
}
|
|
569
635
|
return response.data.address.map((addr) => ({
|
|
570
636
|
id: addr.id,
|
|
571
637
|
title: addr.title,
|
|
@@ -592,8 +658,8 @@ class AymakanAdapter extends BaseCarrierAdapter {
|
|
|
592
658
|
postcode: address.postalCode,
|
|
593
659
|
phone: address.phone,
|
|
594
660
|
description: address.description
|
|
595
|
-
});
|
|
596
|
-
if (!response.success) {
|
|
661
|
+
}, this.errorOpts);
|
|
662
|
+
if (!response.success || !response.data?.address) {
|
|
597
663
|
throw new APIError("Failed to update address", {
|
|
598
664
|
carrier: "aymakan",
|
|
599
665
|
raw: response
|
|
@@ -615,7 +681,7 @@ class AymakanAdapter extends BaseCarrierAdapter {
|
|
|
615
681
|
};
|
|
616
682
|
}
|
|
617
683
|
async deleteCustomerAddress(id) {
|
|
618
|
-
const response = await this.http.delete("/address/delete", { body: { id } });
|
|
684
|
+
const response = await this.http.delete("/address/delete", { body: { id }, errorExtractor: aymakanErrorExtractor });
|
|
619
685
|
return response.success;
|
|
620
686
|
}
|
|
621
687
|
parseWebhook(payload, options) {
|
|
@@ -633,4 +699,4 @@ export {
|
|
|
633
699
|
AymakanAdapter
|
|
634
700
|
};
|
|
635
701
|
|
|
636
|
-
//# debugId=
|
|
702
|
+
//# debugId=52369388ABAA8E1964756E2164756E21
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
"sources": ["../src/carriers/aymakan/services.ts", "../src/carriers/aymakan/mappers.ts", "../src/carriers/aymakan/adapter.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"// file: src/carriers/aymakan/services.ts\n/**\n * Aymakan Service Type Codes\n * Use these constants for type-safe service selection.\n */\n\nexport const AymakanService = {\n /** Default ecommerce service */\n ECOMMERCE: \"ONP\",\n /** Documents or banking docs service */\n DOCUMENTS: \"DOC\",\n /** Same day delivery service */\n SAME_DAY: \"SDD\",\n /** Reverse pickup service */\n REVERSE_PICKUP: \"RVP\",\n /** Exchange service */\n EXCHANGE: \"EXH\",\n /** Lockers service */\n LOCKERS: \"LOC\",\n /** Heavy and bulky service */\n HEAVY: \"BLK\",\n /** Pallet service */\n PALLET: \"PLT\",\n /** Import express (International) */\n IMPORT_EXPRESS: \"IPX\",\n /** Export express (International) */\n EXPORT_EXPRESS: \"EPX\",\n} as const;\n\nexport type AymakanServiceType =\n (typeof AymakanService)[keyof typeof AymakanService];\n\n/**\n * Aymakan Status Codes\n * Maps carrier-specific codes to our unified status.\n */\nexport const AymakanStatusCodes = {\n // Core lifecycle\n \"AY-0001\": \"created\", // Shipment created / AWB created at origin\n \"AY-0002\": \"picked_up\", // Collected from collection point\n \"AY-0003\": \"at_warehouse\", // Received at hub\n \"AY-0004\": \"out_for_delivery\", // Out for final destination\n \"AY-0005\": \"delivered\", // Delivered to customer\n \"AY-0006\": \"exception\", // Not delivered (failed attempt)\n\n // Warehouse / transfer\n \"AY-0026\": \"at_warehouse\", // Received at [City] Warehouse\n \"AY-0027\": \"in_transit\", // Transferred to destination city\n \"AY-0028\": \"in_transit\", // In transit between hubs\n\n // Pending / rescheduled (delivery was attempted but rescheduled)\n \"AY-0032\": \"pending\", // Pending — future delivery / rescheduled\n\n // Terminal states\n \"AY-0007\": \"returned\", // Returned to shipper\n \"AY-0008\": \"cancelled\", // Cancelled\n \"AY-0009\": \"returned\", // Return to origin\n} as const;\n",
|
|
6
|
-
"// file: src/carriers/aymakan/mappers.ts\n/**\n * Aymakan Data Mappers\n * Transform between unified ShipFlow types and Aymakan API formats.\n */\n\nimport { ValidationError, WebhookVerificationError } from \"../../core/errors\";\nimport type {\n Address,\n City,\n CreateShipmentInput,\n CustomerAddress,\n Pickup,\n PickupRequest,\n Shipment,\n ShipmentStatus,\n TrackingEvent,\n TrackingResult,\n WebhookConfig,\n WebhookEvent,\n} from \"../../core/types\";\nimport { AymakanService, AymakanStatusCodes } from \"./services\";\nimport type {\n AymakanAddressRequest,\n AymakanCity,\n AymakanCreateShipmentRequest,\n AymakanNationalAddressRequest,\n AymakanPickupRequest,\n AymakanPickupResponse,\n AymakanShipmentResponse,\n AymakanTrackingEvent,\n AymakanTrackShipmentData,\n AymakanWebhookPayload,\n} from \"./types\";\n\n// ============================================================================\n// STATUS MAPPING\n// ============================================================================\n\nexport function mapAymakanStatus(statusCode: string): ShipmentStatus | undefined {\n const mapped =\n AymakanStatusCodes[statusCode as keyof typeof AymakanStatusCodes];\n return mapped as ShipmentStatus | undefined;\n}\n\n// ============================================================================\n// REQUEST MAPPERS\n// ============================================================================\n\nfunction mapNationalAddress(\n addr?: Address[\"nationalAddress\"],\n): AymakanNationalAddressRequest | undefined {\n if (!addr) return undefined;\n return {\n short_code: addr.shortCode,\n building_number: addr.buildingNumber,\n street_name: addr.streetName,\n district: addr.district,\n additional_number: addr.additionalNumber,\n };\n}\n\n/** Strip non-digit characters from phone numbers (Aymakan requires digits only) */\nfunction sanitizePhone(phone: string): string {\n return phone.replace(/\\D/g, '');\n}\n\n/** Set of valid Aymakan service type codes */\nconst VALID_AYMAKAN_SERVICE_TYPES = new Set(\n Object.values(AymakanService) as string[],\n);\n\n/**\n * Resolve service type to a valid Aymakan code.\n * Returns undefined for unknown values so the API uses its default (ONP/ecommerce).\n */\nfunction resolveServiceType(serviceType?: string): string | undefined {\n if (!serviceType) return undefined;\n if (VALID_AYMAKAN_SERVICE_TYPES.has(serviceType)) return serviceType;\n return undefined;\n}\n\nexport function mapCreateShipmentRequest(\n input: CreateShipmentInput,\n): AymakanCreateShipmentRequest {\n const firstParcel = input.parcels[0];\n const totalPieces = input.parcels.reduce((sum, p) => sum + p.pieces, 0);\n const totalWeight = input.parcels.reduce(\n (sum, p) =>\n sum +\n (p.weight.unit === \"lb\" ? p.weight.value * 0.453592 : p.weight.value),\n 0,\n );\n const totalItems = input.parcels.reduce(\n (sum, p) => sum + (p.itemsCount ?? p.pieces),\n 0,\n );\n\n return {\n requested_by: input.options?.requestedBy ?? input.shipper.name,\n declared_value: input.declaredValue?.amount ?? input.cod?.amount ?? 0,\n declared_value_currency:\n input.declaredValue?.currency ?? input.cod?.currency ?? \"SAR\",\n reference: input.reference,\n customer_tracking: input.options?.customerTracking,\n service_type: resolveServiceType(input.serviceType),\n is_cod: input.cod?.enabled ? 1 : 0,\n cod_amount: input.cod?.enabled ? input.cod.amount : undefined,\n fulfilment_customer_name: input.options?.fulfilmentCustomerName,\n currency: input.cod?.currency ?? \"SAR\",\n\n // Delivery (consignee)\n delivery_name: input.consignee.name,\n delivery_email: input.consignee.email,\n delivery_city: input.consignee.city,\n delivery_address: input.consignee.line1,\n delivery_neighbourhood:\n input.consignee.neighbourhood ?? input.consignee.state,\n delivery_postcode: input.consignee.postalCode,\n delivery_country: input.consignee.countryCode,\n delivery_phone: sanitizePhone(input.consignee.phone),\n delivery_description: input.consignee.description,\n delivery_national_address: mapNationalAddress(\n input.consignee.nationalAddress,\n ),\n\n // Collection (shipper)\n collection_name: input.shipper.company ?? input.shipper.name,\n collection_email: input.shipper.email,\n collection_city: input.shipper.city,\n collection_address: input.shipper.line1,\n collection_neighbourhood:\n input.shipper.neighbourhood ?? input.shipper.state,\n collection_postcode: input.shipper.postalCode,\n collection_country: input.shipper.countryCode,\n collection_phone: sanitizePhone(input.shipper.phone),\n collection_description: input.shipper.description,\n collection_national_address: mapNationalAddress(\n input.shipper.nationalAddress,\n ),\n\n // Parcel details\n weight: totalWeight,\n length: firstParcel?.dimensions?.length,\n width: firstParcel?.dimensions?.width,\n height: firstParcel?.dimensions?.height,\n pieces: totalPieces,\n items_count: totalItems,\n is_insured: input.options?.isInsured ? 1 : 0,\n\n // International\n international_metadata: input.options?.internationalMetadata\n ? {\n document_id: input.options.internationalMetadata.documentId,\n tax_identification_number: input.options.internationalMetadata.taxId,\n invoice_number: input.options.internationalMetadata.invoiceNumber,\n invoice_date: input.options.internationalMetadata.invoiceDate,\n }\n : undefined,\n };\n}\n\nexport function mapPickupRequest(input: PickupRequest): AymakanPickupRequest {\n return {\n reference: input.trackingNumbers?.[0],\n pickup_date: input.date,\n time_slot: input.timeSlot,\n city: input.city,\n contact_name: input.contactName,\n contact_phone: input.contactPhone,\n address: input.address,\n shipments: input.shipmentCount,\n };\n}\n\nexport function mapCustomerAddressRequest(\n addr: CustomerAddress,\n): AymakanAddressRequest {\n return {\n title: addr.title,\n name: addr.name,\n email: addr.email,\n city: addr.city,\n address: addr.address,\n neighbourhood: addr.neighbourhood,\n postcode: addr.postalCode ?? \"\",\n phone: addr.phone,\n description: addr.description ?? \"\",\n };\n}\n\n// ============================================================================\n// RESPONSE MAPPERS\n// ============================================================================\n\nexport function mapShipmentResponse(data: AymakanShipmentResponse): Shipment {\n // Parse cod_amount: API returns string like \"150.00\", we need number\n const codAmount =\n data.cod_amount != null\n ? typeof data.cod_amount === \"string\"\n ? parseFloat(data.cod_amount)\n : data.cod_amount\n : undefined;\n\n return {\n carrier: \"aymakan\",\n trackingNumber: data.tracking_number,\n customerTracking: data.customer_tracking ?? undefined,\n reference: data.reference ?? undefined,\n status: mapAymakanStatus(data.status) ?? \"created\",\n statusLabel: data.status_label,\n labelUrl: data.label || undefined,\n pdfLabelUrl: data.pdf_label || undefined,\n codAmount: codAmount && !Number.isNaN(codAmount) ? codAmount : undefined,\n declaredValue: data.declared_value,\n currency: data.currency,\n createdAt: new Date(data.created_at),\n raw: data,\n };\n}\n\n/**\n * Parse an Aymakan timestamp. Top-level fields are ISO 8601 (with `T`/`Z`), but\n * `tracking_info[].created_at` values are space-separated \"YYYY-MM-DD HH:MM:SS\"\n * strings in Saudi local time (UTC+3) with no timezone marker. `new Date()`\n * would parse those in the host's local timezone, so normalize to +03:00.\n */\nfunction parseAymakanDate(value: string): Date {\n if (/[TZ]|[+-]\\d{2}:?\\d{2}$/.test(value)) {\n return new Date(value);\n }\n return new Date(`${value.replace(\" \", \"T\")}+03:00`);\n}\n\nexport function mapTrackingEvent(event: AymakanTrackingEvent): TrackingEvent {\n return {\n timestamp: parseAymakanDate(event.created_at),\n statusCode: event.status_code,\n status: mapAymakanStatus(event.status_code) ?? \"unknown\",\n description: event.description,\n descriptionArabic: event.description_ar ?? undefined,\n };\n}\n\nexport function mapTrackingResult(\n data: AymakanTrackShipmentData,\n): TrackingResult {\n const events = data.tracking_info.map(mapTrackingEvent);\n\n // The top-level `status` field is a human-readable label on the /track\n // endpoint (e.g. \"Received at Warehouse\") and an AY code on /by_reference.\n // Derive the unified status from the most recent tracking event (whose\n // status_code is always an AY code), falling back to mapping the top-level\n // status, then \"unknown\".\n const latestEvent = events.length\n ? [...events].sort(\n (a, b) => b.timestamp.getTime() - a.timestamp.getTime(),\n )[0]\n : undefined;\n let status: ShipmentStatus;\n if (latestEvent && latestEvent.status !== \"unknown\") {\n status = latestEvent.status;\n } else {\n status = mapAymakanStatus(data.status) ?? latestEvent?.status ?? \"unknown\";\n }\n\n return {\n trackingNumber: data.tracking_number,\n carrier: \"aymakan\",\n reference: data.reference ?? undefined,\n status,\n statusLabel: data.status_label,\n events,\n deliveryDate: data.delivery_date ? new Date(data.delivery_date) : undefined,\n pickupDate: data.pickup_date ? new Date(data.pickup_date) : undefined,\n receivedAt: data.received_at ? new Date(data.received_at) : undefined,\n codAmount: data.cod_amount\n ? Number.isNaN(parseFloat(data.cod_amount))\n ? undefined\n : parseFloat(data.cod_amount)\n : undefined,\n weight: Number.isNaN(parseFloat(data.weight))\n ? undefined\n : parseFloat(data.weight),\n pieces: data.pieces,\n raw: data,\n };\n}\n\nexport function mapCity(city: AymakanCity): City {\n return {\n nameEn: city.city_en,\n nameAr: city.city_ar,\n };\n}\n\nexport function mapPickupResponse(data: AymakanPickupResponse[\"data\"]): Pickup {\n return {\n id: data.id,\n carrier: \"aymakan\",\n status: (\n [\"pending\", \"processing\", \"completed\", \"cancelled\"] as const\n ).includes(data.status as Pickup[\"status\"])\n ? (data.status as Pickup[\"status\"])\n : \"pending\",\n date: data.pickup_date,\n timeSlot: data.time_slot,\n city: data.city,\n contactName: data.contact_name,\n contactPhone: data.contact_phone,\n address: data.address,\n shipmentCount: data.shipments,\n warehouseId: data.warehouse_id,\n warehouseName: data.warehouse_name,\n createdAt: new Date(data.created_at),\n raw: data,\n };\n}\n\n// ============================================================================\n// WEBHOOK MAPPER\n// ============================================================================\n\n/**\n * Timing-safe string comparison to prevent timing attacks on auth tokens.\n */\nfunction timingSafeEqual(a: string, b: string): boolean {\n const encoder = new TextEncoder();\n const bufA = encoder.encode(a);\n const bufB = encoder.encode(b);\n if (bufA.byteLength !== bufB.byteLength) return false;\n // Use crypto.subtle-compatible constant-time compare\n let mismatch = 0;\n for (let i = 0; i < bufA.byteLength; i++) {\n mismatch |= bufA[i]! ^ bufB[i]!;\n }\n return mismatch === 0;\n}\n\nexport function parseAymakanWebhook(\n payload: unknown,\n options?: {\n headers?: Record<string, string>;\n queryParams?: Record<string, string>;\n config?: WebhookConfig;\n },\n): WebhookEvent {\n const { headers = {}, queryParams = {}, config } = options ?? {};\n\n // Validate required fields\n if (\n !payload ||\n typeof payload !== \"object\" ||\n !(\"tracking_number\" in payload) ||\n !(\"status\" in payload)\n ) {\n throw new ValidationError(\n \"Invalid webhook payload: missing required fields\",\n {\n raw: payload,\n },\n );\n }\n\n const data = payload as AymakanWebhookPayload;\n\n // Verify auth via header (case-insensitive lookup, timing-safe comparison)\n if (config?.authHeader && config?.authValue) {\n const lowerKey = config.authHeader.toLowerCase();\n const headerValue = Object.entries(headers).find(\n ([k]) => k.toLowerCase() === lowerKey,\n )?.[1];\n if (!headerValue || !timingSafeEqual(headerValue, config.authValue)) {\n throw new WebhookVerificationError(\"Invalid webhook auth header\", {\n carrier: \"aymakan\",\n });\n }\n }\n\n // Verify auth via query param (timing-safe comparison)\n if (config?.authQueryParam && config?.authQueryValue) {\n const paramValue = queryParams[config.authQueryParam];\n if (!paramValue || !timingSafeEqual(paramValue, config.authQueryValue)) {\n throw new WebhookVerificationError(\"Invalid webhook auth query param\", {\n carrier: \"aymakan\",\n });\n }\n }\n\n const eventType = data.event ?? \"status_update\";\n\n // Validate timestamp\n const timestamp = new Date(data.date_time);\n if (Number.isNaN(timestamp.getTime())) {\n throw new ValidationError(\n \"Invalid webhook payload: invalid date_time value\",\n { raw: data.date_time },\n );\n }\n\n return {\n carrier: \"aymakan\",\n eventType,\n trackingNumber: data.tracking_number,\n reference: data.reference ?? undefined,\n // `status` is a core field present on every webhook regardless of event\n // type, so map it even for weight_update events. Consumers can still branch\n // on `eventType` to detect weight updates.\n status: mapAymakanStatus(data.status) ?? \"unknown\",\n statusCode: data.status,\n statusLabel: data.status_label,\n reasonCode: data.reason_code ?? undefined,\n reasonLabel: data.reason_en ?? undefined,\n timestamp,\n raw: data,\n };\n}\n",
|
|
7
|
-
"// file: src/carriers/aymakan/adapter.ts\n/**\n * Aymakan Carrier Adapter\n * Full implementation of the CarrierAdapter interface for Aymakan API.\n */\n\nimport {\n APIError,\n UnsupportedOperationError,\n ValidationError,\n} from \"../../core/errors\";\nimport { HttpClient } from \"../../core/http\";\nimport {\n validateCreateShipmentInput,\n validatePickupRequest,\n} from \"../../core/schemas\";\nimport type {\n Address,\n CarrierConfig,\n City,\n CreateShipmentInput,\n CustomerAddress,\n Pickup,\n PickupRequest,\n Shipment,\n TimeSlot,\n TrackingResult,\n WebhookConfig,\n WebhookEvent,\n} from \"../../core/types\";\nimport { BaseCarrierAdapter } from \"../base\";\nimport {\n mapCity,\n mapCreateShipmentRequest,\n mapCustomerAddressRequest,\n mapPickupRequest,\n mapPickupResponse,\n mapShipmentResponse,\n mapTrackingResult,\n parseAymakanWebhook,\n} from \"./mappers\";\nimport type {\n AymakanAddressResponse,\n AymakanCancelResponse,\n AymakanCitiesResponse,\n AymakanCreateShipmentResponse,\n AymakanPickupResponse,\n AymakanTrackResponse,\n} from \"./types\";\n\nconst AYMAKAN_SANDBOX_URL = \"https://dev-api.aymakan.com.sa/v2\";\nconst AYMAKAN_PRODUCTION_URL = \"https://api.aymakan.net/v2\";\n\nexport interface AymakanConfig extends CarrierConfig {\n credentials: {\n apiKey: string;\n };\n}\n\nexport class AymakanAdapter extends BaseCarrierAdapter {\n readonly name = \"aymakan\";\n readonly supportedCountries = [\"SA\", \"AE\", \"BH\", \"KW\", \"OM\", \"QA\"];\n\n private http: HttpClient;\n\n /** Cached Aymakan cities list for city name resolution */\n private citiesCache: City[] | null = null;\n private citiesCacheTime = 0;\n /** Cache TTL: 1 hour */\n private static readonly CITIES_CACHE_TTL = 60 * 60 * 1000;\n\n constructor(config: AymakanConfig) {\n super(config);\n this.http = new HttpClient({\n baseUrl: this.getBaseUrl(),\n carrier: \"aymakan\",\n headers: {\n Authorization: config.credentials.apiKey,\n },\n });\n }\n\n protected getBaseUrl(): string {\n return this.config.mode === \"production\"\n ? AYMAKAN_PRODUCTION_URL\n : AYMAKAN_SANDBOX_URL;\n }\n\n // =========================================================================\n // CITY RESOLUTION\n // =========================================================================\n\n /**\n * Load cities list from Aymakan API and cache it.\n * Silently falls back to empty list on failure so shipments can still be attempted.\n */\n private async ensureCitiesLoaded(): Promise<void> {\n if (\n this.citiesCache &&\n Date.now() - this.citiesCacheTime < AymakanAdapter.CITIES_CACHE_TTL\n ) {\n return;\n }\n try {\n this.citiesCache = await this.getCities();\n this.citiesCacheTime = Date.now();\n } catch {\n if (!this.citiesCache) this.citiesCache = [];\n }\n }\n\n /**\n * Normalize Arabic text for comparison:\n * - Strip diacritics (tashkeel)\n * - Normalize alef variants (أ إ آ → ا)\n * - Normalize taa marbuta (ة → ه)\n * - Trim whitespace\n */\n private static normalizeArabic(text: string): string {\n return text\n .trim()\n .replace(/[\\u064B-\\u065F\\u0670]/g, \"\") // strip tashkeel\n .replace(/[أإآ]/g, \"ا\") // normalize alef\n .replace(/ة/g, \"ه\"); // normalize taa marbuta\n }\n\n /**\n * Resolve a user-input city name to the valid Aymakan English city name.\n *\n * Matching strategy (first match wins):\n * 1. Exact match on English name (case-insensitive)\n * 2. Exact match on Arabic name\n * 3. Normalized Arabic match (handles tashkeel, alef variants, taa marbuta)\n * 4. Match after stripping \"ال\" prefix from Arabic input\n * 5. Substring/contains match on English name (case-insensitive)\n *\n * Falls back to the original input if no match is found.\n */\n private resolveCity(inputCity: string): string {\n if (!this.citiesCache || this.citiesCache.length === 0) return inputCity;\n\n const trimmed = inputCity.trim();\n if (!trimmed) return inputCity;\n const lower = trimmed.toLowerCase();\n\n // 1. Exact English match (case-insensitive)\n const exactEn = this.citiesCache.find(\n (c) => c.nameEn.toLowerCase() === lower,\n );\n if (exactEn) return exactEn.nameEn;\n\n // 2. Exact Arabic match\n const exactAr = this.citiesCache.find((c) => c.nameAr === trimmed);\n if (exactAr) return exactAr.nameEn;\n\n // 3. Normalized Arabic match\n const normalizedInput = AymakanAdapter.normalizeArabic(trimmed);\n const normalizedAr = this.citiesCache.find(\n (c) =>\n c.nameAr &&\n AymakanAdapter.normalizeArabic(c.nameAr) === normalizedInput,\n );\n if (normalizedAr) return normalizedAr.nameEn;\n\n // 4. Arabic without \"ال\" prefix\n const withoutAl = normalizedInput.startsWith(\"ال\")\n ? normalizedInput.slice(2)\n : `ال${normalizedInput}`;\n const alMatch = this.citiesCache.find(\n (c) => c.nameAr && AymakanAdapter.normalizeArabic(c.nameAr) === withoutAl,\n );\n if (alMatch) return alMatch.nameEn;\n\n // 5. Contains match on English name (for partial matches like \"Riyadh\" in \"Riyadh City\")\n const containsEn = this.citiesCache.find(\n (c) =>\n c.nameEn.toLowerCase().includes(lower) ||\n lower.includes(c.nameEn.toLowerCase()),\n );\n if (containsEn) return containsEn.nameEn;\n\n // No match — return as-is and let the API return a validation error\n return trimmed;\n }\n\n /**\n * Resolve city names in a CreateShipmentInput to valid Aymakan city names.\n */\n private resolveCitiesInInput(\n input: CreateShipmentInput,\n ): CreateShipmentInput {\n return {\n ...input,\n shipper: {\n ...input.shipper,\n city: this.resolveCity(input.shipper.city),\n },\n consignee: {\n ...input.consignee,\n city: this.resolveCity(input.consignee.city),\n },\n };\n }\n\n // =========================================================================\n // SHIPPING\n // =========================================================================\n\n protected async executeCreateShipment(\n input: CreateShipmentInput,\n ): Promise<Shipment> {\n await this.ensureCitiesLoaded();\n const resolved = this.resolveCitiesInInput(input);\n const request = mapCreateShipmentRequest(resolved);\n const response = await this.http.post<AymakanCreateShipmentResponse>(\n \"/shipping/create\",\n request,\n );\n\n if (!response.success) {\n throw new APIError(\"Failed to create shipment\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return mapShipmentResponse(response.shipping);\n }\n\n async createBulkShipments(\n inputs: CreateShipmentInput[],\n ): Promise<Shipment[]> {\n // Aymakan rejects batches larger than 30 (\"Only 30 shipments can be\n // created at a time.\"), so fail fast with a clear error before the request.\n if (inputs.length > 30) {\n throw new ValidationError(\n \"Aymakan bulk create accepts at most 30 shipments per request\",\n { raw: { count: inputs.length } },\n );\n }\n inputs.forEach(validateCreateShipmentInput);\n await this.ensureCitiesLoaded();\n const requests = inputs\n .map((i) => this.resolveCitiesInInput(i))\n .map(mapCreateShipmentRequest);\n const response = await this.http.post<{\n success: boolean;\n message?: string;\n bulk_awb?: string;\n total_shipments?: number;\n shipments: AymakanCreateShipmentResponse[\"shipping\"][];\n }>(\"/shipping/create/bulk_shipping\", { shipments: requests });\n\n if (!response.success) {\n throw new APIError(\"Failed to create bulk shipments\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return response.shipments.map(mapShipmentResponse);\n }\n\n async cancelShipment(trackingNumber: string): Promise<boolean> {\n // Try passing as body\n const response = await this.http.post<AymakanCancelResponse>(\n \"/shipping/cancel\",\n {\n tracking: trackingNumber,\n },\n );\n return response.success === true;\n }\n\n async cancelByReference(reference: string): Promise<boolean> {\n const response = await this.http.post<AymakanCancelResponse>(\n \"/shipping/cancel_by_reference\",\n { reference },\n );\n return response.success === true;\n }\n\n async updateDeliveryAddress(\n trackingNumber: string,\n address: Address,\n ): Promise<boolean> {\n await this.ensureCitiesLoaded();\n const resolvedCity = this.resolveCity(address.city);\n const response = await this.http.post<{ success: boolean }>(\n `/shipping/update_delivery_address/${encodeURIComponent(trackingNumber)}`,\n {\n delivery_name: address.name,\n delivery_email: address.email,\n delivery_city: resolvedCity,\n delivery_address: address.line1,\n delivery_neighbourhood: address.neighbourhood,\n delivery_postcode: address.postalCode,\n delivery_country: address.countryCode,\n delivery_phone: address.phone,\n },\n );\n return response.success;\n }\n\n // =========================================================================\n // TRACKING\n // =========================================================================\n\n async track(trackingNumber: string): Promise<TrackingResult> {\n const results = await this.trackMultiple([trackingNumber]);\n const result = results[0];\n if (!result) {\n throw new APIError(\"Shipment not found\", { carrier: \"aymakan\" });\n }\n return result;\n }\n\n async trackMultiple(trackingNumbers: string[]): Promise<TrackingResult[]> {\n const ids = trackingNumbers.map(encodeURIComponent).join(\",\");\n const response = await this.http.get<AymakanTrackResponse>(\n `/shipping/track/${ids}`,\n );\n\n if (!response.success) {\n throw new APIError(\"Failed to track shipments\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return response.data.shipments.map(mapTrackingResult);\n }\n\n async trackByReference(reference: string): Promise<TrackingResult> {\n const response = await this.http.get<AymakanTrackResponse>(\n `/shipping/by_reference/${encodeURIComponent(reference)}`,\n );\n\n if (!response.success || !response.data.shipments[0]) {\n throw new APIError(\"Shipment not found\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return mapTrackingResult(response.data.shipments[0]);\n }\n\n // =========================================================================\n // LABELS\n // =========================================================================\n\n async getLabel(\n trackingNumber: string,\n _format?: \"PDF\" | \"ZPL\" | \"PNG\",\n ): Promise<string> {\n // Note: this endpoint always returns a single PDF download URL, so the\n // requested `format` cannot be honored.\n const response = await this.http.get<{\n success: boolean;\n error?: boolean;\n message?: string;\n response?: string;\n data: { awb_url: string };\n }>(`/shipping/awb/tracking/${encodeURIComponent(trackingNumber)}`);\n\n if (!response.success || !response.data?.awb_url) {\n const msg = response.message ?? response.response ?? \"Failed to get label\";\n throw new APIError(msg, { carrier: \"aymakan\", raw: response });\n }\n\n return response.data.awb_url;\n }\n\n async getBulkLabels(trackingNumbers: string[]): Promise<string> {\n // Documented as a GET with comma-separated tracking numbers in the path.\n const ids = trackingNumbers.map(encodeURIComponent).join(\",\");\n const response = await this.http.get<{\n success: boolean;\n error?: boolean;\n message?: string;\n response?: string;\n data: { bulk_awb_url: string };\n }>(`/shipping/bulk_awb/trackings/${ids}`);\n\n if (!response.success || !response.data?.bulk_awb_url) {\n const msg =\n response.message ?? response.response ?? \"Failed to get bulk labels\";\n throw new APIError(msg, { carrier: \"aymakan\", raw: response });\n }\n\n return response.data.bulk_awb_url;\n }\n\n // =========================================================================\n // PICKUPS\n // =========================================================================\n\n async getPickupCities(): Promise<City[]> {\n const response = await this.http.get<AymakanCitiesResponse>(\n \"/pickup_request/cities\",\n );\n\n if (!response.success) {\n throw new APIError(\"Failed to get pickup cities\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return response.data.cities.map(mapCity);\n }\n\n async getTimeSlots(_city: string, date: string): Promise<TimeSlot[]> {\n const response = await this.http.get<{\n success: boolean;\n error?: boolean;\n message?: string;\n data: {\n date: string;\n slots: Record<string, string>;\n };\n }>(`/time_slots/${date}`);\n\n // Aymakan returns error responses for invalid dates (past, Fridays, etc.)\n if (!response.success || response.error || !response.data?.slots) {\n const msg = response.message ?? \"No slots available\";\n throw new APIError(msg, { carrier: \"aymakan\", raw: response });\n }\n\n // data.slots is an object like { \"afternoon\": \"After Noon (02 PM - 06 PM)\" }\n return Object.entries(response.data.slots).map(([id, label]) => ({\n id,\n label,\n }));\n }\n\n async createPickup(input: PickupRequest): Promise<Pickup> {\n validatePickupRequest(input);\n await this.ensureCitiesLoaded();\n const resolvedInput = { ...input, city: this.resolveCity(input.city) };\n const request = mapPickupRequest(resolvedInput);\n const response = await this.http.post<AymakanPickupResponse>(\n \"/pickup_request/create\",\n request,\n );\n\n if (!response.success) {\n throw new APIError(\"Failed to create pickup\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return mapPickupResponse(response.data);\n }\n\n async cancelPickup(pickupId: string | number): Promise<boolean> {\n const response = await this.http.post<{ success: boolean }>(\n \"/pickup_request/cancel\",\n { pickup_request: Number(pickupId) },\n );\n return response.success === true;\n }\n\n async getPickupRequests(): Promise<Pickup[]> {\n // The list endpoint returns a Laravel paginated structure:\n // { success, data: { pickupRequests: { data: [...] } } }\n const response = await this.http.get<{\n success: boolean;\n data: { pickupRequests: { data: AymakanPickupResponse[\"data\"][] } };\n }>(\"/pickup_request/list\");\n\n if (!response.success) {\n throw new APIError(\"Failed to get pickup requests\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return response.data.pickupRequests.data.map(mapPickupResponse);\n }\n\n // =========================================================================\n // CITIES & LOCATIONS\n // =========================================================================\n\n async getCities(): Promise<City[]> {\n const response = await this.http.get<AymakanCitiesResponse>(\"/cities\");\n\n if (!response.success) {\n throw new APIError(\"Failed to get cities\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return response.data.cities.map(mapCity);\n }\n\n async getDropoffLocations(): Promise<\n {\n id: string;\n name: string;\n address?: string;\n city?: string;\n latitude?: number;\n longitude?: number;\n }[]\n > {\n // The API returns `data` as a flat array of warehouse objects. There is no\n // `id` field — the warehouse code lives in `name` (e.g. \"RUH-WH\").\n const response = await this.http.get<{\n success: boolean;\n data: Array<{\n name: string;\n city?: string;\n address?: string;\n manager?: string;\n mobile_phone?: string;\n email?: string;\n location_lat?: number | null;\n location_lng?: number | null;\n }>;\n }>(\"/dropoff_locations\");\n\n if (!response.success) {\n throw new APIError(\"Failed to get dropoff locations\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return response.data.map((loc) => ({\n id: loc.name,\n name: loc.name,\n address: loc.address,\n city: loc.city,\n latitude: loc.location_lat ?? undefined,\n longitude: loc.location_lng ?? undefined,\n }));\n }\n\n // =========================================================================\n // CUSTOMER ADDRESSES\n // =========================================================================\n\n async createCustomerAddress(\n address: CustomerAddress,\n ): Promise<CustomerAddress> {\n const request = mapCustomerAddressRequest(address);\n const response = await this.http.post<AymakanAddressResponse>(\n \"/address/create\",\n request,\n );\n\n if (!response.success) {\n throw new APIError(\"Failed to create address\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n const addr = response.data.address;\n return {\n id: addr.id,\n title: addr.title,\n name: addr.name,\n email: addr.email,\n phone: addr.phone,\n city: addr.city,\n address: addr.address,\n neighbourhood: addr.neighbourhood,\n postalCode: addr.postcode,\n countryCode: addr.country,\n description: addr.description,\n };\n }\n\n async getCustomerAddresses(): Promise<CustomerAddress[]> {\n const response = await this.http.get<{\n success: boolean;\n data: { address: AymakanAddressResponse[\"data\"][\"address\"][] };\n }>(\"/address/list\");\n\n if (!response.success) {\n throw new APIError(\"Failed to get customer addresses\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return response.data.address.map((addr) => ({\n id: addr.id,\n title: addr.title,\n name: addr.name,\n email: addr.email,\n phone: addr.phone,\n city: addr.city,\n address: addr.address,\n neighbourhood: addr.neighbourhood,\n postalCode: addr.postcode,\n countryCode: addr.country,\n description: addr.description,\n }));\n }\n\n async updateCustomerAddress(\n id: number,\n address: Partial<CustomerAddress>,\n ): Promise<CustomerAddress> {\n const response = await this.http.put<AymakanAddressResponse>(\n \"/address/update\",\n {\n id,\n title: address.title,\n name: address.name,\n email: address.email,\n city: address.city,\n address: address.address,\n neighbourhood: address.neighbourhood,\n postcode: address.postalCode,\n phone: address.phone,\n description: address.description,\n },\n );\n\n if (!response.success) {\n throw new APIError(\"Failed to update address\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n const addr = response.data.address;\n return {\n id: addr.id,\n title: addr.title,\n name: addr.name,\n email: addr.email,\n phone: addr.phone,\n city: addr.city,\n address: addr.address,\n neighbourhood: addr.neighbourhood,\n postalCode: addr.postcode,\n countryCode: addr.country,\n description: addr.description,\n };\n }\n\n async deleteCustomerAddress(id: number): Promise<boolean> {\n // The id must be sent in the JSON request body, not the URL path.\n const response = await this.http.delete<{ success: boolean }>(\n \"/address/delete\",\n { body: { id } },\n );\n return response.success;\n }\n\n // =========================================================================\n // WEBHOOKS\n // =========================================================================\n\n parseWebhook(\n payload: unknown,\n options?: {\n headers?: Record<string, string>;\n queryParams?: Record<string, string>;\n config?: WebhookConfig;\n },\n ): WebhookEvent {\n return parseAymakanWebhook(payload, options);\n }\n\n // =========================================================================\n // NOT SUPPORTED\n // =========================================================================\n\n getRates(): Promise<never> {\n throw new UnsupportedOperationError(\"aymakan\", \"getRates\");\n }\n}\n"
|
|
6
|
+
"// file: src/carriers/aymakan/mappers.ts\n/**\n * Aymakan Data Mappers\n * Transform between unified ShipFlow types and Aymakan API formats.\n */\n\nimport { ValidationError, WebhookVerificationError } from \"../../core/errors\";\nimport type {\n Address,\n City,\n CreateShipmentInput,\n CustomerAddress,\n Pickup,\n PickupRequest,\n Shipment,\n ShipmentStatus,\n TrackingEvent,\n TrackingResult,\n WebhookConfig,\n WebhookEvent,\n} from \"../../core/types\";\nimport { AymakanService, AymakanStatusCodes } from \"./services\";\nimport type {\n AymakanAddressRequest,\n AymakanCity,\n AymakanCreateShipmentRequest,\n AymakanNationalAddressRequest,\n AymakanPickupRequest,\n AymakanPickupResponse,\n AymakanShipmentResponse,\n AymakanTrackingEvent,\n AymakanTrackShipmentData,\n AymakanWebhookPayload,\n} from \"./types\";\n\n// ============================================================================\n// STATUS MAPPING\n// ============================================================================\n\nexport function mapAymakanStatus(statusCode: string): ShipmentStatus | undefined {\n const mapped =\n AymakanStatusCodes[statusCode as keyof typeof AymakanStatusCodes];\n return mapped as ShipmentStatus | undefined;\n}\n\n/**\n * The /track endpoint's top-level `status` is a human-readable English label\n * (e.g. \"Received at Warehouse\") rather than an AY code, so `mapAymakanStatus`\n * can never resolve it. This map recovers a real unified status from that label\n * when there are no tracking events to derive it from. Keys are compared\n * case-insensitively.\n */\nconst AymakanStatusLabels: Record<string, ShipmentStatus> = {\n \"awb created at origin\": \"created\",\n \"shipment created\": \"created\",\n \"collected from collection point\": \"picked_up\",\n collected: \"picked_up\",\n \"picked up\": \"picked_up\",\n \"received at hub\": \"at_warehouse\",\n \"received at warehouse\": \"at_warehouse\",\n \"out for delivery\": \"out_for_delivery\",\n \"out for final destination\": \"out_for_delivery\",\n delivered: \"delivered\",\n \"shipment is delivered\": \"delivered\",\n \"transferred to destination city\": \"in_transit\",\n \"in transit\": \"in_transit\",\n \"not delivered\": \"exception\",\n \"failed attempt\": \"exception\",\n pending: \"pending\",\n \"future delivery\": \"pending\",\n returned: \"returned\",\n \"returned to shipper\": \"returned\",\n \"return to origin\": \"returned\",\n cancelled: \"cancelled\",\n canceled: \"cancelled\",\n};\n\n/**\n * Map a human-readable Aymakan status label to a unified status.\n * Returns undefined for unrecognized labels.\n */\nexport function mapAymakanStatusLabel(\n label: string,\n): ShipmentStatus | undefined {\n return AymakanStatusLabels[label.trim().toLowerCase()];\n}\n\n// ============================================================================\n// REQUEST MAPPERS\n// ============================================================================\n\nfunction mapNationalAddress(\n addr?: Address[\"nationalAddress\"],\n): AymakanNationalAddressRequest | undefined {\n if (!addr) return undefined;\n return {\n short_code: addr.shortCode,\n building_number: addr.buildingNumber,\n street_name: addr.streetName,\n district: addr.district,\n additional_number: addr.additionalNumber,\n };\n}\n\n/** Strip non-digit characters from phone numbers (Aymakan requires digits only) */\nfunction sanitizePhone(phone: string): string {\n return phone.replace(/\\D/g, '');\n}\n\n/** Set of valid Aymakan service type codes */\nconst VALID_AYMAKAN_SERVICE_TYPES = new Set(\n Object.values(AymakanService) as string[],\n);\n\n/**\n * Resolve service type to a valid Aymakan code.\n * Returns undefined for unknown values so the API uses its default (ONP/ecommerce).\n */\nfunction resolveServiceType(serviceType?: string): string | undefined {\n if (!serviceType) return undefined;\n if (VALID_AYMAKAN_SERVICE_TYPES.has(serviceType)) return serviceType;\n return undefined;\n}\n\nexport function mapCreateShipmentRequest(\n input: CreateShipmentInput,\n): AymakanCreateShipmentRequest {\n const firstParcel = input.parcels[0];\n const totalPieces = input.parcels.reduce((sum, p) => sum + p.pieces, 0);\n const totalWeight = input.parcels.reduce(\n (sum, p) =>\n sum +\n (p.weight.unit === \"lb\" ? p.weight.value * 0.453592 : p.weight.value),\n 0,\n );\n const totalItems = input.parcels.reduce(\n (sum, p) => sum + (p.itemsCount ?? p.pieces),\n 0,\n );\n\n // COD (collected from the buyer on delivery) and declared/customs value are\n // distinct concepts and must never be conflated. Declared value comes solely\n // from input.declaredValue; COD comes solely from input.cod.\n const codEnabled = input.cod?.enabled === true;\n const declaredValueCurrency = input.declaredValue?.currency ?? \"SAR\";\n\n return {\n requested_by: input.options?.requestedBy ?? input.shipper.name,\n declared_value: input.declaredValue?.amount ?? 0,\n declared_value_currency: declaredValueCurrency,\n reference: input.reference,\n customer_tracking: input.options?.customerTracking,\n service_type: resolveServiceType(input.serviceType),\n is_cod: codEnabled ? 1 : 0,\n cod_amount: codEnabled ? input.cod?.amount : undefined,\n fulfilment_customer_name: input.options?.fulfilmentCustomerName,\n // Top-level currency prefers the COD currency when COD is enabled (it's the\n // amount actually collected), otherwise the declared-value currency.\n currency: codEnabled\n ? (input.cod?.currency ?? \"SAR\")\n : declaredValueCurrency,\n\n // Delivery (consignee)\n delivery_name: input.consignee.name,\n delivery_email: input.consignee.email,\n delivery_city: input.consignee.city,\n delivery_address: input.consignee.line1,\n delivery_neighbourhood:\n input.consignee.neighbourhood ?? input.consignee.state,\n delivery_postcode: input.consignee.postalCode,\n delivery_country: input.consignee.countryCode,\n delivery_phone: sanitizePhone(input.consignee.phone),\n delivery_description: input.consignee.description,\n delivery_national_address: mapNationalAddress(\n input.consignee.nationalAddress,\n ),\n\n // Collection (shipper)\n collection_name: input.shipper.company ?? input.shipper.name,\n collection_email: input.shipper.email,\n collection_city: input.shipper.city,\n collection_address: input.shipper.line1,\n collection_neighbourhood:\n input.shipper.neighbourhood ?? input.shipper.state,\n collection_postcode: input.shipper.postalCode,\n collection_country: input.shipper.countryCode,\n collection_phone: sanitizePhone(input.shipper.phone),\n collection_description: input.shipper.description,\n collection_national_address: mapNationalAddress(\n input.shipper.nationalAddress,\n ),\n\n // Parcel details\n weight: totalWeight,\n length: firstParcel?.dimensions?.length,\n width: firstParcel?.dimensions?.width,\n height: firstParcel?.dimensions?.height,\n pieces: totalPieces,\n items_count: totalItems,\n is_insured: input.options?.isInsured ? 1 : 0,\n\n // International\n international_metadata: input.options?.internationalMetadata\n ? {\n document_id: input.options.internationalMetadata.documentId,\n tax_identification_number: input.options.internationalMetadata.taxId,\n invoice_number: input.options.internationalMetadata.invoiceNumber,\n invoice_date: input.options.internationalMetadata.invoiceDate,\n }\n : undefined,\n };\n}\n\nexport function mapPickupRequest(input: PickupRequest): AymakanPickupRequest {\n return {\n reference: input.trackingNumbers?.[0],\n pickup_date: input.date,\n time_slot: input.timeSlot,\n city: input.city,\n contact_name: input.contactName,\n contact_phone: input.contactPhone,\n address: input.address,\n shipments: input.shipmentCount,\n };\n}\n\nexport function mapCustomerAddressRequest(\n addr: CustomerAddress,\n): AymakanAddressRequest {\n return {\n title: addr.title,\n name: addr.name,\n email: addr.email,\n city: addr.city,\n address: addr.address,\n neighbourhood: addr.neighbourhood,\n postcode: addr.postalCode ?? \"\",\n phone: addr.phone,\n description: addr.description ?? \"\",\n };\n}\n\n// ============================================================================\n// RESPONSE MAPPERS\n// ============================================================================\n\nexport function mapShipmentResponse(data: AymakanShipmentResponse): Shipment {\n // Parse cod_amount: API returns string like \"150.00\", we need number\n const codAmount =\n data.cod_amount != null\n ? typeof data.cod_amount === \"string\"\n ? parseFloat(data.cod_amount)\n : data.cod_amount\n : undefined;\n\n return {\n carrier: \"aymakan\",\n trackingNumber: data.tracking_number,\n customerTracking: data.customer_tracking ?? undefined,\n reference: data.reference ?? undefined,\n status: mapAymakanStatus(data.status) ?? \"created\",\n statusLabel: data.status_label,\n labelUrl: data.label || undefined,\n pdfLabelUrl: data.pdf_label || undefined,\n // Emit a defined COD amount whenever we parsed a real number. A COD of 0\n // means \"no cash collected\", so surface it as undefined, but never let NaN\n // logic on a falsy 0 drop an otherwise valid value.\n codAmount:\n codAmount != null && !Number.isNaN(codAmount) && codAmount !== 0\n ? codAmount\n : undefined,\n declaredValue: data.declared_value,\n currency: data.currency,\n createdAt: new Date(data.created_at),\n raw: data,\n };\n}\n\n/**\n * Parse an Aymakan timestamp. Top-level fields are ISO 8601 (with `T`/`Z`), but\n * `tracking_info[].created_at` values are space-separated \"YYYY-MM-DD HH:MM:SS\"\n * strings in Saudi local time (UTC+3) with no timezone marker. `new Date()`\n * would parse those in the host's local timezone, so normalize to +03:00.\n */\nfunction parseAymakanDate(value: string): Date {\n if (/[TZ]|[+-]\\d{2}:?\\d{2}$/.test(value)) {\n return new Date(value);\n }\n return new Date(`${value.replace(\" \", \"T\")}+03:00`);\n}\n\nexport function mapTrackingEvent(event: AymakanTrackingEvent): TrackingEvent {\n return {\n timestamp: parseAymakanDate(event.created_at),\n statusCode: event.status_code,\n status: mapAymakanStatus(event.status_code) ?? \"unknown\",\n description: event.description,\n descriptionArabic: event.description_ar ?? undefined,\n };\n}\n\nexport function mapTrackingResult(\n data: AymakanTrackShipmentData,\n): TrackingResult {\n const events = data.tracking_info.map(mapTrackingEvent);\n\n // The top-level `status` field is a human-readable label on the /track\n // endpoint (e.g. \"Received at Warehouse\") and an AY code on /by_reference.\n // Derive the unified status from the most recent tracking event (whose\n // status_code is always an AY code), falling back to mapping the top-level\n // status. The fallback tries the AY-code map first (handles /by_reference)\n // and then the human-label map (handles /track), so a real state is recovered\n // even when there are no events, then \"unknown\".\n const latestEvent = events.length\n ? [...events].sort(\n (a, b) => b.timestamp.getTime() - a.timestamp.getTime(),\n )[0]\n : undefined;\n let status: ShipmentStatus;\n if (latestEvent && latestEvent.status !== \"unknown\") {\n status = latestEvent.status;\n } else {\n status =\n mapAymakanStatus(data.status) ??\n mapAymakanStatusLabel(data.status) ??\n latestEvent?.status ??\n \"unknown\";\n }\n\n return {\n trackingNumber: data.tracking_number,\n carrier: \"aymakan\",\n reference: data.reference ?? undefined,\n status,\n statusLabel: data.status_label,\n events,\n deliveryDate: data.delivery_date ? new Date(data.delivery_date) : undefined,\n pickupDate: data.pickup_date ? new Date(data.pickup_date) : undefined,\n receivedAt: data.received_at ? new Date(data.received_at) : undefined,\n codAmount: data.cod_amount\n ? Number.isNaN(parseFloat(data.cod_amount))\n ? undefined\n : parseFloat(data.cod_amount)\n : undefined,\n weight: Number.isNaN(parseFloat(data.weight))\n ? undefined\n : parseFloat(data.weight),\n pieces: data.pieces,\n raw: data,\n };\n}\n\nexport function mapCity(city: AymakanCity): City {\n return {\n nameEn: city.city_en,\n nameAr: city.city_ar,\n };\n}\n\nexport function mapPickupResponse(data: AymakanPickupResponse[\"data\"]): Pickup {\n return {\n id: data.id,\n carrier: \"aymakan\",\n status: (\n [\"pending\", \"processing\", \"completed\", \"cancelled\"] as const\n ).includes(data.status as Pickup[\"status\"])\n ? (data.status as Pickup[\"status\"])\n : \"pending\",\n date: data.pickup_date,\n timeSlot: data.time_slot,\n city: data.city,\n contactName: data.contact_name,\n contactPhone: data.contact_phone,\n address: data.address,\n shipmentCount: data.shipments,\n warehouseId: data.warehouse_id,\n warehouseName: data.warehouse_name,\n createdAt: new Date(data.created_at),\n raw: data,\n };\n}\n\n// ============================================================================\n// WEBHOOK MAPPER\n// ============================================================================\n\n/**\n * Timing-safe string comparison to prevent timing attacks on auth tokens.\n */\nfunction timingSafeEqual(a: string, b: string): boolean {\n const encoder = new TextEncoder();\n const bufA = encoder.encode(a);\n const bufB = encoder.encode(b);\n if (bufA.byteLength !== bufB.byteLength) return false;\n // Use crypto.subtle-compatible constant-time compare\n let mismatch = 0;\n for (let i = 0; i < bufA.byteLength; i++) {\n mismatch |= bufA[i]! ^ bufB[i]!;\n }\n return mismatch === 0;\n}\n\nexport function parseAymakanWebhook(\n payload: unknown,\n options?: {\n headers?: Record<string, string>;\n queryParams?: Record<string, string>;\n config?: WebhookConfig;\n },\n): WebhookEvent {\n const { headers = {}, queryParams = {}, config } = options ?? {};\n\n // Validate required fields\n if (\n !payload ||\n typeof payload !== \"object\" ||\n !(\"tracking_number\" in payload) ||\n !(\"status\" in payload)\n ) {\n throw new ValidationError(\n \"Invalid webhook payload: missing required fields\",\n {\n raw: payload,\n },\n );\n }\n\n const data = payload as AymakanWebhookPayload;\n\n // Verify auth via header (case-insensitive lookup, timing-safe comparison)\n if (config?.authHeader && config?.authValue) {\n const lowerKey = config.authHeader.toLowerCase();\n const headerValue = Object.entries(headers).find(\n ([k]) => k.toLowerCase() === lowerKey,\n )?.[1];\n if (!headerValue || !timingSafeEqual(headerValue, config.authValue)) {\n throw new WebhookVerificationError(\"Invalid webhook auth header\", {\n carrier: \"aymakan\",\n });\n }\n }\n\n // Verify auth via query param (timing-safe comparison)\n if (config?.authQueryParam && config?.authQueryValue) {\n const paramValue = queryParams[config.authQueryParam];\n if (!paramValue || !timingSafeEqual(paramValue, config.authQueryValue)) {\n throw new WebhookVerificationError(\"Invalid webhook auth query param\", {\n carrier: \"aymakan\",\n });\n }\n }\n\n const eventType = data.event ?? \"status_update\";\n\n // Validate timestamp\n const timestamp = new Date(data.date_time);\n if (Number.isNaN(timestamp.getTime())) {\n throw new ValidationError(\n \"Invalid webhook payload: invalid date_time value\",\n { raw: data.date_time },\n );\n }\n\n return {\n carrier: \"aymakan\",\n eventType,\n trackingNumber: data.tracking_number,\n reference: data.reference ?? undefined,\n // `status` is a core field present on every webhook regardless of event\n // type, so map it even for weight_update events. Consumers can still branch\n // on `eventType` to detect weight updates.\n status: mapAymakanStatus(data.status) ?? \"unknown\",\n statusCode: data.status,\n statusLabel: data.status_label,\n reasonCode: data.reason_code ?? undefined,\n reasonLabel: data.reason_en ?? undefined,\n timestamp,\n raw: data,\n };\n}\n",
|
|
7
|
+
"// file: src/carriers/aymakan/adapter.ts\n/**\n * Aymakan Carrier Adapter\n * Full implementation of the CarrierAdapter interface for Aymakan API.\n */\n\nimport {\n APIError,\n UnsupportedOperationError,\n ValidationError,\n} from \"../../core/errors\";\nimport { HttpClient } from \"../../core/http\";\nimport {\n validateCreateShipmentInput,\n validatePickupRequest,\n} from \"../../core/schemas\";\nimport type {\n Address,\n CarrierConfig,\n City,\n CreateShipmentInput,\n CustomerAddress,\n Pickup,\n PickupRequest,\n Shipment,\n TimeSlot,\n TrackingResult,\n WebhookConfig,\n WebhookEvent,\n} from \"../../core/types\";\nimport { BaseCarrierAdapter } from \"../base\";\nimport {\n mapCity,\n mapCreateShipmentRequest,\n mapCustomerAddressRequest,\n mapPickupRequest,\n mapPickupResponse,\n mapShipmentResponse,\n mapTrackingResult,\n parseAymakanWebhook,\n} from \"./mappers\";\nimport type {\n AymakanAddressResponse,\n AymakanCancelResponse,\n AymakanCitiesResponse,\n AymakanCreateShipmentResponse,\n AymakanPickupResponse,\n AymakanTrackResponse,\n} from \"./types\";\n\nconst AYMAKAN_SANDBOX_URL = \"https://dev-api.aymakan.com.sa/v2\";\nconst AYMAKAN_PRODUCTION_URL = \"https://api.aymakan.net/v2\";\n\n/**\n * Aymakan returns logical errors inside a fake-200 envelope (HTTP 200 with an\n * error flag/message in the body). Without an extractor these are swallowed —\n * e.g. cancel endpoints would return `success: undefined` and the adapter would\n * report `false` with no reason. This extractor surfaces those as APIError.\n *\n * Recognised error shapes (all on a 200 body):\n * - `{ error: true, ... }` → Laravel/Aymakan error flag\n * - `{ success: false, ... }` → explicit failure flag\n * - `{ message }` / `{ response }` → human-readable error text\n * - `{ errors: { field: [...] } }` → Laravel field validation errors\n *\n * A bare `{ message }` is NOT treated as an error on its own (many success\n * envelopes carry a `message`); we only flag when `error`/`success:false` is\n * present, or validation `errors` exist.\n */\nfunction aymakanErrorExtractor(json: unknown): {\n hasError: boolean;\n message?: string;\n errors?: Record<string, string[]>;\n} {\n if (!json || typeof json !== \"object\") {\n return { hasError: false };\n }\n const obj = json as Record<string, unknown>;\n\n const hasValidationErrors =\n !!obj.errors &&\n typeof obj.errors === \"object\" &&\n Object.keys(obj.errors as object).length > 0;\n const hasError = obj.error === true || obj.success === false || hasValidationErrors;\n\n if (!hasError) {\n return { hasError: false };\n }\n\n const message =\n (typeof obj.message === \"string\" && obj.message) ||\n (typeof obj.response === \"string\" && obj.response) ||\n (typeof obj.error === \"string\" && obj.error) ||\n undefined;\n\n return {\n hasError: true,\n message,\n errors: hasValidationErrors\n ? (obj.errors as Record<string, string[]>)\n : undefined,\n };\n}\n\nexport interface AymakanConfig extends CarrierConfig {\n credentials: {\n apiKey: string;\n };\n}\n\nexport class AymakanAdapter extends BaseCarrierAdapter {\n readonly name = \"aymakan\";\n readonly supportedCountries = [\"SA\", \"AE\", \"BH\", \"KW\", \"OM\", \"QA\"];\n\n private http: HttpClient;\n\n /**\n * Shared options applied to every Aymakan http call so fake-200 error\n * envelopes surface as APIError instead of being silently swallowed.\n */\n private readonly errorOpts = { errorExtractor: aymakanErrorExtractor };\n\n /** Cached Aymakan cities list for city name resolution */\n private citiesCache: City[] | null = null;\n private citiesCacheTime = 0;\n /** Cache TTL: 1 hour */\n private static readonly CITIES_CACHE_TTL = 60 * 60 * 1000;\n\n constructor(config: AymakanConfig) {\n super(config);\n this.http = new HttpClient({\n baseUrl: this.getBaseUrl(),\n carrier: \"aymakan\",\n headers: {\n Authorization: config.credentials.apiKey,\n },\n });\n }\n\n protected getBaseUrl(): string {\n return this.config.mode === \"production\"\n ? AYMAKAN_PRODUCTION_URL\n : AYMAKAN_SANDBOX_URL;\n }\n\n // =========================================================================\n // CITY RESOLUTION\n // =========================================================================\n\n /**\n * Load cities list from Aymakan API and cache it.\n * Silently falls back to empty list on failure so shipments can still be attempted.\n */\n private async ensureCitiesLoaded(): Promise<void> {\n if (\n this.citiesCache &&\n Date.now() - this.citiesCacheTime < AymakanAdapter.CITIES_CACHE_TTL\n ) {\n return;\n }\n try {\n this.citiesCache = await this.getCities();\n this.citiesCacheTime = Date.now();\n } catch {\n if (!this.citiesCache) this.citiesCache = [];\n }\n }\n\n /**\n * Normalize Arabic text for comparison:\n * - Strip diacritics (tashkeel)\n * - Normalize alef variants (أ إ آ → ا)\n * - Normalize taa marbuta (ة → ه)\n * - Trim whitespace\n */\n private static normalizeArabic(text: string): string {\n return text\n .trim()\n .replace(/[\\u064B-\\u065F\\u0670]/g, \"\") // strip tashkeel\n .replace(/[أإآ]/g, \"ا\") // normalize alef\n .replace(/ة/g, \"ه\"); // normalize taa marbuta\n }\n\n /**\n * Resolve a user-input city name to the valid Aymakan English city name.\n *\n * Matching strategy (first match wins):\n * 1. Exact match on English name (case-insensitive)\n * 2. Exact match on Arabic name\n * 3. Normalized Arabic match (handles tashkeel, alef variants, taa marbuta)\n * 4. Match after stripping \"ال\" prefix from Arabic input\n * 5. Substring/contains match on English name (case-insensitive)\n *\n * Falls back to the original input if no match is found.\n */\n private resolveCity(inputCity: string): string {\n if (!this.citiesCache || this.citiesCache.length === 0) return inputCity;\n\n const trimmed = inputCity.trim();\n if (!trimmed) return inputCity;\n const lower = trimmed.toLowerCase();\n\n // 1. Exact English match (case-insensitive)\n const exactEn = this.citiesCache.find(\n (c) => c.nameEn.toLowerCase() === lower,\n );\n if (exactEn) return exactEn.nameEn;\n\n // 2. Exact Arabic match\n const exactAr = this.citiesCache.find((c) => c.nameAr === trimmed);\n if (exactAr) return exactAr.nameEn;\n\n // 3. Normalized Arabic match\n const normalizedInput = AymakanAdapter.normalizeArabic(trimmed);\n const normalizedAr = this.citiesCache.find(\n (c) =>\n c.nameAr &&\n AymakanAdapter.normalizeArabic(c.nameAr) === normalizedInput,\n );\n if (normalizedAr) return normalizedAr.nameEn;\n\n // 4. Arabic without \"ال\" prefix\n const withoutAl = normalizedInput.startsWith(\"ال\")\n ? normalizedInput.slice(2)\n : `ال${normalizedInput}`;\n const alMatch = this.citiesCache.find(\n (c) => c.nameAr && AymakanAdapter.normalizeArabic(c.nameAr) === withoutAl,\n );\n if (alMatch) return alMatch.nameEn;\n\n // 5. Contains match on English name (for partial matches like \"Riyadh\" in \"Riyadh City\")\n const containsEn = this.citiesCache.find(\n (c) =>\n c.nameEn.toLowerCase().includes(lower) ||\n lower.includes(c.nameEn.toLowerCase()),\n );\n if (containsEn) return containsEn.nameEn;\n\n // No match — return as-is and let the API return a validation error\n return trimmed;\n }\n\n /**\n * Resolve city names in a CreateShipmentInput to valid Aymakan city names.\n */\n private resolveCitiesInInput(\n input: CreateShipmentInput,\n ): CreateShipmentInput {\n return {\n ...input,\n shipper: {\n ...input.shipper,\n city: this.resolveCity(input.shipper.city),\n },\n consignee: {\n ...input.consignee,\n city: this.resolveCity(input.consignee.city),\n },\n };\n }\n\n // =========================================================================\n // SHIPPING\n // =========================================================================\n\n protected async executeCreateShipment(\n input: CreateShipmentInput,\n ): Promise<Shipment> {\n await this.ensureCitiesLoaded();\n const resolved = this.resolveCitiesInInput(input);\n const request = mapCreateShipmentRequest(resolved);\n const response = await this.http.post<AymakanCreateShipmentResponse>(\n \"/shipping/create\",\n request,\n this.errorOpts,\n );\n\n if (!response.success) {\n throw new APIError(\"Failed to create shipment\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return mapShipmentResponse(response.shipping);\n }\n\n async createBulkShipments(\n inputs: CreateShipmentInput[],\n ): Promise<Shipment[]> {\n // Nothing to create — avoid an empty request to the carrier.\n if (inputs.length === 0) return [];\n // Aymakan rejects batches larger than 30 (\"Only 30 shipments can be\n // created at a time.\"), so fail fast with a clear error before the request.\n if (inputs.length > 30) {\n throw new ValidationError(\n \"Aymakan bulk create accepts at most 30 shipments per request\",\n { raw: { count: inputs.length } },\n );\n }\n inputs.forEach(validateCreateShipmentInput);\n await this.ensureCitiesLoaded();\n const requests = inputs\n .map((i) => this.resolveCitiesInInput(i))\n .map(mapCreateShipmentRequest);\n const response = await this.http.post<{\n success: boolean;\n message?: string;\n bulk_awb?: string;\n total_shipments?: number;\n shipments: AymakanCreateShipmentResponse[\"shipping\"][];\n }>(\"/shipping/create/bulk_shipping\", { shipments: requests }, this.errorOpts);\n\n if (!response.success) {\n throw new APIError(\"Failed to create bulk shipments\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n if (!Array.isArray(response.shipments)) {\n throw new APIError(\n \"Aymakan bulk create response is missing the shipments array\",\n { carrier: \"aymakan\", raw: response },\n );\n }\n\n return response.shipments.map(mapShipmentResponse);\n }\n\n async cancelShipment(trackingNumber: string): Promise<boolean> {\n // The errorExtractor surfaces fake-200 error envelopes as APIError, so a\n // real failure throws with the carrier's message rather than silently\n // returning false.\n const response = await this.http.post<AymakanCancelResponse>(\n \"/shipping/cancel\",\n {\n tracking: trackingNumber,\n },\n this.errorOpts,\n );\n return response.success === true;\n }\n\n async cancelByReference(reference: string): Promise<boolean> {\n const response = await this.http.post<AymakanCancelResponse>(\n \"/shipping/cancel_by_reference\",\n { reference },\n this.errorOpts,\n );\n return response.success === true;\n }\n\n async updateDeliveryAddress(\n trackingNumber: string,\n address: Address,\n ): Promise<boolean> {\n await this.ensureCitiesLoaded();\n const resolvedCity = this.resolveCity(address.city);\n const response = await this.http.post<{ success: boolean }>(\n `/shipping/update_delivery_address/${encodeURIComponent(trackingNumber)}`,\n {\n delivery_name: address.name,\n delivery_email: address.email,\n delivery_city: resolvedCity,\n delivery_address: address.line1,\n delivery_neighbourhood: address.neighbourhood,\n delivery_postcode: address.postalCode,\n delivery_country: address.countryCode,\n delivery_phone: address.phone,\n },\n );\n return response.success;\n }\n\n // =========================================================================\n // TRACKING\n // =========================================================================\n\n async track(trackingNumber: string): Promise<TrackingResult> {\n const results = await this.trackMultiple([trackingNumber]);\n const result = results[0];\n if (!result) {\n throw new APIError(\"Shipment not found\", { carrier: \"aymakan\" });\n }\n return result;\n }\n\n async trackMultiple(trackingNumbers: string[]): Promise<TrackingResult[]> {\n const ids = trackingNumbers.map(encodeURIComponent).join(\",\");\n const response = await this.http.get<AymakanTrackResponse>(\n `/shipping/track/${ids}`,\n this.errorOpts,\n );\n\n if (!response.success) {\n throw new APIError(\"Failed to track shipments\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n if (!Array.isArray(response.data?.shipments)) {\n throw new APIError(\"Aymakan track response is missing data.shipments\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return response.data.shipments.map(mapTrackingResult);\n }\n\n async trackByReference(reference: string): Promise<TrackingResult> {\n const response = await this.http.get<AymakanTrackResponse>(\n `/shipping/by_reference/${encodeURIComponent(reference)}`,\n this.errorOpts,\n );\n\n const shipment = response.data?.shipments?.[0];\n if (!response.success || !shipment) {\n throw new APIError(\"Shipment not found\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return mapTrackingResult(shipment);\n }\n\n // =========================================================================\n // LABELS\n // =========================================================================\n\n async getLabel(\n trackingNumber: string,\n _format?: \"PDF\" | \"ZPL\" | \"PNG\",\n ): Promise<string> {\n // Note: this endpoint always returns a single PDF download URL, so the\n // requested `format` cannot be honored.\n const response = await this.http.get<{\n success: boolean;\n error?: boolean;\n message?: string;\n response?: string;\n data: { awb_url: string };\n }>(\n `/shipping/awb/tracking/${encodeURIComponent(trackingNumber)}`,\n this.errorOpts,\n );\n\n if (!response.success || !response.data?.awb_url) {\n const msg = response.message ?? response.response ?? \"Failed to get label\";\n throw new APIError(msg, { carrier: \"aymakan\", raw: response });\n }\n\n return response.data.awb_url;\n }\n\n async getBulkLabels(trackingNumbers: string[]): Promise<string> {\n // Documented as a GET with comma-separated tracking numbers in the path.\n const ids = trackingNumbers.map(encodeURIComponent).join(\",\");\n const response = await this.http.get<{\n success: boolean;\n error?: boolean;\n message?: string;\n response?: string;\n data: { bulk_awb_url: string };\n }>(`/shipping/bulk_awb/trackings/${ids}`, this.errorOpts);\n\n if (!response.success || !response.data?.bulk_awb_url) {\n const msg =\n response.message ?? response.response ?? \"Failed to get bulk labels\";\n throw new APIError(msg, { carrier: \"aymakan\", raw: response });\n }\n\n return response.data.bulk_awb_url;\n }\n\n // =========================================================================\n // PICKUPS\n // =========================================================================\n\n async getPickupCities(): Promise<City[]> {\n const response = await this.http.get<AymakanCitiesResponse>(\n \"/pickup_request/cities\",\n this.errorOpts,\n );\n\n if (!response.success) {\n throw new APIError(\"Failed to get pickup cities\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return response.data.cities.map(mapCity);\n }\n\n async getTimeSlots(_city: string, date: string): Promise<TimeSlot[]> {\n const response = await this.http.get<{\n success: boolean;\n error?: boolean;\n message?: string;\n data: {\n date: string;\n slots: Record<string, string>;\n };\n }>(`/time_slots/${date}`);\n\n // Aymakan returns error responses for invalid dates (past, Fridays, etc.)\n if (!response.success || response.error || !response.data?.slots) {\n const msg = response.message ?? \"No slots available\";\n throw new APIError(msg, { carrier: \"aymakan\", raw: response });\n }\n\n // data.slots is an object like { \"afternoon\": \"After Noon (02 PM - 06 PM)\" }\n return Object.entries(response.data.slots).map(([id, label]) => ({\n id,\n label,\n }));\n }\n\n async createPickup(input: PickupRequest): Promise<Pickup> {\n validatePickupRequest(input);\n await this.ensureCitiesLoaded();\n const resolvedInput = { ...input, city: this.resolveCity(input.city) };\n const request = mapPickupRequest(resolvedInput);\n const response = await this.http.post<AymakanPickupResponse>(\n \"/pickup_request/create\",\n request,\n this.errorOpts,\n );\n\n if (!response.success) {\n throw new APIError(\"Failed to create pickup\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return mapPickupResponse(response.data);\n }\n\n async cancelPickup(pickupId: string | number): Promise<boolean> {\n const response = await this.http.post<{ success: boolean }>(\n \"/pickup_request/cancel\",\n { pickup_request: Number(pickupId) },\n this.errorOpts,\n );\n return response.success === true;\n }\n\n async getPickupRequests(): Promise<Pickup[]> {\n // The list endpoint returns a Laravel paginated structure:\n // { success, data: { pickupRequests: { data: [...] } } }\n const response = await this.http.get<{\n success: boolean;\n data: { pickupRequests: { data: AymakanPickupResponse[\"data\"][] } };\n }>(\"/pickup_request/list\", this.errorOpts);\n\n if (!response.success) {\n throw new APIError(\"Failed to get pickup requests\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n const list = response.data?.pickupRequests?.data;\n if (!Array.isArray(list)) {\n throw new APIError(\n \"Aymakan pickup list response is missing data.pickupRequests.data\",\n { carrier: \"aymakan\", raw: response },\n );\n }\n\n return list.map(mapPickupResponse);\n }\n\n // =========================================================================\n // CITIES & LOCATIONS\n // =========================================================================\n\n async getCities(): Promise<City[]> {\n const response = await this.http.get<AymakanCitiesResponse>(\"/cities\");\n\n if (!response.success) {\n throw new APIError(\"Failed to get cities\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return response.data.cities.map(mapCity);\n }\n\n async getDropoffLocations(): Promise<\n {\n id: string;\n name: string;\n address?: string;\n city?: string;\n latitude?: number;\n longitude?: number;\n }[]\n > {\n // The API returns `data` as a flat array of warehouse objects. There is no\n // `id` field — the warehouse code lives in `name` (e.g. \"RUH-WH\").\n const response = await this.http.get<{\n success: boolean;\n data: Array<{\n name: string;\n city?: string;\n address?: string;\n manager?: string;\n mobile_phone?: string;\n email?: string;\n location_lat?: number | null;\n location_lng?: number | null;\n }>;\n }>(\"/dropoff_locations\");\n\n if (!response.success) {\n throw new APIError(\"Failed to get dropoff locations\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n return response.data.map((loc) => ({\n id: loc.name,\n name: loc.name,\n address: loc.address,\n city: loc.city,\n latitude: loc.location_lat ?? undefined,\n longitude: loc.location_lng ?? undefined,\n }));\n }\n\n // =========================================================================\n // CUSTOMER ADDRESSES\n // =========================================================================\n\n async createCustomerAddress(\n address: CustomerAddress,\n ): Promise<CustomerAddress> {\n const request = mapCustomerAddressRequest(address);\n const response = await this.http.post<AymakanAddressResponse>(\n \"/address/create\",\n request,\n this.errorOpts,\n );\n\n if (!response.success || !response.data?.address) {\n throw new APIError(\"Failed to create address\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n const addr = response.data.address;\n return {\n id: addr.id,\n title: addr.title,\n name: addr.name,\n email: addr.email,\n phone: addr.phone,\n city: addr.city,\n address: addr.address,\n neighbourhood: addr.neighbourhood,\n postalCode: addr.postcode,\n countryCode: addr.country,\n description: addr.description,\n };\n }\n\n async getCustomerAddresses(): Promise<CustomerAddress[]> {\n const response = await this.http.get<{\n success: boolean;\n data: { address: AymakanAddressResponse[\"data\"][\"address\"][] };\n }>(\"/address/list\", this.errorOpts);\n\n if (!response.success) {\n throw new APIError(\"Failed to get customer addresses\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n if (!Array.isArray(response.data?.address)) {\n throw new APIError(\n \"Aymakan address list response is missing data.address\",\n { carrier: \"aymakan\", raw: response },\n );\n }\n\n return response.data.address.map((addr) => ({\n id: addr.id,\n title: addr.title,\n name: addr.name,\n email: addr.email,\n phone: addr.phone,\n city: addr.city,\n address: addr.address,\n neighbourhood: addr.neighbourhood,\n postalCode: addr.postcode,\n countryCode: addr.country,\n description: addr.description,\n }));\n }\n\n async updateCustomerAddress(\n id: number,\n address: Partial<CustomerAddress>,\n ): Promise<CustomerAddress> {\n const response = await this.http.put<AymakanAddressResponse>(\n \"/address/update\",\n {\n id,\n title: address.title,\n name: address.name,\n email: address.email,\n city: address.city,\n address: address.address,\n neighbourhood: address.neighbourhood,\n postcode: address.postalCode,\n phone: address.phone,\n description: address.description,\n },\n this.errorOpts,\n );\n\n if (!response.success || !response.data?.address) {\n throw new APIError(\"Failed to update address\", {\n carrier: \"aymakan\",\n raw: response,\n });\n }\n\n const addr = response.data.address;\n return {\n id: addr.id,\n title: addr.title,\n name: addr.name,\n email: addr.email,\n phone: addr.phone,\n city: addr.city,\n address: addr.address,\n neighbourhood: addr.neighbourhood,\n postalCode: addr.postcode,\n countryCode: addr.country,\n description: addr.description,\n };\n }\n\n async deleteCustomerAddress(id: number): Promise<boolean> {\n // The id must be sent in the JSON request body, not the URL path.\n const response = await this.http.delete<{ success: boolean }>(\n \"/address/delete\",\n { body: { id }, errorExtractor: aymakanErrorExtractor },\n );\n return response.success;\n }\n\n // =========================================================================\n // WEBHOOKS\n // =========================================================================\n\n parseWebhook(\n payload: unknown,\n options?: {\n headers?: Record<string, string>;\n queryParams?: Record<string, string>;\n config?: WebhookConfig;\n },\n ): WebhookEvent {\n return parseAymakanWebhook(payload, options);\n }\n\n // =========================================================================\n // NOT SUPPORTED\n // =========================================================================\n\n getRates(): Promise<never> {\n throw new UnsupportedOperationError(\"aymakan\", \"getRates\");\n }\n}\n"
|
|
8
8
|
],
|
|
9
|
-
"mappings": "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",
|
|
10
|
-
"debugId": "
|
|
9
|
+
"mappings": ";;;;;;;;;;;;AAMO,IAAM,iBAAiB;AAAA,EAE5B,WAAW;AAAA,EAEX,WAAW;AAAA,EAEX,UAAU;AAAA,EAEV,gBAAgB;AAAA,EAEhB,UAAU;AAAA,EAEV,SAAS;AAAA,EAET,OAAO;AAAA,EAEP,QAAQ;AAAA,EAER,gBAAgB;AAAA,EAEhB,gBAAgB;AAClB;AASO,IAAM,qBAAqB;AAAA,EAEhC,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EAGX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EAGX,WAAW;AAAA,EAGX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AACb;;;AClBO,SAAS,gBAAgB,CAAC,YAAgD;AAAA,EAC/E,MAAM,SACJ,mBAAmB;AAAA,EACrB,OAAO;AAAA;AAUT,IAAM,sBAAsD;AAAA,EAC1D,yBAAyB;AAAA,EACzB,oBAAoB;AAAA,EACpB,mCAAmC;AAAA,EACnC,WAAW;AAAA,EACX,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,yBAAyB;AAAA,EACzB,oBAAoB;AAAA,EACpB,6BAA6B;AAAA,EAC7B,WAAW;AAAA,EACX,yBAAyB;AAAA,EACzB,mCAAmC;AAAA,EACnC,cAAc;AAAA,EACd,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,SAAS;AAAA,EACT,mBAAmB;AAAA,EACnB,UAAU;AAAA,EACV,uBAAuB;AAAA,EACvB,oBAAoB;AAAA,EACpB,WAAW;AAAA,EACX,UAAU;AACZ;AAMO,SAAS,qBAAqB,CACnC,OAC4B;AAAA,EAC5B,OAAO,oBAAoB,MAAM,KAAK,EAAE,YAAY;AAAA;AAOtD,SAAS,kBAAkB,CACzB,MAC2C;AAAA,EAC3C,IAAI,CAAC;AAAA,IAAM;AAAA,EACX,OAAO;AAAA,IACL,YAAY,KAAK;AAAA,IACjB,iBAAiB,KAAK;AAAA,IACtB,aAAa,KAAK;AAAA,IAClB,UAAU,KAAK;AAAA,IACf,mBAAmB,KAAK;AAAA,EAC1B;AAAA;AAIF,SAAS,aAAa,CAAC,OAAuB;AAAA,EAC5C,OAAO,MAAM,QAAQ,OAAO,EAAE;AAAA;AAIhC,IAAM,8BAA8B,IAAI,IACtC,OAAO,OAAO,cAAc,CAC9B;AAMA,SAAS,kBAAkB,CAAC,aAA0C;AAAA,EACpE,IAAI,CAAC;AAAA,IAAa;AAAA,EAClB,IAAI,4BAA4B,IAAI,WAAW;AAAA,IAAG,OAAO;AAAA,EACzD;AAAA;AAGK,SAAS,wBAAwB,CACtC,OAC8B;AAAA,EAC9B,MAAM,cAAc,MAAM,QAAQ;AAAA,EAClC,MAAM,cAAc,MAAM,QAAQ,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,QAAQ,CAAC;AAAA,EACtE,MAAM,cAAc,MAAM,QAAQ,OAChC,CAAC,KAAK,MACJ,OACC,EAAE,OAAO,SAAS,OAAO,EAAE,OAAO,QAAQ,WAAW,EAAE,OAAO,QACjE,CACF;AAAA,EACA,MAAM,aAAa,MAAM,QAAQ,OAC/B,CAAC,KAAK,MAAM,OAAO,EAAE,cAAc,EAAE,SACrC,CACF;AAAA,EAKA,MAAM,aAAa,MAAM,KAAK,YAAY;AAAA,EAC1C,MAAM,wBAAwB,MAAM,eAAe,YAAY;AAAA,EAE/D,OAAO;AAAA,IACL,cAAc,MAAM,SAAS,eAAe,MAAM,QAAQ;AAAA,IAC1D,gBAAgB,MAAM,eAAe,UAAU;AAAA,IAC/C,yBAAyB;AAAA,IACzB,WAAW,MAAM;AAAA,IACjB,mBAAmB,MAAM,SAAS;AAAA,IAClC,cAAc,mBAAmB,MAAM,WAAW;AAAA,IAClD,QAAQ,aAAa,IAAI;AAAA,IACzB,YAAY,aAAa,MAAM,KAAK,SAAS;AAAA,IAC7C,0BAA0B,MAAM,SAAS;AAAA,IAGzC,UAAU,aACL,MAAM,KAAK,YAAY,QACxB;AAAA,IAGJ,eAAe,MAAM,UAAU;AAAA,IAC/B,gBAAgB,MAAM,UAAU;AAAA,IAChC,eAAe,MAAM,UAAU;AAAA,IAC/B,kBAAkB,MAAM,UAAU;AAAA,IAClC,wBACE,MAAM,UAAU,iBAAiB,MAAM,UAAU;AAAA,IACnD,mBAAmB,MAAM,UAAU;AAAA,IACnC,kBAAkB,MAAM,UAAU;AAAA,IAClC,gBAAgB,cAAc,MAAM,UAAU,KAAK;AAAA,IACnD,sBAAsB,MAAM,UAAU;AAAA,IACtC,2BAA2B,mBACzB,MAAM,UAAU,eAClB;AAAA,IAGA,iBAAiB,MAAM,QAAQ,WAAW,MAAM,QAAQ;AAAA,IACxD,kBAAkB,MAAM,QAAQ;AAAA,IAChC,iBAAiB,MAAM,QAAQ;AAAA,IAC/B,oBAAoB,MAAM,QAAQ;AAAA,IAClC,0BACE,MAAM,QAAQ,iBAAiB,MAAM,QAAQ;AAAA,IAC/C,qBAAqB,MAAM,QAAQ;AAAA,IACnC,oBAAoB,MAAM,QAAQ;AAAA,IAClC,kBAAkB,cAAc,MAAM,QAAQ,KAAK;AAAA,IACnD,wBAAwB,MAAM,QAAQ;AAAA,IACtC,6BAA6B,mBAC3B,MAAM,QAAQ,eAChB;AAAA,IAGA,QAAQ;AAAA,IACR,QAAQ,aAAa,YAAY;AAAA,IACjC,OAAO,aAAa,YAAY;AAAA,IAChC,QAAQ,aAAa,YAAY;AAAA,IACjC,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,YAAY,MAAM,SAAS,YAAY,IAAI;AAAA,IAG3C,wBAAwB,MAAM,SAAS,wBACnC;AAAA,MACA,aAAa,MAAM,QAAQ,sBAAsB;AAAA,MACjD,2BAA2B,MAAM,QAAQ,sBAAsB;AAAA,MAC/D,gBAAgB,MAAM,QAAQ,sBAAsB;AAAA,MACpD,cAAc,MAAM,QAAQ,sBAAsB;AAAA,IACpD,IACE;AAAA,EACN;AAAA;AAGK,SAAS,gBAAgB,CAAC,OAA4C;AAAA,EAC3E,OAAO;AAAA,IACL,WAAW,MAAM,kBAAkB;AAAA,IACnC,aAAa,MAAM;AAAA,IACnB,WAAW,MAAM;AAAA,IACjB,MAAM,MAAM;AAAA,IACZ,cAAc,MAAM;AAAA,IACpB,eAAe,MAAM;AAAA,IACrB,SAAS,MAAM;AAAA,IACf,WAAW,MAAM;AAAA,EACnB;AAAA;AAGK,SAAS,yBAAyB,CACvC,MACuB;AAAA,EACvB,OAAO;AAAA,IACL,OAAO,KAAK;AAAA,IACZ,MAAM,KAAK;AAAA,IACX,OAAO,KAAK;AAAA,IACZ,MAAM,KAAK;AAAA,IACX,SAAS,KAAK;AAAA,IACd,eAAe,KAAK;AAAA,IACpB,UAAU,KAAK,cAAc;AAAA,IAC7B,OAAO,KAAK;AAAA,IACZ,aAAa,KAAK,eAAe;AAAA,EACnC;AAAA;AAOK,SAAS,mBAAmB,CAAC,MAAyC;AAAA,EAE3E,MAAM,YACJ,KAAK,cAAc,OACf,OAAO,KAAK,eAAe,WACzB,WAAW,KAAK,UAAU,IAC1B,KAAK,aACP;AAAA,EAEN,OAAO;AAAA,IACL,SAAS;AAAA,IACT,gBAAgB,KAAK;AAAA,IACrB,kBAAkB,KAAK,qBAAqB;AAAA,IAC5C,WAAW,KAAK,aAAa;AAAA,IAC7B,QAAQ,iBAAiB,KAAK,MAAM,KAAK;AAAA,IACzC,aAAa,KAAK;AAAA,IAClB,UAAU,KAAK,SAAS;AAAA,IACxB,aAAa,KAAK,aAAa;AAAA,IAI/B,WACE,aAAa,QAAQ,CAAC,OAAO,MAAM,SAAS,KAAK,cAAc,IAC3D,YACA;AAAA,IACN,eAAe,KAAK;AAAA,IACpB,UAAU,KAAK;AAAA,IACf,WAAW,IAAI,KAAK,KAAK,UAAU;AAAA,IACnC,KAAK;AAAA,EACP;AAAA;AASF,SAAS,gBAAgB,CAAC,OAAqB;AAAA,EAC7C,IAAI,yBAAyB,KAAK,KAAK,GAAG;AAAA,IACxC,OAAO,IAAI,KAAK,KAAK;AAAA,EACvB;AAAA,EACA,OAAO,IAAI,KAAK,GAAG,MAAM,QAAQ,KAAK,GAAG,SAAS;AAAA;AAG7C,SAAS,gBAAgB,CAAC,OAA4C;AAAA,EAC3E,OAAO;AAAA,IACL,WAAW,iBAAiB,MAAM,UAAU;AAAA,IAC5C,YAAY,MAAM;AAAA,IAClB,QAAQ,iBAAiB,MAAM,WAAW,KAAK;AAAA,IAC/C,aAAa,MAAM;AAAA,IACnB,mBAAmB,MAAM,kBAAkB;AAAA,EAC7C;AAAA;AAGK,SAAS,iBAAiB,CAC/B,MACgB;AAAA,EAChB,MAAM,SAAS,KAAK,cAAc,IAAI,gBAAgB;AAAA,EAStD,MAAM,cAAc,OAAO,SACvB,CAAC,GAAG,MAAM,EAAE,KACV,CAAC,GAAG,MAAM,EAAE,UAAU,QAAQ,IAAI,EAAE,UAAU,QAAQ,CACxD,EAAE,KACF;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI,eAAe,YAAY,WAAW,WAAW;AAAA,IACnD,SAAS,YAAY;AAAA,EACvB,EAAO;AAAA,IACL,SACE,iBAAiB,KAAK,MAAM,KAC5B,sBAAsB,KAAK,MAAM,KACjC,aAAa,UACb;AAAA;AAAA,EAGJ,OAAO;AAAA,IACL,gBAAgB,KAAK;AAAA,IACrB,SAAS;AAAA,IACT,WAAW,KAAK,aAAa;AAAA,IAC7B;AAAA,IACA,aAAa,KAAK;AAAA,IAClB;AAAA,IACA,cAAc,KAAK,gBAAgB,IAAI,KAAK,KAAK,aAAa,IAAI;AAAA,IAClE,YAAY,KAAK,cAAc,IAAI,KAAK,KAAK,WAAW,IAAI;AAAA,IAC5D,YAAY,KAAK,cAAc,IAAI,KAAK,KAAK,WAAW,IAAI;AAAA,IAC5D,WAAW,KAAK,aACZ,OAAO,MAAM,WAAW,KAAK,UAAU,CAAC,IACtC,YACA,WAAW,KAAK,UAAU,IAC5B;AAAA,IACJ,QAAQ,OAAO,MAAM,WAAW,KAAK,MAAM,CAAC,IACxC,YACA,WAAW,KAAK,MAAM;AAAA,IAC1B,QAAQ,KAAK;AAAA,IACb,KAAK;AAAA,EACP;AAAA;AAGK,SAAS,OAAO,CAAC,MAAyB;AAAA,EAC/C,OAAO;AAAA,IACL,QAAQ,KAAK;AAAA,IACb,QAAQ,KAAK;AAAA,EACf;AAAA;AAGK,SAAS,iBAAiB,CAAC,MAA6C;AAAA,EAC7E,OAAO;AAAA,IACL,IAAI,KAAK;AAAA,IACT,SAAS;AAAA,IACT,QACE,CAAC,WAAW,cAAc,aAAa,WAAW,EAClD,SAAS,KAAK,MAA0B,IACrC,KAAK,SACN;AAAA,IACJ,MAAM,KAAK;AAAA,IACX,UAAU,KAAK;AAAA,IACf,MAAM,KAAK;AAAA,IACX,aAAa,KAAK;AAAA,IAClB,cAAc,KAAK;AAAA,IACnB,SAAS,KAAK;AAAA,IACd,eAAe,KAAK;AAAA,IACpB,aAAa,KAAK;AAAA,IAClB,eAAe,KAAK;AAAA,IACpB,WAAW,IAAI,KAAK,KAAK,UAAU;AAAA,IACnC,KAAK;AAAA,EACP;AAAA;AAUF,SAAS,eAAe,CAAC,GAAW,GAAoB;AAAA,EACtD,MAAM,UAAU,IAAI;AAAA,EACpB,MAAM,OAAO,QAAQ,OAAO,CAAC;AAAA,EAC7B,MAAM,OAAO,QAAQ,OAAO,CAAC;AAAA,EAC7B,IAAI,KAAK,eAAe,KAAK;AAAA,IAAY,OAAO;AAAA,EAEhD,IAAI,WAAW;AAAA,EACf,SAAS,IAAI,EAAG,IAAI,KAAK,YAAY,KAAK;AAAA,IACxC,YAAY,KAAK,KAAM,KAAK;AAAA,EAC9B;AAAA,EACA,OAAO,aAAa;AAAA;AAGf,SAAS,mBAAmB,CACjC,SACA,SAKc;AAAA,EACd,QAAQ,UAAU,CAAC,GAAG,cAAc,CAAC,GAAG,WAAW,WAAW,CAAC;AAAA,EAG/D,IACE,CAAC,WACD,OAAO,YAAY,YACnB,EAAE,qBAAqB,YACvB,EAAE,YAAY,UACd;AAAA,IACA,MAAM,IAAI,gBACR,oDACA;AAAA,MACE,KAAK;AAAA,IACP,CACF;AAAA,EACF;AAAA,EAEA,MAAM,OAAO;AAAA,EAGb,IAAI,QAAQ,cAAc,QAAQ,WAAW;AAAA,IAC3C,MAAM,WAAW,OAAO,WAAW,YAAY;AAAA,IAC/C,MAAM,cAAc,OAAO,QAAQ,OAAO,EAAE,KAC1C,EAAE,OAAO,EAAE,YAAY,MAAM,QAC/B,IAAI;AAAA,IACJ,IAAI,CAAC,eAAe,CAAC,gBAAgB,aAAa,OAAO,SAAS,GAAG;AAAA,MACnE,MAAM,IAAI,yBAAyB,+BAA+B;AAAA,QAChE,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAGA,IAAI,QAAQ,kBAAkB,QAAQ,gBAAgB;AAAA,IACpD,MAAM,aAAa,YAAY,OAAO;AAAA,IACtC,IAAI,CAAC,cAAc,CAAC,gBAAgB,YAAY,OAAO,cAAc,GAAG;AAAA,MACtE,MAAM,IAAI,yBAAyB,oCAAoC;AAAA,QACrE,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,KAAK,SAAS;AAAA,EAGhC,MAAM,YAAY,IAAI,KAAK,KAAK,SAAS;AAAA,EACzC,IAAI,OAAO,MAAM,UAAU,QAAQ,CAAC,GAAG;AAAA,IACrC,MAAM,IAAI,gBACR,oDACA,EAAE,KAAK,KAAK,UAAU,CACxB;AAAA,EACF;AAAA,EAEA,OAAO;AAAA,IACL,SAAS;AAAA,IACT;AAAA,IACA,gBAAgB,KAAK;AAAA,IACrB,WAAW,KAAK,aAAa;AAAA,IAI7B,QAAQ,iBAAiB,KAAK,MAAM,KAAK;AAAA,IACzC,YAAY,KAAK;AAAA,IACjB,aAAa,KAAK;AAAA,IAClB,YAAY,KAAK,eAAe;AAAA,IAChC,aAAa,KAAK,aAAa;AAAA,IAC/B;AAAA,IACA,KAAK;AAAA,EACP;AAAA;;;AC5aF,IAAM,sBAAsB;AAC5B,IAAM,yBAAyB;AAkB/B,SAAS,qBAAqB,CAAC,MAI7B;AAAA,EACA,IAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AAAA,IACrC,OAAO,EAAE,UAAU,MAAM;AAAA,EAC3B;AAAA,EACA,MAAM,MAAM;AAAA,EAEZ,MAAM,sBACJ,CAAC,CAAC,IAAI,UACN,OAAO,IAAI,WAAW,YACtB,OAAO,KAAK,IAAI,MAAgB,EAAE,SAAS;AAAA,EAC7C,MAAM,WAAW,IAAI,UAAU,QAAQ,IAAI,YAAY,SAAS;AAAA,EAEhE,IAAI,CAAC,UAAU;AAAA,IACb,OAAO,EAAE,UAAU,MAAM;AAAA,EAC3B;AAAA,EAEA,MAAM,UACH,OAAO,IAAI,YAAY,YAAY,IAAI,WACvC,OAAO,IAAI,aAAa,YAAY,IAAI,YACxC,OAAO,IAAI,UAAU,YAAY,IAAI,SACtC;AAAA,EAEF,OAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,IACA,QAAQ,sBACH,IAAI,SACL;AAAA,EACN;AAAA;AAAA;AASK,MAAM,uBAAuB,mBAAmB;AAAA,EAC5C,OAAO;AAAA,EACP,qBAAqB,CAAC,MAAM,MAAM,MAAM,MAAM,MAAM,IAAI;AAAA,EAEzD;AAAA,EAMS,YAAY,EAAE,gBAAgB,sBAAsB;AAAA,EAG7D,cAA6B;AAAA,EAC7B,kBAAkB;AAAA,SAEF,mBAAmB,KAAK,KAAK;AAAA,EAErD,WAAW,CAAC,QAAuB;AAAA,IACjC,MAAM,MAAM;AAAA,IACZ,KAAK,OAAO,IAAI,WAAW;AAAA,MACzB,SAAS,KAAK,WAAW;AAAA,MACzB,SAAS;AAAA,MACT,SAAS;AAAA,QACP,eAAe,OAAO,YAAY;AAAA,MACpC;AAAA,IACF,CAAC;AAAA;AAAA,EAGO,UAAU,GAAW;AAAA,IAC7B,OAAO,KAAK,OAAO,SAAS,eACxB,yBACA;AAAA;AAAA,OAWQ,mBAAkB,GAAkB;AAAA,IAChD,IACE,KAAK,eACL,KAAK,IAAI,IAAI,KAAK,kBAAkB,eAAe,kBACnD;AAAA,MACA;AAAA,IACF;AAAA,IACA,IAAI;AAAA,MACF,KAAK,cAAc,MAAM,KAAK,UAAU;AAAA,MACxC,KAAK,kBAAkB,KAAK,IAAI;AAAA,MAChC,MAAM;AAAA,MACN,IAAI,CAAC,KAAK;AAAA,QAAa,KAAK,cAAc,CAAC;AAAA;AAAA;AAAA,SAWhC,eAAe,CAAC,MAAsB;AAAA,IACnD,OAAO,KACJ,KAAK,EACL,QAAQ,0BAA0B,EAAE,EACpC,QAAQ,UAAS,GAAG,EACpB,QAAQ,MAAK,GAAG;AAAA;AAAA,EAeb,WAAW,CAAC,WAA2B;AAAA,IAC7C,IAAI,CAAC,KAAK,eAAe,KAAK,YAAY,WAAW;AAAA,MAAG,OAAO;AAAA,IAE/D,MAAM,UAAU,UAAU,KAAK;AAAA,IAC/B,IAAI,CAAC;AAAA,MAAS,OAAO;AAAA,IACrB,MAAM,QAAQ,QAAQ,YAAY;AAAA,IAGlC,MAAM,UAAU,KAAK,YAAY,KAC/B,CAAC,MAAM,EAAE,OAAO,YAAY,MAAM,KACpC;AAAA,IACA,IAAI;AAAA,MAAS,OAAO,QAAQ;AAAA,IAG5B,MAAM,UAAU,KAAK,YAAY,KAAK,CAAC,MAAM,EAAE,WAAW,OAAO;AAAA,IACjE,IAAI;AAAA,MAAS,OAAO,QAAQ;AAAA,IAG5B,MAAM,kBAAkB,eAAe,gBAAgB,OAAO;AAAA,IAC9D,MAAM,eAAe,KAAK,YAAY,KACpC,CAAC,MACC,EAAE,UACF,eAAe,gBAAgB,EAAE,MAAM,MAAM,eACjD;AAAA,IACA,IAAI;AAAA,MAAc,OAAO,aAAa;AAAA,IAGtC,MAAM,YAAY,gBAAgB,WAAW,IAAG,IAC5C,gBAAgB,MAAM,CAAC,IACvB,KAAI;AAAA,IACR,MAAM,UAAU,KAAK,YAAY,KAC/B,CAAC,MAAM,EAAE,UAAU,eAAe,gBAAgB,EAAE,MAAM,MAAM,SAClE;AAAA,IACA,IAAI;AAAA,MAAS,OAAO,QAAQ;AAAA,IAG5B,MAAM,aAAa,KAAK,YAAY,KAClC,CAAC,MACC,EAAE,OAAO,YAAY,EAAE,SAAS,KAAK,KACrC,MAAM,SAAS,EAAE,OAAO,YAAY,CAAC,CACzC;AAAA,IACA,IAAI;AAAA,MAAY,OAAO,WAAW;AAAA,IAGlC,OAAO;AAAA;AAAA,EAMD,oBAAoB,CAC1B,OACqB;AAAA,IACrB,OAAO;AAAA,SACF;AAAA,MACH,SAAS;AAAA,WACJ,MAAM;AAAA,QACT,MAAM,KAAK,YAAY,MAAM,QAAQ,IAAI;AAAA,MAC3C;AAAA,MACA,WAAW;AAAA,WACN,MAAM;AAAA,QACT,MAAM,KAAK,YAAY,MAAM,UAAU,IAAI;AAAA,MAC7C;AAAA,IACF;AAAA;AAAA,OAOc,sBAAqB,CACnC,OACmB;AAAA,IACnB,MAAM,KAAK,mBAAmB;AAAA,IAC9B,MAAM,WAAW,KAAK,qBAAqB,KAAK;AAAA,IAChD,MAAM,UAAU,yBAAyB,QAAQ;AAAA,IACjD,MAAM,WAAW,MAAM,KAAK,KAAK,KAC/B,oBACA,SACA,KAAK,SACP;AAAA,IAEA,IAAI,CAAC,SAAS,SAAS;AAAA,MACrB,MAAM,IAAI,SAAS,6BAA6B;AAAA,QAC9C,SAAS;AAAA,QACT,KAAK;AAAA,MACP,CAAC;AAAA,IACH;AAAA,IAEA,OAAO,oBAAoB,SAAS,QAAQ;AAAA;AAAA,OAGxC,oBAAmB,CACvB,QACqB;AAAA,IAErB,IAAI,OAAO,WAAW;AAAA,MAAG,OAAO,CAAC;AAAA,IAGjC,IAAI,OAAO,SAAS,IAAI;AAAA,MACtB,MAAM,IAAI,gBACR,gEACA,EAAE,KAAK,EAAE,OAAO,OAAO,OAAO,EAAE,CAClC;AAAA,IACF;AAAA,IACA,OAAO,QAAQ,2BAA2B;AAAA,IAC1C,MAAM,KAAK,mBAAmB;AAAA,IAC9B,MAAM,WAAW,OACd,IAAI,CAAC,MAAM,KAAK,qBAAqB,CAAC,CAAC,EACvC,IAAI,wBAAwB;AAAA,IAC/B,MAAM,WAAW,MAAM,KAAK,KAAK,KAM9B,kCAAkC,EAAE,WAAW,SAAS,GAAG,KAAK,SAAS;AAAA,IAE5E,IAAI,CAAC,SAAS,SAAS;AAAA,MACrB,MAAM,IAAI,SAAS,mCAAmC;AAAA,QACpD,SAAS;AAAA,QACT,KAAK;AAAA,MACP,CAAC;AAAA,IACH;AAAA,IAEA,IAAI,CAAC,MAAM,QAAQ,SAAS,SAAS,GAAG;AAAA,MACtC,MAAM,IAAI,SACR,+DACA,EAAE,SAAS,WAAW,KAAK,SAAS,CACtC;AAAA,IACF;AAAA,IAEA,OAAO,SAAS,UAAU,IAAI,mBAAmB;AAAA;AAAA,OAG7C,eAAc,CAAC,gBAA0C;AAAA,IAI7D,MAAM,WAAW,MAAM,KAAK,KAAK,KAC/B,oBACA;AAAA,MACE,UAAU;AAAA,IACZ,GACA,KAAK,SACP;AAAA,IACA,OAAO,SAAS,YAAY;AAAA;AAAA,OAGxB,kBAAiB,CAAC,WAAqC;AAAA,IAC3D,MAAM,WAAW,MAAM,KAAK,KAAK,KAC/B,iCACA,EAAE,UAAU,GACZ,KAAK,SACP;AAAA,IACA,OAAO,SAAS,YAAY;AAAA;AAAA,OAGxB,sBAAqB,CACzB,gBACA,SACkB;AAAA,IAClB,MAAM,KAAK,mBAAmB;AAAA,IAC9B,MAAM,eAAe,KAAK,YAAY,QAAQ,IAAI;AAAA,IAClD,MAAM,WAAW,MAAM,KAAK,KAAK,KAC/B,qCAAqC,mBAAmB,cAAc,KACtE;AAAA,MACE,eAAe,QAAQ;AAAA,MACvB,gBAAgB,QAAQ;AAAA,MACxB,eAAe;AAAA,MACf,kBAAkB,QAAQ;AAAA,MAC1B,wBAAwB,QAAQ;AAAA,MAChC,mBAAmB,QAAQ;AAAA,MAC3B,kBAAkB,QAAQ;AAAA,MAC1B,gBAAgB,QAAQ;AAAA,IAC1B,CACF;AAAA,IACA,OAAO,SAAS;AAAA;AAAA,OAOZ,MAAK,CAAC,gBAAiD;AAAA,IAC3D,MAAM,UAAU,MAAM,KAAK,cAAc,CAAC,cAAc,CAAC;AAAA,IACzD,MAAM,SAAS,QAAQ;AAAA,IACvB,IAAI,CAAC,QAAQ;AAAA,MACX,MAAM,IAAI,SAAS,sBAAsB,EAAE,SAAS,UAAU,CAAC;AAAA,IACjE;AAAA,IACA,OAAO;AAAA;AAAA,OAGH,cAAa,CAAC,iBAAsD;AAAA,IACxE,MAAM,MAAM,gBAAgB,IAAI,kBAAkB,EAAE,KAAK,GAAG;AAAA,IAC5D,MAAM,WAAW,MAAM,KAAK,KAAK,IAC/B,mBAAmB,OACnB,KAAK,SACP;AAAA,IAEA,IAAI,CAAC,SAAS,SAAS;AAAA,MACrB,MAAM,IAAI,SAAS,6BAA6B;AAAA,QAC9C,SAAS;AAAA,QACT,KAAK;AAAA,MACP,CAAC;AAAA,IACH;AAAA,IAEA,IAAI,CAAC,MAAM,QAAQ,SAAS,MAAM,SAAS,GAAG;AAAA,MAC5C,MAAM,IAAI,SAAS,oDAAoD;AAAA,QACrE,SAAS;AAAA,QACT,KAAK;AAAA,MACP,CAAC;AAAA,IACH;AAAA,IAEA,OAAO,SAAS,KAAK,UAAU,IAAI,iBAAiB;AAAA;AAAA,OAGhD,iBAAgB,CAAC,WAA4C;AAAA,IACjE,MAAM,WAAW,MAAM,KAAK,KAAK,IAC/B,0BAA0B,mBAAmB,SAAS,KACtD,KAAK,SACP;AAAA,IAEA,MAAM,WAAW,SAAS,MAAM,YAAY;AAAA,IAC5C,IAAI,CAAC,SAAS,WAAW,CAAC,UAAU;AAAA,MAClC,MAAM,IAAI,SAAS,sBAAsB;AAAA,QACvC,SAAS;AAAA,QACT,KAAK;AAAA,MACP,CAAC;AAAA,IACH;AAAA,IAEA,OAAO,kBAAkB,QAAQ;AAAA;AAAA,OAO7B,SAAQ,CACZ,gBACA,SACiB;AAAA,IAGjB,MAAM,WAAW,MAAM,KAAK,KAAK,IAO/B,0BAA0B,mBAAmB,cAAc,KAC3D,KAAK,SACP;AAAA,IAEA,IAAI,CAAC,SAAS,WAAW,CAAC,SAAS,MAAM,SAAS;AAAA,MAChD,MAAM,MAAM,SAAS,WAAW,SAAS,YAAY;AAAA,MACrD,MAAM,IAAI,SAAS,KAAK,EAAE,SAAS,WAAW,KAAK,SAAS,CAAC;AAAA,IAC/D;AAAA,IAEA,OAAO,SAAS,KAAK;AAAA;AAAA,OAGjB,cAAa,CAAC,iBAA4C;AAAA,IAE9D,MAAM,MAAM,gBAAgB,IAAI,kBAAkB,EAAE,KAAK,GAAG;AAAA,IAC5D,MAAM,WAAW,MAAM,KAAK,KAAK,IAM9B,gCAAgC,OAAO,KAAK,SAAS;AAAA,IAExD,IAAI,CAAC,SAAS,WAAW,CAAC,SAAS,MAAM,cAAc;AAAA,MACrD,MAAM,MACJ,SAAS,WAAW,SAAS,YAAY;AAAA,MAC3C,MAAM,IAAI,SAAS,KAAK,EAAE,SAAS,WAAW,KAAK,SAAS,CAAC;AAAA,IAC/D;AAAA,IAEA,OAAO,SAAS,KAAK;AAAA;AAAA,OAOjB,gBAAe,GAAoB;AAAA,IACvC,MAAM,WAAW,MAAM,KAAK,KAAK,IAC/B,0BACA,KAAK,SACP;AAAA,IAEA,IAAI,CAAC,SAAS,SAAS;AAAA,MACrB,MAAM,IAAI,SAAS,+BAA+B;AAAA,QAChD,SAAS;AAAA,QACT,KAAK;AAAA,MACP,CAAC;AAAA,IACH;AAAA,IAEA,OAAO,SAAS,KAAK,OAAO,IAAI,OAAO;AAAA;AAAA,OAGnC,aAAY,CAAC,OAAe,MAAmC;AAAA,IACnE,MAAM,WAAW,MAAM,KAAK,KAAK,IAQ9B,eAAe,MAAM;AAAA,IAGxB,IAAI,CAAC,SAAS,WAAW,SAAS,SAAS,CAAC,SAAS,MAAM,OAAO;AAAA,MAChE,MAAM,MAAM,SAAS,WAAW;AAAA,MAChC,MAAM,IAAI,SAAS,KAAK,EAAE,SAAS,WAAW,KAAK,SAAS,CAAC;AAAA,IAC/D;AAAA,IAGA,OAAO,OAAO,QAAQ,SAAS,KAAK,KAAK,EAAE,IAAI,EAAE,IAAI,YAAY;AAAA,MAC/D;AAAA,MACA;AAAA,IACF,EAAE;AAAA;AAAA,OAGE,aAAY,CAAC,OAAuC;AAAA,IACxD,sBAAsB,KAAK;AAAA,IAC3B,MAAM,KAAK,mBAAmB;AAAA,IAC9B,MAAM,gBAAgB,KAAK,OAAO,MAAM,KAAK,YAAY,MAAM,IAAI,EAAE;AAAA,IACrE,MAAM,UAAU,iBAAiB,aAAa;AAAA,IAC9C,MAAM,WAAW,MAAM,KAAK,KAAK,KAC/B,0BACA,SACA,KAAK,SACP;AAAA,IAEA,IAAI,CAAC,SAAS,SAAS;AAAA,MACrB,MAAM,IAAI,SAAS,2BAA2B;AAAA,QAC5C,SAAS;AAAA,QACT,KAAK;AAAA,MACP,CAAC;AAAA,IACH;AAAA,IAEA,OAAO,kBAAkB,SAAS,IAAI;AAAA;AAAA,OAGlC,aAAY,CAAC,UAA6C;AAAA,IAC9D,MAAM,WAAW,MAAM,KAAK,KAAK,KAC/B,0BACA,EAAE,gBAAgB,OAAO,QAAQ,EAAE,GACnC,KAAK,SACP;AAAA,IACA,OAAO,SAAS,YAAY;AAAA;AAAA,OAGxB,kBAAiB,GAAsB;AAAA,IAG3C,MAAM,WAAW,MAAM,KAAK,KAAK,IAG9B,wBAAwB,KAAK,SAAS;AAAA,IAEzC,IAAI,CAAC,SAAS,SAAS;AAAA,MACrB,MAAM,IAAI,SAAS,iCAAiC;AAAA,QAClD,SAAS;AAAA,QACT,KAAK;AAAA,MACP,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,OAAO,SAAS,MAAM,gBAAgB;AAAA,IAC5C,IAAI,CAAC,MAAM,QAAQ,IAAI,GAAG;AAAA,MACxB,MAAM,IAAI,SACR,oEACA,EAAE,SAAS,WAAW,KAAK,SAAS,CACtC;AAAA,IACF;AAAA,IAEA,OAAO,KAAK,IAAI,iBAAiB;AAAA;AAAA,OAO7B,UAAS,GAAoB;AAAA,IACjC,MAAM,WAAW,MAAM,KAAK,KAAK,IAA2B,SAAS;AAAA,IAErE,IAAI,CAAC,SAAS,SAAS;AAAA,MACrB,MAAM,IAAI,SAAS,wBAAwB;AAAA,QACzC,SAAS;AAAA,QACT,KAAK;AAAA,MACP,CAAC;AAAA,IACH;AAAA,IAEA,OAAO,SAAS,KAAK,OAAO,IAAI,OAAO;AAAA;AAAA,OAGnC,oBAAmB,GASvB;AAAA,IAGA,MAAM,WAAW,MAAM,KAAK,KAAK,IAY9B,oBAAoB;AAAA,IAEvB,IAAI,CAAC,SAAS,SAAS;AAAA,MACrB,MAAM,IAAI,SAAS,mCAAmC;AAAA,QACpD,SAAS;AAAA,QACT,KAAK;AAAA,MACP,CAAC;AAAA,IACH;AAAA,IAEA,OAAO,SAAS,KAAK,IAAI,CAAC,SAAS;AAAA,MACjC,IAAI,IAAI;AAAA,MACR,MAAM,IAAI;AAAA,MACV,SAAS,IAAI;AAAA,MACb,MAAM,IAAI;AAAA,MACV,UAAU,IAAI,gBAAgB;AAAA,MAC9B,WAAW,IAAI,gBAAgB;AAAA,IACjC,EAAE;AAAA;AAAA,OAOE,sBAAqB,CACzB,SAC0B;AAAA,IAC1B,MAAM,UAAU,0BAA0B,OAAO;AAAA,IACjD,MAAM,WAAW,MAAM,KAAK,KAAK,KAC/B,mBACA,SACA,KAAK,SACP;AAAA,IAEA,IAAI,CAAC,SAAS,WAAW,CAAC,SAAS,MAAM,SAAS;AAAA,MAChD,MAAM,IAAI,SAAS,4BAA4B;AAAA,QAC7C,SAAS;AAAA,QACT,KAAK;AAAA,MACP,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,OAAO,SAAS,KAAK;AAAA,IAC3B,OAAO;AAAA,MACL,IAAI,KAAK;AAAA,MACT,OAAO,KAAK;AAAA,MACZ,MAAM,KAAK;AAAA,MACX,OAAO,KAAK;AAAA,MACZ,OAAO,KAAK;AAAA,MACZ,MAAM,KAAK;AAAA,MACX,SAAS,KAAK;AAAA,MACd,eAAe,KAAK;AAAA,MACpB,YAAY,KAAK;AAAA,MACjB,aAAa,KAAK;AAAA,MAClB,aAAa,KAAK;AAAA,IACpB;AAAA;AAAA,OAGI,qBAAoB,GAA+B;AAAA,IACvD,MAAM,WAAW,MAAM,KAAK,KAAK,IAG9B,iBAAiB,KAAK,SAAS;AAAA,IAElC,IAAI,CAAC,SAAS,SAAS;AAAA,MACrB,MAAM,IAAI,SAAS,oCAAoC;AAAA,QACrD,SAAS;AAAA,QACT,KAAK;AAAA,MACP,CAAC;AAAA,IACH;AAAA,IAEA,IAAI,CAAC,MAAM,QAAQ,SAAS,MAAM,OAAO,GAAG;AAAA,MAC1C,MAAM,IAAI,SACR,yDACA,EAAE,SAAS,WAAW,KAAK,SAAS,CACtC;AAAA,IACF;AAAA,IAEA,OAAO,SAAS,KAAK,QAAQ,IAAI,CAAC,UAAU;AAAA,MAC1C,IAAI,KAAK;AAAA,MACT,OAAO,KAAK;AAAA,MACZ,MAAM,KAAK;AAAA,MACX,OAAO,KAAK;AAAA,MACZ,OAAO,KAAK;AAAA,MACZ,MAAM,KAAK;AAAA,MACX,SAAS,KAAK;AAAA,MACd,eAAe,KAAK;AAAA,MACpB,YAAY,KAAK;AAAA,MACjB,aAAa,KAAK;AAAA,MAClB,aAAa,KAAK;AAAA,IACpB,EAAE;AAAA;AAAA,OAGE,sBAAqB,CACzB,IACA,SAC0B;AAAA,IAC1B,MAAM,WAAW,MAAM,KAAK,KAAK,IAC/B,mBACA;AAAA,MACE;AAAA,MACA,OAAO,QAAQ;AAAA,MACf,MAAM,QAAQ;AAAA,MACd,OAAO,QAAQ;AAAA,MACf,MAAM,QAAQ;AAAA,MACd,SAAS,QAAQ;AAAA,MACjB,eAAe,QAAQ;AAAA,MACvB,UAAU,QAAQ;AAAA,MAClB,OAAO,QAAQ;AAAA,MACf,aAAa,QAAQ;AAAA,IACvB,GACA,KAAK,SACP;AAAA,IAEA,IAAI,CAAC,SAAS,WAAW,CAAC,SAAS,MAAM,SAAS;AAAA,MAChD,MAAM,IAAI,SAAS,4BAA4B;AAAA,QAC7C,SAAS;AAAA,QACT,KAAK;AAAA,MACP,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,OAAO,SAAS,KAAK;AAAA,IAC3B,OAAO;AAAA,MACL,IAAI,KAAK;AAAA,MACT,OAAO,KAAK;AAAA,MACZ,MAAM,KAAK;AAAA,MACX,OAAO,KAAK;AAAA,MACZ,OAAO,KAAK;AAAA,MACZ,MAAM,KAAK;AAAA,MACX,SAAS,KAAK;AAAA,MACd,eAAe,KAAK;AAAA,MACpB,YAAY,KAAK;AAAA,MACjB,aAAa,KAAK;AAAA,MAClB,aAAa,KAAK;AAAA,IACpB;AAAA;AAAA,OAGI,sBAAqB,CAAC,IAA8B;AAAA,IAExD,MAAM,WAAW,MAAM,KAAK,KAAK,OAC/B,mBACA,EAAE,MAAM,EAAE,GAAG,GAAG,gBAAgB,sBAAsB,CACxD;AAAA,IACA,OAAO,SAAS;AAAA;AAAA,EAOlB,YAAY,CACV,SACA,SAKc;AAAA,IACd,OAAO,oBAAoB,SAAS,OAAO;AAAA;AAAA,EAO7C,QAAQ,GAAmB;AAAA,IACzB,MAAM,IAAI,0BAA0B,WAAW,UAAU;AAAA;AAE7D;",
|
|
10
|
+
"debugId": "52369388ABAA8E1964756E2164756E21",
|
|
11
11
|
"names": []
|
|
12
12
|
}
|
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
import type { City, CreateShipmentInput, CustomerAddress, Pickup, PickupRequest, Shipment, ShipmentStatus, TrackingEvent, TrackingResult, WebhookConfig, WebhookEvent } from "../../core/types.js";
|
|
6
6
|
import type { AymakanAddressRequest, AymakanCity, AymakanCreateShipmentRequest, AymakanPickupRequest, AymakanPickupResponse, AymakanShipmentResponse, AymakanTrackingEvent, AymakanTrackShipmentData } from "./types.js";
|
|
7
7
|
export declare function mapAymakanStatus(statusCode: string): ShipmentStatus | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Map a human-readable Aymakan status label to a unified status.
|
|
10
|
+
* Returns undefined for unrecognized labels.
|
|
11
|
+
*/
|
|
12
|
+
export declare function mapAymakanStatusLabel(label: string): ShipmentStatus | undefined;
|
|
8
13
|
export declare function mapCreateShipmentRequest(input: CreateShipmentInput): AymakanCreateShipmentRequest;
|
|
9
14
|
export declare function mapPickupRequest(input: PickupRequest): AymakanPickupRequest;
|
|
10
15
|
export declare function mapCustomerAddressRequest(addr: CustomerAddress): AymakanAddressRequest;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mappers.d.ts","sourceRoot":"","sources":["../../../src/carriers/aymakan/mappers.ts"],"names":[],"mappings":"AACA;;;GAGG;AAGH,OAAO,KAAK,EAEV,IAAI,EACJ,mBAAmB,EACnB,eAAe,EACf,MAAM,EACN,aAAa,EACb,QAAQ,EACR,cAAc,EACd,aAAa,EACb,cAAc,EACd,aAAa,EACb,YAAY,EACb,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EACV,qBAAqB,EACrB,WAAW,EACX,4BAA4B,EAE5B,oBAAoB,EACpB,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,wBAAwB,EAEzB,MAAM,SAAS,CAAC;AAMjB,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAI/E;AAuCD,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,mBAAmB,GACzB,4BAA4B,
|
|
1
|
+
{"version":3,"file":"mappers.d.ts","sourceRoot":"","sources":["../../../src/carriers/aymakan/mappers.ts"],"names":[],"mappings":"AACA;;;GAGG;AAGH,OAAO,KAAK,EAEV,IAAI,EACJ,mBAAmB,EACnB,eAAe,EACf,MAAM,EACN,aAAa,EACb,QAAQ,EACR,cAAc,EACd,aAAa,EACb,cAAc,EACd,aAAa,EACb,YAAY,EACb,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EACV,qBAAqB,EACrB,WAAW,EACX,4BAA4B,EAE5B,oBAAoB,EACpB,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,wBAAwB,EAEzB,MAAM,SAAS,CAAC;AAMjB,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAI/E;AAkCD;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,GACZ,cAAc,GAAG,SAAS,CAE5B;AAuCD,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,mBAAmB,GACzB,4BAA4B,CAqF9B;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,aAAa,GAAG,oBAAoB,CAW3E;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,eAAe,GACpB,qBAAqB,CAYvB;AAMD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,uBAAuB,GAAG,QAAQ,CA8B3E;AAeD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,oBAAoB,GAAG,aAAa,CAQ3E;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,wBAAwB,GAC7B,cAAc,CA+ChB;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAK/C;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,qBAAqB,CAAC,MAAM,CAAC,GAAG,MAAM,CAqB7E;AAsBD,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE;IACR,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB,GACA,YAAY,CAsEd"}
|