@syscli/yalidine
v0.1.0
Published
Typed Yalidine API client for Node. Server-side only.
Downloads
74
Maintainers
Readme
yalidine
A typed client for the Yalidine shipping API. It covers the parts every integration otherwise rewrites by hand: the auth headers, the rate-limit quota, pagination, batch parcel creation, and the fee math.
It is meant for servers, serverless functions, and edge runtimes. Your API id and token must never reach the browser. The Yalidine docs say so plainly, and this library does nothing to change that. Keep it on the server.
Published as @syscli/yalidine. It needs Node 18 or newer, uses the global
fetch, and ships no runtime dependencies.
Install
npm install @syscli/yalidineQuick start
import { Yalidine } from "@syscli/yalidine";
const yal = new Yalidine({
id: process.env.YALIDINE_API_ID!,
token: process.env.YALIDINE_API_TOKEN!,
});
// Reference data. Alger is 16, Sétif is 19, Batna is 5.
const wilayas = await yal.wilayas.list();
const desks = await yal.centers.list({ wilaya_id: 16 });
// Create a parcel and read back its tracking.
const created = await yal.parcels.create({
order_id: "ORD-1043",
from_wilaya_name: "Batna",
firstname: "Amine",
familyname: "Khelifi",
contact_phone: "0661234567",
address: "12 Rue Didouche Mourad",
to_commune_name: "Alger Centre",
to_wilaya_name: "Alger",
product_list: "Casque audio",
price: 8500,
do_insurance: false,
declared_value: 8500,
length: 25,
width: 20,
height: 15,
weight: 2,
freeshipping: false,
is_stopdesk: false,
has_exchange: false,
});
console.log(created.tracking); // yal-XXXXXXQuota
Yalidine returns how many requests you have left per second, minute, hour, and
day on every response. The client reads those headers after each call, success
or error, and keeps them on yal.quota:
await yal.wilayas.list();
console.log(yal.quota.perDay.left); // e.g. 9412Each window starts undefined and fills in once the first response arrives.
Watch these numbers. Accounts that ignore the limit get banned, which is also
why the next section behaves the way it does.
Retry on 429, by default
When the API answers 429, the client waits the Retry-After it sends and
tries again, up to three attempts total, before raising a RateLimitError. This
is on out of the box because hammering past the limit is what gets an account
banned, so a short pause is the safer default. Tune or switch it off through the
constructor:
const yal = new Yalidine({
id,
token,
retry: { attempts: 1, respectRetryAfter: true }, // 1 disables retrying
});Pagination
list() returns a Page. Step through it with nextPage(), or let
paginate() walk every page for you:
const first = await yal.parcels.list({ to_wilaya_id: 16 });
console.log(first.data, first.total, first.hasMore);
for await (const parcel of yal.parcels.paginate({ to_wilaya_id: 16 })) {
console.log(parcel.tracking, parcel.last_status);
}Fee quote
Fetch the fees for a wilaya pair, then total a real shipment for a commune. It applies the math for you: volumetric weight, the overweight surcharge past 5 kg, COD, and optional insurance.
const fees = await yal.fees.get({ from: 5, to: 16 }); // Batna to Alger
const quote = fees.quote(1601, {
weight: 7,
dimensions: { w: 40, h: 30, l: 50 },
declaredValue: 12000,
price: 12000,
stopdesk: true,
insurance: true,
});
// { delivery, overweight, cod, insurance, total }The same functions are exported on their own (billableWeight, overweightFee,
percentageFee) when you already hold the rates and want to skip the call.
Batch creation
createMany posts a batch and splits the result so you do not have to read the
raw order-id map yourself:
const result = await yal.parcels.createMany([first, second]);
for (const ok of result.created) console.log(ok.tracking);
for (const bad of result.failed) console.warn(bad.orderId, bad.message);result.byOrderId still holds the raw response for anything the split leaves out.
Validation
Parcels are checked before the request leaves. A missing stopdesk_id on a
stop-desk delivery, a missing product_to_collect on an exchange, a price out of
the 0 to 150000 range, or a malformed phone throws a ValidationError, keyed by
order_id, and spends no request or quota:
import { ValidationError } from "@syscli/yalidine";
try {
await yal.parcels.createMany(batch);
} catch (error) {
if (error instanceof ValidationError) {
for (const issue of error.issues) {
console.warn(issue.orderId, issue.field, issue.message);
}
}
}Errors
Every failure is a YalidineError, so you can catch one type and branch on it:
import { AuthError, NotFoundError, RateLimitError } from "@syscli/yalidine";AuthError (401/403), NotFoundError (404), RateLimitError (429, with
retryAfter), RequestError (other 4xx/5xx), and ValidationError (client-side)
all carry status, a stable code, and the parsed response body.
Limitations
Worth knowing before you build on it:
- Server only. The credentials grant full account access. Putting them in client-side code exposes them to anyone who opens the network tab. There is no browser build, and that is on purpose.
- Masked PII in reads.
GETandPATCHresponses maskfirstname,familyname,contact_phone,address, and the phone insideqr_text. Those are placeholders, not the real values. Never write them back to your own database. The masked fields carry a doc-comment as a reminder. - Rate limits and the ban. The per-second through per-day limits are real,
and repeatedly blowing past them gets the account banned. The default retry
helps; reading
yal.quotahelps more. - Edits are narrow. A parcel can only be updated or removed while its status
is still en préparation. Once it moves on, the API rejects the change, and so
will your
update/removecall.
Development
npm install
npm test # vitest, with msw mocking the API
npm run build # tsup, dual ESM and CJS plus typesA small set of live tests run against the real API when YALIDINE_API_ID and
YALIDINE_API_TOKEN are set, and are skipped otherwise.
License
MIT. See LICENSE.
