Skip to content

About

Fully Typed Yalidine API client for Node. Server-side only.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Repository files navigation

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/yalidine

Quick 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, Setif 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-XXXXXX

Quota

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. 9412

Each 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
});

A Retry-After longer than retry.maxWaitSeconds (60 by default) is not waited out: the RateLimitError surfaces at once so a request never hangs for an hour.

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 (volumetricWeight, 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, including a quote() for a commune or option that has no price) all carry status, a stable code, and the parsed response body.

Transport failures are wrapped too, with the original error on cause: TimeoutError when timeoutMs runs out and NetworkError when no response arrives at all.

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. GET and PATCH responses mask firstname, familyname, contact_phone, address, and the phone inside qr_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.quota helps more.
  • Edits are narrow. A parcel can only be updated or removed while its status is still en preparation. Once it moves on, the API rejects the change, and so will your update/remove call.

Development

npm install
npm run typecheck   # src, tests, and examples
npm test            # vitest, with msw mocking the API (Node 20+)
npm run build       # tsup, dual ESM and CJS plus types

A 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.

About

Fully Typed Yalidine API client for Node. Server-side only.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages