yalidine-sdk 0.1.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 ADDED
@@ -0,0 +1,15 @@
1
+ # yalidine-sdk
2
+
3
+ Core SDK for interacting with the Yalidine API.
4
+ Provides helpers for parcel creation, fee calculation, tracking, and related operations.
5
+
6
+ ## Installation
7
+
8
+ ```bash
9
+ npm install yalidine-sdk
10
+ ```
11
+
12
+ ## Documentation
13
+
14
+ Full documentation is available here:
15
+ [SDK documentation](../../docs/core/index.md)
package/dist/index.cjs ADDED
@@ -0,0 +1,512 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // index.js
30
+ var index_exports = {};
31
+ __export(index_exports, {
32
+ calculateBillableWeight: () => calculateBillableWeight,
33
+ createParcels: () => createParcels,
34
+ getCentersByCommune: () => getCentersByCommune,
35
+ getCentersByWilaya: () => getCentersByWilaya,
36
+ getCommunesByWilaya: () => getCommunesByWilaya,
37
+ getFees: () => getFees,
38
+ getWilayas: () => getWilayas
39
+ });
40
+ module.exports = __toCommonJS(index_exports);
41
+
42
+ // src/helpers/calculateBillabeWeight.js
43
+ function calculateBillableWeight(height, width, length, weight) {
44
+ const volumetricWeight = height * width * length * 2e-4;
45
+ return weight > volumetricWeight ? weight : volumetricWeight;
46
+ }
47
+
48
+ // src/utils.js
49
+ function ensureServer() {
50
+ if (typeof window !== "undefined") {
51
+ throw new Error(
52
+ "astro-yalidine: server-only helper imported in client code. Use these helpers only in server contexts ( API routes, server-side page formatter, etc.)."
53
+ );
54
+ }
55
+ }
56
+ function getConfig() {
57
+ if (typeof __YALIDINE_CONFIG__ === void 0 || !__YALIDINE_CONFIG__) {
58
+ throw new Error(
59
+ "astro-yalidine: Configuration missing. Did you add yalidine to astro.config.mjs ?"
60
+ );
61
+ }
62
+ return __YALIDINE_CONFIG__;
63
+ }
64
+ async function setRequest({ endpoint, method = "GET", options = {}, params = {} }) {
65
+ const { apiId, apiToken, apiUrl } = getConfig();
66
+ const query = new URLSearchParams(params).toString();
67
+ const url = `${apiUrl.replace(/\/$/, "")}/${endpoint.replace(/^\//, "")}${query ? `?${query}` : ""}`;
68
+ const fetchOptions = {
69
+ method,
70
+ headers: {
71
+ "X-API-ID": apiId.trim(),
72
+ "X-API-TOKEN": apiToken.trim(),
73
+ "Accept": "application/json",
74
+ ...options.headers
75
+ }
76
+ };
77
+ if (method !== "GET" && options.body) {
78
+ fetchOptions.body = JSON.stringify(options.body);
79
+ }
80
+ const res = await fetch(url, fetchOptions);
81
+ if (!res.ok) {
82
+ throw new Error(`'Request failed: ${res.status} ${res.statusText}`);
83
+ }
84
+ return await res.json();
85
+ }
86
+ function getIds(options) {
87
+ return options.id.split(",").map(Number);
88
+ }
89
+ function calculateOverWeight(baseFee, overSizeFee, weight) {
90
+ if (weight <= 5) {
91
+ return baseFee;
92
+ } else {
93
+ return baseFee + overSizeFee * (weight - 5);
94
+ }
95
+ }
96
+ function validateParcels(parcels) {
97
+ if (!parcels || parcels.length === 0) {
98
+ throw new Error("At least one parcel should be entered!");
99
+ }
100
+ if (parcels.length > 50) {
101
+ throw new Error("Too many parcels in one request. Please split into smaller batches.");
102
+ }
103
+ let errorsCount = 0;
104
+ let errorMessage = "";
105
+ const displayErrorCount = (count) => count === 1 ? "One error" : `${count} errors`;
106
+ parcels.forEach((parcel, index) => {
107
+ const requiredStrings = [
108
+ "order_id",
109
+ "from_wilaya_name",
110
+ "firstname",
111
+ "familyname",
112
+ "contact_phone",
113
+ "address",
114
+ "to_commune_name",
115
+ "to_wilaya_name",
116
+ "product_list"
117
+ ];
118
+ requiredStrings.forEach((field) => {
119
+ if (!parcel[field] || typeof parcel[field] !== "string" || parcel[field].trim().length === 0) {
120
+ errorsCount++;
121
+ errorMessage += `Parcel ${index}: ${field} must be a non-empty string
122
+ `;
123
+ }
124
+ });
125
+ const numericFields = ["price", "declared_value", "length", "width", "height", "weight"];
126
+ numericFields.forEach((field) => {
127
+ if (parcel[field] == null || typeof parcel[field] !== "number" || parcel[field] < 0) {
128
+ errorsCount++;
129
+ errorMessage += `Parcel ${index}: ${field} must be a number >= 0
130
+ `;
131
+ }
132
+ });
133
+ if (parcel.price < 0 || parcel.price > 15e4) {
134
+ errorsCount++;
135
+ errorMessage += `Parcel ${index}: price must be between 0 and 150000
136
+ `;
137
+ }
138
+ if (parcel.declared_value < 0 || parcel.declared_value > 15e4) {
139
+ errorsCount++;
140
+ errorMessage += `Parcel ${index}: declared_value must be between 0 and 150000
141
+ `;
142
+ }
143
+ const booleanFields = ["do_insurance", "freeshipping", "is_stopdesk", "has_exchange", "economic"];
144
+ booleanFields.forEach((field) => {
145
+ if (parcel[field] != null && typeof parcel[field] !== "boolean") {
146
+ errorsCount++;
147
+ errorMessage += `Parcel ${index}: ${field} must be a boolean if provided
148
+ `;
149
+ }
150
+ });
151
+ if (parcel.is_stopdesk && (!parcel.stopdesk_id || typeof parcel.stopdesk_id !== "string")) {
152
+ errorsCount++;
153
+ errorMessage += `Parcel ${index}: stopdesk_id is required when is_stopdesk is true
154
+ `;
155
+ }
156
+ if (parcel.has_exchange && (!parcel.product_to_collect || typeof parcel.product_to_collect !== "string")) {
157
+ errorsCount++;
158
+ errorMessage += `Parcel ${index}: product_to_collect is required when has_exchange is true
159
+ `;
160
+ }
161
+ if (parcel.contact_phone) {
162
+ const phones = parcel.contact_phone.split(",").map((p) => p.trim());
163
+ phones.forEach((p) => {
164
+ if (!/^0\d{8,9}$/.test(p)) {
165
+ errorsCount++;
166
+ errorMessage += `Parcel ${index}: contact_phone "${p}" is invalid, must start with 0 and be 9 or 10 digits
167
+ `;
168
+ }
169
+ });
170
+ }
171
+ });
172
+ if (errorsCount > 0) {
173
+ throw new Error(`${displayErrorCount(errorsCount)} prevent the helper from sending the request:
174
+ ${errorMessage}`);
175
+ }
176
+ return true;
177
+ }
178
+
179
+ // src/helpers/calculateParcels.js
180
+ async function createParcels(...parcels) {
181
+ ensureServer();
182
+ validateParcels(parcels);
183
+ const data = await setRequest({
184
+ endpoint: "parcel",
185
+ method: "POST",
186
+ params: { body: parcels }
187
+ });
188
+ const succeededParcels = [];
189
+ const failedParcels = [];
190
+ for (const [orderId, result] of Object.entries(data)) {
191
+ if (result.success) succeededParcels.push(orderId);
192
+ else failedParcels.push(orderId);
193
+ }
194
+ return {
195
+ data,
196
+ meta: { succeededParcels, failedParcels }
197
+ };
198
+ }
199
+
200
+ // src/helpers/getCentersByCommune.js
201
+ async function getCentersByCommune({
202
+ communeId,
203
+ params = {}
204
+ }) {
205
+ ensureServer();
206
+ if (!communeId) {
207
+ throw new Error("A commune Id must be entered to retrieve centers.");
208
+ }
209
+ const response = await setRequest({
210
+ endpoint: "centers",
211
+ params: {
212
+ commune_id: communeId,
213
+ ...params
214
+ }
215
+ });
216
+ const centers = response.data;
217
+ return centers.map((c) => ({
218
+ id: c.center_id,
219
+ name: c.name,
220
+ address: c.address,
221
+ gps: c.gps,
222
+ commune_id: c.commune_id,
223
+ commune_name: c.commune_name,
224
+ wilaya_id: c.wilaya_id
225
+ }));
226
+ }
227
+
228
+ // src/cache/fileCacheAdapter.js
229
+ var import_fs = __toESM(require("fs"), 1);
230
+ var import_path = __toESM(require("path"), 1);
231
+
232
+ // src/cache/cacheAdapter.js
233
+ var CacheAdapter = class {
234
+ async get(key) {
235
+ throw new Error("Not implemented");
236
+ }
237
+ async set(key, value, ttl) {
238
+ throw new Error("Not implemented");
239
+ }
240
+ async delete(key) {
241
+ throw new Error("Not implemented");
242
+ }
243
+ };
244
+
245
+ // src/cache/memoryCacheAdapter.js
246
+ var MemoryCacheAdapter = class extends CacheAdapter {
247
+ constructor() {
248
+ super();
249
+ this.cache = /* @__PURE__ */ new Map();
250
+ }
251
+ async get(key) {
252
+ const entry = this.cache.get(key);
253
+ if (!entry) return null;
254
+ if (entry.expiry && date.now() > entry.expiry) {
255
+ this.cache.delete(key);
256
+ return null;
257
+ }
258
+ return entry.value;
259
+ }
260
+ async set(key, value, ttl) {
261
+ const expiry = ttl ? Date.now() + ttl : null;
262
+ this.cache.set(key, { value, expiry });
263
+ }
264
+ async delete(key) {
265
+ this.cache.delete(key);
266
+ }
267
+ };
268
+
269
+ // src/cache/fileCacheAdapter.js
270
+ var FileCacheAdapter = class extends CacheAdapter {
271
+ constructor(filePath = "./.cache/yalidine.json") {
272
+ super();
273
+ this.filePath = filePath;
274
+ this.memoryFallback = new MemoryCacheAdapter();
275
+ this.load();
276
+ }
277
+ load() {
278
+ try {
279
+ if (import_fs.default.existsSync(this.filePath)) {
280
+ const json = JSON.parse(import_fs.default.readFileSync(this.filePath, "utf-8"));
281
+ this.store = new Map(Object.entries(json));
282
+ } else {
283
+ this.store = /* @__PURE__ */ new Map();
284
+ }
285
+ } catch {
286
+ this.store = null;
287
+ }
288
+ }
289
+ async get(key) {
290
+ if (!this.store) return this.memoryFallback.get(key);
291
+ const entry = this.store.get(key);
292
+ if (!entry) return null;
293
+ if (entry.expiry && Date.now() > entry.expiry) {
294
+ this.store.delete(key);
295
+ this.save();
296
+ return null;
297
+ }
298
+ return entry.value;
299
+ }
300
+ async set(key, value, ttl) {
301
+ if (!this.store) return this.memoryFallback.set(key, value, ttl);
302
+ const expiry = ttl ? Date.now() + ttl : null;
303
+ this.store.set(key, { value, expiry });
304
+ this.save();
305
+ }
306
+ save() {
307
+ try {
308
+ import_fs.default.mkdirSync(import_path.default.dirname(this.filePath), { recursive: true });
309
+ import_fs.default.writeFileSync(
310
+ this.filePath,
311
+ JSON.stringify(Object.fromEntries(this.store), null, 2)
312
+ );
313
+ } catch {
314
+ this.store = null;
315
+ }
316
+ }
317
+ async delete(key) {
318
+ if (!this.store) return this.memoryFallback.delete(key);
319
+ this.store.delete(key);
320
+ this.save();
321
+ }
322
+ };
323
+
324
+ // src/cache/cacheConfig.js
325
+ function getCacheConfig() {
326
+ const { defaultCache, cacheLifeTime } = getConfig();
327
+ const CACHE_TTL = cacheLifeTime * 1e3 * 60 * 60 * 24;
328
+ let cache;
329
+ switch (defaultCache) {
330
+ case "file":
331
+ cache = new FileCacheAdapter();
332
+ break;
333
+ case "memory":
334
+ default:
335
+ cache = new MemoryCacheAdapter();
336
+ break;
337
+ }
338
+ return {
339
+ cache,
340
+ CACHE_TTL
341
+ };
342
+ }
343
+
344
+ // src/helpers/getCentersByWilaya.js
345
+ async function getCentersByWilaya({
346
+ wilayaId,
347
+ params = {}
348
+ }) {
349
+ ensureServer();
350
+ if (!wilayaId) {
351
+ throw new Error("A wilaya Id must be entered to retrieve centers.");
352
+ }
353
+ const { cache, CACHE_TTL } = getCacheConfig();
354
+ const cacheKey = `centers-${wilayaId}`;
355
+ const cached = await cache.get(cacheKey);
356
+ let centers;
357
+ if (cached) {
358
+ centers = cached;
359
+ } else {
360
+ const response = await setRequest({
361
+ endpoint: "centers",
362
+ params: {
363
+ wilaya_id: wilayaId,
364
+ ...params
365
+ }
366
+ });
367
+ centers = response.data;
368
+ await cache.set(cacheKey, centers, CACHE_TTL);
369
+ }
370
+ return centers.map((c) => ({
371
+ id: c.center_id,
372
+ name: c.name,
373
+ address: c.address,
374
+ gps: c.gps,
375
+ commune_id: c.commune_id,
376
+ commune_name: c.commune_name,
377
+ wilaya_id: c.wilaya_id
378
+ }));
379
+ }
380
+
381
+ // src/helpers/getCommunesByWilaya.js
382
+ async function getCommunesByWilaya({
383
+ wilayaId,
384
+ deliverableOnly = true,
385
+ hasStopDesk = false,
386
+ params = {}
387
+ }) {
388
+ ensureServer();
389
+ const { cache, CACHE_TTL } = getCacheConfig();
390
+ const isPartial = Boolean(params.id);
391
+ const cacheKey = `communes-${wilayaId}`;
392
+ const cached = await cache.get(cacheKey);
393
+ let communes;
394
+ if (cached) {
395
+ if (isPartial) {
396
+ const ids = getIds(params);
397
+ communes = cached.filter((c) => ids.includes(c.id));
398
+ } else {
399
+ communes = cached;
400
+ }
401
+ } else {
402
+ const response = await setRequest({
403
+ endpoint: "communes",
404
+ params: {
405
+ wilaya_id: wilayaId,
406
+ ...params
407
+ }
408
+ });
409
+ communes = response.data;
410
+ if (!isPartial) {
411
+ await cache.set(cacheKey, communes, CACHE_TTL);
412
+ }
413
+ }
414
+ const cleanedDeliverability = deliverableOnly ? communes.filter((c) => c.is_deliverable === 1) : communes;
415
+ const cleaned = hasStopDesk ? cleanedDeliverability.filter((c) => c.has_stop_desk === 1) : cleanedDeliverability;
416
+ return cleaned.map((c) => ({
417
+ id: c.id,
418
+ name: c.name,
419
+ delivery_time_parcel: c.delivery_time_parcel,
420
+ ...deliverableOnly ? {} : { is_deliverable: c.is_deliverable },
421
+ ...hasStopDesk ? {} : { has_stop_desk: c.has_stop_desk }
422
+ }));
423
+ }
424
+
425
+ // src/helpers/getFees.js
426
+ async function getFees({
427
+ fromWilayaId,
428
+ toWilayaId,
429
+ toCommuneId,
430
+ billableWeight = 5
431
+ }) {
432
+ ensureServer();
433
+ if (!toWilayaId || !toCommuneId) {
434
+ throw new Error("Destination wilaya and commune are required to calculate fees.");
435
+ }
436
+ const safeWeight = typeof billableWeight === "number" && billableWeight > 0 ? billableWeight : 5;
437
+ const startingWilaya = fromWilayaId ?? getConfig().startingWilaya;
438
+ const data = await setRequest({
439
+ endpoint: "fees",
440
+ params: {
441
+ from_wilaya_id: startingWilaya,
442
+ to_wilaya_id: toWilayaId
443
+ }
444
+ });
445
+ if (!data.per_commune[toCommuneId]) {
446
+ throw new Error("Commune not found");
447
+ }
448
+ ;
449
+ const communeFee = data.per_commune[toCommuneId];
450
+ const oversizeFee = data.oversize_fee;
451
+ return {
452
+ fees: {
453
+ expressHome: calculateOverWeight(communeFee.express_home, oversizeFee, safeWeight),
454
+ expressDesk: calculateOverWeight(communeFee.express_desk, oversizeFee, safeWeight),
455
+ economicHome: communeFee.economic_home ? calculateOverWeight(communeFee.economic_home, oversizeFee, safeWeight) : null,
456
+ economicDesk: communeFee.economic_desk ? calculateOverWeight(communeFee.economic_desk, oversizeFee, safeWeight) : null
457
+ },
458
+ meta: {
459
+ retourFee: data.retour_fee,
460
+ codPercentage: data.cod_percentage,
461
+ insurancePercentage: data.insurance_percentage,
462
+ oversizeApplied: safeWeight > 5
463
+ }
464
+ };
465
+ }
466
+
467
+ // src/helpers/getWilayas.js
468
+ async function getWilayas({
469
+ deliverableOnly = true,
470
+ params = {}
471
+ }) {
472
+ ensureServer();
473
+ const { cache, CACHE_TTL } = getCacheConfig();
474
+ const isPartial = Boolean(params.id);
475
+ const cacheKey = "wilayas";
476
+ const cached = await cache.get(cacheKey);
477
+ let wilayas;
478
+ if (cached) {
479
+ if (isPartial) {
480
+ const ids = getIds(params);
481
+ wilayas = cached.filter((w) => ids.includes(w.id));
482
+ } else {
483
+ wilayas = cached;
484
+ }
485
+ } else {
486
+ const response = await setRequest({
487
+ endpoint: "wilayas",
488
+ params
489
+ });
490
+ wilayas = response.data;
491
+ if (!isPartial) {
492
+ await cache.set(cacheKey, wilayas, CACHE_TTL);
493
+ }
494
+ }
495
+ const cleaned = deliverableOnly ? wilayas.filter((w) => w.is_deliverable === 1) : wilayas;
496
+ return cleaned.map((w) => ({
497
+ id: w.id,
498
+ name: w.name,
499
+ zone: w.zone,
500
+ ...deliverableOnly ? {} : { is_deliverable: w.is_deliverable }
501
+ }));
502
+ }
503
+ // Annotate the CommonJS export names for ESM import in node:
504
+ 0 && (module.exports = {
505
+ calculateBillableWeight,
506
+ createParcels,
507
+ getCentersByCommune,
508
+ getCentersByWilaya,
509
+ getCommunesByWilaya,
510
+ getFees,
511
+ getWilayas
512
+ });