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
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ShipFlow
|
|
2
2
|
|
|
3
|
-
Unified Shipping SDK for MENA region carriers. A single API to create shipments, track packages, manage labels, handle webhooks, and more — across Aymakan, SMSA Express, and future carriers.
|
|
3
|
+
Unified Shipping SDK for MENA region carriers. A single API to create shipments, track packages, manage labels, handle webhooks, and more — across Aymakan, SMSA Express, Aramex, and future carriers.
|
|
4
4
|
|
|
5
5
|
Think EasyPost / Shippo, but purpose-built for Saudi Arabia and the GCC.
|
|
6
6
|
|
|
@@ -8,9 +8,10 @@ Think EasyPost / Shippo, but purpose-built for Saudi Arabia and the GCC.
|
|
|
8
8
|
|
|
9
9
|
- **Unified types** — one `CreateShipmentInput`, one `TrackingResult`, one `WebhookEvent`, regardless of carrier
|
|
10
10
|
- **Tree-shakeable** — only the carriers you import are bundled
|
|
11
|
-
- **Auto-validation** —
|
|
11
|
+
- **Auto-validation** — Valibot schemas validate every `createShipment()` call before it hits the network
|
|
12
12
|
- **Webhook parsing** — normalize incoming carrier webhooks into a single event format
|
|
13
|
-
- **
|
|
13
|
+
- **Smart retries** — dependency-free retry with jittered backoff that honors carrier `Retry-After` on 429/503, surfacing a `RateLimitError` when the wait is too long to absorb inline
|
|
14
|
+
- **Minimal dependencies** — only [Valibot](https://github.com/fabian-hiller/valibot) for validation; uses the runtime's global `fetch` (Node 20+, Deno, Bun, edge/workers), no axios/node-fetch
|
|
14
15
|
- **TypeScript-first** — strict types, no `any`
|
|
15
16
|
|
|
16
17
|
## Installation
|
|
@@ -25,6 +26,7 @@ bun add shipflow
|
|
|
25
26
|
import { ShipFlow } from "shipflow";
|
|
26
27
|
import { AymakanAdapter, AymakanService } from "shipflow/carriers/aymakan";
|
|
27
28
|
import { SMSAExpressAdapter, SMSAService } from "shipflow/carriers/smsaexpress";
|
|
29
|
+
import { AramexAdapter } from "shipflow/carriers/aramex";
|
|
28
30
|
|
|
29
31
|
const client = new ShipFlow({
|
|
30
32
|
adapters: [
|
|
@@ -36,6 +38,18 @@ const client = new ShipFlow({
|
|
|
36
38
|
mode: "sandbox",
|
|
37
39
|
credentials: { apiKey: process.env.SMSA_API_KEY! },
|
|
38
40
|
}),
|
|
41
|
+
// Aramex auth is a ClientInfo object sent in every request body (no API key)
|
|
42
|
+
new AramexAdapter({
|
|
43
|
+
mode: "sandbox",
|
|
44
|
+
credentials: {
|
|
45
|
+
userName: process.env.ARAMEX_USERNAME!,
|
|
46
|
+
password: process.env.ARAMEX_PASSWORD!,
|
|
47
|
+
accountNumber: process.env.ARAMEX_ACCOUNT_NUMBER!,
|
|
48
|
+
accountPin: process.env.ARAMEX_ACCOUNT_PIN!,
|
|
49
|
+
accountEntity: process.env.ARAMEX_ACCOUNT_ENTITY!, // e.g. "RUH"
|
|
50
|
+
accountCountryCode: process.env.ARAMEX_ACCOUNT_COUNTRY_CODE!, // e.g. "SA"
|
|
51
|
+
},
|
|
52
|
+
}),
|
|
39
53
|
],
|
|
40
54
|
});
|
|
41
55
|
|
|
@@ -91,25 +105,26 @@ Every carrier adapter implements these **required** methods:
|
|
|
91
105
|
|
|
92
106
|
Plus these **optional** methods (availability varies by carrier):
|
|
93
107
|
|
|
94
|
-
| Method | Aymakan | SMSA |
|
|
95
|
-
| ------------------------------------ | ------- | ---- |
|
|
96
|
-
| `createBulkShipments(inputs)` | ✅ | — |
|
|
97
|
-
| `cancelByReference(ref)` | ✅ | — |
|
|
98
|
-
| `updateDeliveryAddress(tn, address)` | ✅ | — |
|
|
99
|
-
| `trackByReference(ref)` | ✅ | ✅ |
|
|
100
|
-
| `getBulkLabels(trackingNumbers)` | ✅ | — |
|
|
101
|
-
| `getPickupCities()` | ✅ | — |
|
|
102
|
-
| `getTimeSlots(city, date)` | ✅ | — |
|
|
103
|
-
| `createPickup(input)` | ✅ | — |
|
|
104
|
-
| `cancelPickup(id)` | ✅ | — |
|
|
105
|
-
| `getPickupRequests()` | ✅ | — |
|
|
106
|
-
| `getCities()` | ✅ | ✅ |
|
|
107
|
-
| `getDropoffLocations()` | ✅ | ✅ |
|
|
108
|
-
| `createCustomerAddress(addr)` | ✅ | — |
|
|
109
|
-
| `getCustomerAddresses()` | ✅ | — |
|
|
110
|
-
| `updateCustomerAddress(id, addr)` | ✅ | — |
|
|
111
|
-
| `deleteCustomerAddress(id)` | ✅ | — |
|
|
112
|
-
| `
|
|
108
|
+
| Method | Aymakan | SMSA | Aramex |
|
|
109
|
+
| ------------------------------------ | ------- | ---- | ------ |
|
|
110
|
+
| `createBulkShipments(inputs)` | ✅ | — | ✅ |
|
|
111
|
+
| `cancelByReference(ref)` | ✅ | — | — |
|
|
112
|
+
| `updateDeliveryAddress(tn, address)` | ✅ | — | — |
|
|
113
|
+
| `trackByReference(ref)` | ✅ | ✅ | ✅ |
|
|
114
|
+
| `getBulkLabels(trackingNumbers)` | ✅ | — | — |
|
|
115
|
+
| `getPickupCities()` | ✅ | — | — |
|
|
116
|
+
| `getTimeSlots(city, date)` | ✅ | — | — |
|
|
117
|
+
| `createPickup(input)` | ✅ | — | ✅ |
|
|
118
|
+
| `cancelPickup(id)` | ✅ | — | ✅ |
|
|
119
|
+
| `getPickupRequests()` | ✅ | — | — |
|
|
120
|
+
| `getCities()` | ✅ | ✅ | ✅ |
|
|
121
|
+
| `getDropoffLocations()` | ✅ | ✅ | ✅ |
|
|
122
|
+
| `createCustomerAddress(addr)` | ✅ | — | — |
|
|
123
|
+
| `getCustomerAddresses()` | ✅ | — | — |
|
|
124
|
+
| `updateCustomerAddress(id, addr)` | ✅ | — | — |
|
|
125
|
+
| `deleteCustomerAddress(id)` | ✅ | — | — |
|
|
126
|
+
| `getRates(input)` | — | — | ✅ |
|
|
127
|
+
| `parseWebhook(payload, options)` | ✅ | ✅ | — |
|
|
113
128
|
|
|
114
129
|
SMSA-specific methods:
|
|
115
130
|
|
|
@@ -123,19 +138,112 @@ SMSA-specific methods:
|
|
|
123
138
|
|
|
124
139
|
## Carrier Support
|
|
125
140
|
|
|
126
|
-
| Feature | Aymakan | SMSA Express |
|
|
127
|
-
| ----------------- | ------------------------------- | ---------------------------------- |
|
|
128
|
-
| Countries | SA, AE, BH, KW, OM, QA | SA, AE, BH, EG, KW, OM, QA, JO |
|
|
129
|
-
| Service types | 10 (ONP, SDD, RVP, EXH, ...) | 3 (EDDL, EDEL, EDCR) |
|
|
130
|
-
| Shipment creation | Single + Bulk | B2C + C2B + 2-Way |
|
|
131
|
-
| COD | ✅ | ✅ (B2C only) |
|
|
132
|
-
| Cancellation | By tracking # or reference | C2B only |
|
|
133
|
-
| Tracking | Single, bulk, by reference | Single, bulk, by reference |
|
|
134
|
-
| Labels | PDF/PNG, single + bulk | PDF/ZPL |
|
|
135
|
-
| Pickups | Full lifecycle | — |
|
|
136
|
-
| Webhooks | ✅ (with auth verification) | ✅ (batch, with auth verification) |
|
|
137
|
-
| City resolution | Arabic ↔ English smart matching | Code-based lookup |
|
|
138
|
-
| Rates | ❌ | ❌ |
|
|
141
|
+
| Feature | Aymakan | SMSA Express | Aramex |
|
|
142
|
+
| ----------------- | ------------------------------- | ---------------------------------- | -------------------------------------- |
|
|
143
|
+
| Countries | SA, AE, BH, KW, OM, QA | SA, AE, BH, EG, KW, OM, QA, JO | SA, AE, BH, KW, OM, QA, JO, EG, LB, IQ |
|
|
144
|
+
| Service types | 10 (ONP, SDD, RVP, EXH, ...) | 3 (EDDL, EDEL, EDCR) | 10 product types (OND, PPX, EPX, ...) |
|
|
145
|
+
| Shipment creation | Single + Bulk | B2C + C2B + 2-Way | Single + Bulk (native batch) |
|
|
146
|
+
| COD | ✅ | ✅ (B2C only) | ✅ |
|
|
147
|
+
| Cancellation | By tracking # or reference | C2B only | Pickups only (no shipment cancel API) |
|
|
148
|
+
| Tracking | Single, bulk, by reference | Single, bulk, by reference | Single, bulk, by reference |
|
|
149
|
+
| Labels | PDF/PNG, single + bulk | PDF/ZPL | URL (HTML/PDF) |
|
|
150
|
+
| Pickups | Full lifecycle | — | Create + cancel |
|
|
151
|
+
| Webhooks | ✅ (with auth verification) | ✅ (batch, with auth verification) | — (poll via tracking) |
|
|
152
|
+
| City resolution | Arabic ↔ English smart matching | Code-based lookup | Name list (FetchCities / FetchOffices) |
|
|
153
|
+
| Rates | ❌ | ❌ | ✅ (CalculateRate) |
|
|
154
|
+
|
|
155
|
+
## Aramex
|
|
156
|
+
|
|
157
|
+
Aramex is integrated via the **JSON flavor of the classic `ShippingAPI.V2` services**. A few
|
|
158
|
+
things make it different from the other carriers:
|
|
159
|
+
|
|
160
|
+
- **Auth is a `ClientInfo` object in every request body** (no API key / header, no token
|
|
161
|
+
exchange). Pass `userName`, `password`, `accountNumber`, `accountPin`, `accountEntity` (the
|
|
162
|
+
3-letter origin office, e.g. `RUH`/`DXB`/`AMM`) and `accountCountryCode`.
|
|
163
|
+
- **Four independent services on separate hosts** — Shipping, Tracking, RateCalculator, and
|
|
164
|
+
Location. The adapter holds one HTTP client per service and routes automatically. If your
|
|
165
|
+
account provisions the Location service on a different host (some WSDLs use `anfe02.aramex.com`),
|
|
166
|
+
set `locationBaseUrl` on the config.
|
|
167
|
+
- **"Fake 200 OK" errors** — Aramex returns HTTP 200 even on logical failures, with
|
|
168
|
+
`HasErrors: true` + `Notifications[]`. ShipFlow surfaces these as `APIError`, including
|
|
169
|
+
per-shipment errors inside an otherwise-clean `CreateShipments` batch. Throttling
|
|
170
|
+
notifications in that envelope are surfaced as `RateLimitError` so retries back off.
|
|
171
|
+
- **Rates are supported** (`getRates` → `CalculateRate`), unlike Aymakan/SMSA.
|
|
172
|
+
- **`cancelShipment` is unsupported** (the classic API has no shipment-cancel operation) and
|
|
173
|
+
throws `UnsupportedOperationError`. Pickups can be cancelled via `cancelPickup`.
|
|
174
|
+
- **Labels resolve to a URL** — the `format` argument of `getLabel` can't be honored.
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { AramexAdapter, AramexProductType } from "shipflow/carriers/aramex";
|
|
178
|
+
|
|
179
|
+
const aramex = client.carrier("aramex");
|
|
180
|
+
|
|
181
|
+
// Create a domestic COD shipment (freight prepaid, cash collected on delivery)
|
|
182
|
+
const shipment = await aramex.createShipment({
|
|
183
|
+
shipper: {
|
|
184
|
+
name: "My Store",
|
|
185
|
+
company: "ShipFlow",
|
|
186
|
+
phone: "966500000000",
|
|
187
|
+
line1: "King Fahd Road",
|
|
188
|
+
city: "Riyadh",
|
|
189
|
+
countryCode: "SA",
|
|
190
|
+
},
|
|
191
|
+
consignee: {
|
|
192
|
+
name: "Customer",
|
|
193
|
+
phone: "966500000001",
|
|
194
|
+
line1: "Prince Sultan Road",
|
|
195
|
+
city: "Jeddah",
|
|
196
|
+
countryCode: "SA",
|
|
197
|
+
},
|
|
198
|
+
parcels: [{ weight: { value: 1, unit: "kg" }, pieces: 1 }],
|
|
199
|
+
cod: { enabled: true, amount: 150, currency: "SAR" },
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Quote a rate, then track
|
|
203
|
+
const rates = await aramex.getRates!(input);
|
|
204
|
+
const result = await aramex.track(shipment.trackingNumber);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Product group / type & payment** — ShipFlow infers `DOM` (domestic) when shipper and consignee
|
|
208
|
+
share a country, else `EXP`, and picks a sensible default product type (`OND` for domestic, `EPX`
|
|
209
|
+
for express). Override with `serviceType` (a valid Aramex code) or
|
|
210
|
+
`options.metadata.productGroup` / `productType`.
|
|
211
|
+
|
|
212
|
+
The freight **`PaymentType`** — who pays the *shipping cost* — defaults to `P` and is independent
|
|
213
|
+
of COD: enabling COD adds the `CODS` service and the cash amount to collect from the consignee, but
|
|
214
|
+
does **not** charge them freight. Override the freight payer with `options.metadata.paymentType`:
|
|
215
|
+
|
|
216
|
+
| Value | Freight billed to | When to use |
|
|
217
|
+
| ----- | ----------------- | ----------- |
|
|
218
|
+
| `"P"` _(default)_ | Shipper's Aramex account (prepaid) | Standard KSA/GCC e-commerce — merchant pays shipping, even with COD |
|
|
219
|
+
| `"C"` | Consignee, collected at delivery | Customer pays shipping on top of any COD |
|
|
220
|
+
| `"3"` | A third-party account | Freight billed to someone other than shipper/consignee |
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
// Default: COD shipment with prepaid freight (PaymentType "P")
|
|
224
|
+
await aramex.createShipment({
|
|
225
|
+
...input,
|
|
226
|
+
cod: { enabled: true, amount: 150, currency: "SAR" }, // freight stays "P"
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Override: charge the customer freight at the door (PaymentType "C")
|
|
230
|
+
await aramex.createShipment({
|
|
231
|
+
...input,
|
|
232
|
+
cod: { enabled: true, amount: 150, currency: "SAR" },
|
|
233
|
+
options: { metadata: { paymentType: "C" } },
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Override: bill freight to a third party (PaymentType "3")
|
|
237
|
+
await aramex.createShipment({
|
|
238
|
+
...input,
|
|
239
|
+
options: { metadata: { paymentType: "3" } },
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
> Aramex does not push webhooks — poll `track()` / `trackMultiple()` for status updates.
|
|
244
|
+
> Tracking `UpdateCode`s vary by region and aren't fully published, so ShipFlow maps known codes
|
|
245
|
+
> and falls back to a description-keyword heuristic (then `"unknown"`) — an unmapped code never
|
|
246
|
+
> breaks tracking.
|
|
139
247
|
|
|
140
248
|
## Webhook Handling
|
|
141
249
|
|
|
@@ -202,7 +310,7 @@ interface WebhookEvent {
|
|
|
202
310
|
|
|
203
311
|
## Input Validation
|
|
204
312
|
|
|
205
|
-
All `createShipment()` calls are **automatically validated** using
|
|
313
|
+
All `createShipment()` calls are **automatically validated** using Valibot schemas before hitting the carrier API. Invalid input throws a `ValidationError` with field-level details:
|
|
206
314
|
|
|
207
315
|
```typescript
|
|
208
316
|
try {
|
|
@@ -234,7 +342,7 @@ validateCreateShipmentInput(input); // throws ValidationError or returns validat
|
|
|
234
342
|
validatePickupRequest(pickupInput); // same pattern
|
|
235
343
|
```
|
|
236
344
|
|
|
237
|
-
Exported
|
|
345
|
+
Exported Valibot schemas for advanced use (custom refinements, partial validation, etc.):
|
|
238
346
|
|
|
239
347
|
```typescript
|
|
240
348
|
import {
|
|
@@ -253,6 +361,7 @@ All errors extend `ShipFlowError` for easy catch-all handling:
|
|
|
253
361
|
import {
|
|
254
362
|
ShipFlowError,
|
|
255
363
|
NetworkError,
|
|
364
|
+
RateLimitError,
|
|
256
365
|
APIError,
|
|
257
366
|
ValidationError,
|
|
258
367
|
AuthenticationError,
|
|
@@ -267,6 +376,8 @@ try {
|
|
|
267
376
|
// Bad input — check error.issues
|
|
268
377
|
} else if (error instanceof AuthenticationError) {
|
|
269
378
|
// Invalid API key
|
|
379
|
+
} else if (error instanceof RateLimitError) {
|
|
380
|
+
// Rate limited — check error.retryAfterMs (ms to wait), reschedule if set
|
|
270
381
|
} else if (error instanceof APIError) {
|
|
271
382
|
// Carrier returned an error — check error.statusCode, error.errors
|
|
272
383
|
} else if (error instanceof NetworkError) {
|
|
@@ -275,6 +386,37 @@ try {
|
|
|
275
386
|
}
|
|
276
387
|
```
|
|
277
388
|
|
|
389
|
+
> `RateLimitError extends APIError`, so check it **before** `APIError` in your
|
|
390
|
+
> `if`/`else` chain (a plain `catch (e) { if (e instanceof APIError) }` still
|
|
391
|
+
> catches it).
|
|
392
|
+
|
|
393
|
+
### Retries & rate limiting
|
|
394
|
+
|
|
395
|
+
Safe, idempotent requests (GETs, plus tracking endpoints opted in by the
|
|
396
|
+
adapters) are retried automatically with **jittered exponential backoff**.
|
|
397
|
+
Mutating requests (create/cancel) are **not** retried by default, so a timed-out
|
|
398
|
+
`createShipment` never risks a duplicate on the carrier.
|
|
399
|
+
|
|
400
|
+
When a carrier replies `429`/`503` with a `Retry-After` header, ShipFlow:
|
|
401
|
+
|
|
402
|
+
- **honors it inline** if the wait is within the inline cap (15s by default),
|
|
403
|
+
sleeping out the window (plus a little jitter) before retrying; otherwise
|
|
404
|
+
- **stops and throws `RateLimitError`** carrying `retryAfterMs`, so a durable
|
|
405
|
+
queue/worker can reschedule instead of blocking the request for minutes.
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
try {
|
|
409
|
+
await client.carrier("aymakan").track(trackingNumber);
|
|
410
|
+
} catch (error) {
|
|
411
|
+
if (error instanceof RateLimitError && error.retryAfterMs != null) {
|
|
412
|
+
await scheduleRetryIn(error.retryAfterMs); // your queue/worker
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Aramex reports throttling inside its "fake 200" envelope rather than via HTTP
|
|
418
|
+
429; ShipFlow detects that and raises the same `RateLimitError`.
|
|
419
|
+
|
|
278
420
|
## Custom Adapters
|
|
279
421
|
|
|
280
422
|
Implement the `CarrierAdapter` interface or extend `BaseCarrierAdapter`:
|
|
@@ -51,6 +51,13 @@ export declare class AramexAdapter extends BaseCarrierAdapter {
|
|
|
51
51
|
* Extracts the Aramex "Fake 200 OK" error (envelope-level `HasErrors` +
|
|
52
52
|
* `Notifications`). Passed to every request so the HttpClient raises APIError.
|
|
53
53
|
*/
|
|
54
|
+
/**
|
|
55
|
+
* Aramex reports throttling inside its "fake 200" envelope (and sometimes on a
|
|
56
|
+
* non-429 status) rather than via HTTP 429, so status-code detection misses it.
|
|
57
|
+
* Match the rate-limit wording in the notifications and flag it so the
|
|
58
|
+
* HttpClient raises a retryable `RateLimitError` instead of a plain APIError.
|
|
59
|
+
*/
|
|
60
|
+
private static readonly RATE_LIMIT_PATTERN;
|
|
54
61
|
private static aramexErrorExtractor;
|
|
55
62
|
private static notificationsToErrors;
|
|
56
63
|
protected executeCreateShipment(input: CreateShipmentInput): Promise<Shipment>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/carriers/aramex/adapter.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;GAWG;AAYH,OAAO,KAAK,EACV,aAAa,EACb,IAAI,EACJ,mBAAmB,EACnB,QAAQ,EACR,MAAM,EACN,aAAa,EACb,IAAI,EACJ,QAAQ,EACR,cAAc,EACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/carriers/aramex/adapter.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;GAWG;AAYH,OAAO,KAAK,EACV,aAAa,EACb,IAAI,EACJ,mBAAmB,EACnB,QAAQ,EACR,MAAM,EACN,aAAa,EACb,IAAI,EACJ,QAAQ,EACR,cAAc,EACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAsD7C,MAAM,WAAW,YAAa,SAAQ,aAAa;IACjD,WAAW,EAAE;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC;QACnB,aAAa,EAAE,MAAM,CAAC;QACtB,kBAAkB,EAAE,MAAM,CAAC;KAC5B,CAAC;IACF,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,aAAc,SAAQ,kBAAkB;IACnD,QAAQ,CAAC,IAAI,YAAY;IACzB,QAAQ,CAAC,kBAAkB,WAWzB;IAEF,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,YAAY,CAAa;gBAErB,MAAM,EAAE,YAAY;IAuBhC,SAAS,CAAC,UAAU,IAAI,MAAM;IAM9B,8EAA8E;IAC9E,OAAO,CAAC,WAAW;IAInB,OAAO,KAAK,YAAY,GAEvB;IAED,OAAO,CAAC,eAAe;IAQvB;;;OAGG;IACH;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CACe;IAEzD,OAAO,CAAC,MAAM,CAAC,oBAAoB;IA4BnC,OAAO,CAAC,MAAM,CAAC,qBAAqB;cAcpB,qBAAqB,CACnC,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,QAAQ,CAAC;IA6CpB;;;;;;;;OAQG;IACG,mBAAmB,CACvB,MAAM,EAAE,mBAAmB,EAAE,GAC5B,OAAO,CAAC,QAAQ,EAAE,CAAC;IAwDtB;;;OAGG;IACH,cAAc,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQnD,QAAQ,CACZ,cAAc,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAC9B,OAAO,CAAC,MAAM,CAAC;IA8BZ,KAAK,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAetD,aAAa,CAAC,eAAe,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IA4BnE,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAU5D,QAAQ,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IA4BrD,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;IAyDnD,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAoBzD,SAAS,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAehD,mBAAmB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;CAcrE"}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
ValidationError,
|
|
7
7
|
validateCreateShipmentInput,
|
|
8
8
|
validatePickupRequest
|
|
9
|
-
} from "../../index-
|
|
9
|
+
} from "../../index-qnxj8bct.js";
|
|
10
10
|
|
|
11
11
|
// src/carriers/aramex/services.ts
|
|
12
12
|
var AramexProductGroup = {
|
|
@@ -119,7 +119,7 @@ function resolvePaymentType(input) {
|
|
|
119
119
|
const meta = getMeta(input, "paymentType");
|
|
120
120
|
if (meta === "P" || meta === "C" || meta === "3")
|
|
121
121
|
return meta;
|
|
122
|
-
return
|
|
122
|
+
return "P";
|
|
123
123
|
}
|
|
124
124
|
function aggregateWeight(input) {
|
|
125
125
|
const allLb = input.parcels.every((p) => p.weight.unit === "lb");
|
|
@@ -141,19 +141,31 @@ function mapDimensions(dims) {
|
|
|
141
141
|
Unit: "CM"
|
|
142
142
|
};
|
|
143
143
|
}
|
|
144
|
-
function
|
|
144
|
+
function buildPartyAddress(fields) {
|
|
145
145
|
return {
|
|
146
|
-
Line1:
|
|
147
|
-
Line2:
|
|
148
|
-
Line3:
|
|
149
|
-
City:
|
|
150
|
-
StateOrProvinceCode:
|
|
151
|
-
PostCode:
|
|
152
|
-
CountryCode:
|
|
153
|
-
Longitude:
|
|
154
|
-
Latitude:
|
|
146
|
+
Line1: fields.line1,
|
|
147
|
+
Line2: fields.line2 ?? "",
|
|
148
|
+
Line3: fields.line3 ?? "",
|
|
149
|
+
City: fields.city,
|
|
150
|
+
StateOrProvinceCode: fields.state,
|
|
151
|
+
PostCode: fields.postCode ?? "",
|
|
152
|
+
CountryCode: fields.countryCode,
|
|
153
|
+
Longitude: fields.coordinates?.longitude,
|
|
154
|
+
Latitude: fields.coordinates?.latitude
|
|
155
155
|
};
|
|
156
156
|
}
|
|
157
|
+
function mapAddress(addr) {
|
|
158
|
+
return buildPartyAddress({
|
|
159
|
+
line1: addr.line1,
|
|
160
|
+
line2: addr.line2,
|
|
161
|
+
line3: addr.neighbourhood,
|
|
162
|
+
city: addr.city,
|
|
163
|
+
state: addr.state,
|
|
164
|
+
postCode: addr.postalCode,
|
|
165
|
+
countryCode: addr.countryCode,
|
|
166
|
+
coordinates: addr.coordinates
|
|
167
|
+
});
|
|
168
|
+
}
|
|
157
169
|
function buildContact(opts) {
|
|
158
170
|
return {
|
|
159
171
|
Department: "",
|
|
@@ -257,11 +269,11 @@ function mapPickupRequest(input, ctx) {
|
|
|
257
269
|
const closing = new Date(`${input.date}T17:00:00`);
|
|
258
270
|
return {
|
|
259
271
|
Reference1: input.trackingNumbers?.[0],
|
|
260
|
-
PickupAddress: {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
},
|
|
272
|
+
PickupAddress: buildPartyAddress({
|
|
273
|
+
line1: input.address,
|
|
274
|
+
city: input.city,
|
|
275
|
+
countryCode: ctx.countryCode
|
|
276
|
+
}),
|
|
265
277
|
PickupContact: buildContact({
|
|
266
278
|
personName: input.contactName,
|
|
267
279
|
companyName: ctx.companyName ?? input.contactName,
|
|
@@ -347,6 +359,16 @@ function normalizeTrackingResults(raw) {
|
|
|
347
359
|
}
|
|
348
360
|
return raw;
|
|
349
361
|
}
|
|
362
|
+
function mapNonExistingWaybill(waybill) {
|
|
363
|
+
return {
|
|
364
|
+
trackingNumber: waybill,
|
|
365
|
+
carrier: "aramex",
|
|
366
|
+
status: "unknown",
|
|
367
|
+
statusLabel: "Waybill not found",
|
|
368
|
+
events: [],
|
|
369
|
+
raw: { nonExisting: true, waybill }
|
|
370
|
+
};
|
|
371
|
+
}
|
|
350
372
|
function mapRate(response, input) {
|
|
351
373
|
const { productType } = resolveProductGroupAndType(input);
|
|
352
374
|
return {
|
|
@@ -459,6 +481,7 @@ class AramexAdapter extends BaseCarrierAdapter {
|
|
|
459
481
|
version: cfg.version
|
|
460
482
|
});
|
|
461
483
|
}
|
|
484
|
+
static RATE_LIMIT_PATTERN = /rate.?limit|too many request|throttl|quota exceeded/i;
|
|
462
485
|
static aramexErrorExtractor(json) {
|
|
463
486
|
const obj = json;
|
|
464
487
|
const notifications = obj?.Notifications ?? [];
|
|
@@ -467,7 +490,8 @@ class AramexAdapter extends BaseCarrierAdapter {
|
|
|
467
490
|
return {
|
|
468
491
|
hasError,
|
|
469
492
|
message,
|
|
470
|
-
errors: notifications.length ? AramexAdapter.notificationsToErrors(notifications) : undefined
|
|
493
|
+
errors: notifications.length ? AramexAdapter.notificationsToErrors(notifications) : undefined,
|
|
494
|
+
rateLimited: hasError && AramexAdapter.RATE_LIMIT_PATTERN.test(message ?? "")
|
|
471
495
|
};
|
|
472
496
|
}
|
|
473
497
|
static notificationsToErrors(notifications) {
|
|
@@ -548,7 +572,7 @@ class AramexAdapter extends BaseCarrierAdapter {
|
|
|
548
572
|
Transaction: EMPTY_TRANSACTION,
|
|
549
573
|
ShipmentNumber: trackingNumber,
|
|
550
574
|
LabelInfo: DEFAULT_LABEL_INFO
|
|
551
|
-
}, { errorExtractor: AramexAdapter.aramexErrorExtractor });
|
|
575
|
+
}, { retry: true, errorExtractor: AramexAdapter.aramexErrorExtractor });
|
|
552
576
|
const url = response.ShipmentLabel?.LabelURL;
|
|
553
577
|
if (!url) {
|
|
554
578
|
throw new APIError("Failed to get label", {
|
|
@@ -561,10 +585,10 @@ class AramexAdapter extends BaseCarrierAdapter {
|
|
|
561
585
|
async track(trackingNumber) {
|
|
562
586
|
const results = await this.trackMultiple([trackingNumber]);
|
|
563
587
|
const result = results[0];
|
|
564
|
-
if (!result) {
|
|
588
|
+
if (!result || result.status === "unknown" && result.events.length === 0) {
|
|
565
589
|
throw new APIError("Shipment not found", {
|
|
566
590
|
carrier: "aramex",
|
|
567
|
-
raw: { trackingNumber }
|
|
591
|
+
raw: result?.raw ?? { trackingNumber }
|
|
568
592
|
});
|
|
569
593
|
}
|
|
570
594
|
return result;
|
|
@@ -577,7 +601,9 @@ class AramexAdapter extends BaseCarrierAdapter {
|
|
|
577
601
|
GetLastTrackingUpdateOnly: false
|
|
578
602
|
}, { retry: true, errorExtractor: AramexAdapter.aramexErrorExtractor });
|
|
579
603
|
const map = normalizeTrackingResults(response.TrackingResults);
|
|
580
|
-
|
|
604
|
+
const found = Object.entries(map).map(([waybill, results]) => mapTrackingResult(waybill, results));
|
|
605
|
+
const nonExisting = (response.NonExistingWaybills ?? []).map(mapNonExistingWaybill);
|
|
606
|
+
return [...found, ...nonExisting];
|
|
581
607
|
}
|
|
582
608
|
async trackByReference(reference) {
|
|
583
609
|
const result = await this.track(reference);
|
|
@@ -618,6 +644,15 @@ class AramexAdapter extends BaseCarrierAdapter {
|
|
|
618
644
|
raw: response
|
|
619
645
|
});
|
|
620
646
|
}
|
|
647
|
+
const failedShipments = (processed.ProcessedShipments ?? []).filter((s) => s.HasErrors);
|
|
648
|
+
if (failedShipments.length > 0) {
|
|
649
|
+
const notifications = failedShipments.flatMap((s) => s.Notifications ?? []);
|
|
650
|
+
throw new APIError(notifications.map((n) => n.Message).filter(Boolean).join("; ") || `Pickup created but ${failedShipments.length} shipment(s) failed`, {
|
|
651
|
+
carrier: "aramex",
|
|
652
|
+
errors: AramexAdapter.notificationsToErrors(notifications),
|
|
653
|
+
raw: response
|
|
654
|
+
});
|
|
655
|
+
}
|
|
621
656
|
return mapPickupResponse(processed, input);
|
|
622
657
|
}
|
|
623
658
|
async cancelPickup(pickupId) {
|
|
@@ -658,4 +693,4 @@ export {
|
|
|
658
693
|
AramexAdapter
|
|
659
694
|
};
|
|
660
695
|
|
|
661
|
-
//# debugId=
|
|
696
|
+
//# debugId=A6EBA2B597BA645F64756E2164756E21
|