ebag 0.0.2 → 0.1.1

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.
@@ -7,6 +7,7 @@ exports.getConfigDir = getConfigDir;
7
7
  exports.getConfigPath = getConfigPath;
8
8
  exports.getSessionPath = getSessionPath;
9
9
  exports.getCachePath = getCachePath;
10
+ exports.getLogPath = getLogPath;
10
11
  exports.loadConfig = loadConfig;
11
12
  exports.saveConfig = saveConfig;
12
13
  exports.loadSession = loadSession;
@@ -17,11 +18,11 @@ const node_fs_1 = __importDefault(require("node:fs"));
17
18
  const node_path_1 = __importDefault(require("node:path"));
18
19
  const node_os_1 = __importDefault(require("node:os"));
19
20
  const DEFAULT_CONFIG = {
20
- baseUrl: 'https://www.ebag.bg',
21
+ baseUrl: "https://www.ebag.bg",
21
22
  algolia: {
22
- appId: 'JMJMDQ9HHX',
23
- apiKey: '42ca9458d9354298c7016ce9155d8481',
24
- host: 'jmjmdq9hhx-dsn.algolia.net',
23
+ appId: "JMJMDQ9HHX",
24
+ apiKey: "42ca9458d9354298c7016ce9155d8481",
25
+ host: "jmjmdq9hhx-dsn.algolia.net",
25
26
  },
26
27
  };
27
28
  function ensureDir(dir) {
@@ -32,11 +33,11 @@ function getConfigDir() {
32
33
  if (override) {
33
34
  return override;
34
35
  }
35
- return node_path_1.default.join(node_os_1.default.homedir(), '.config', 'ebag');
36
+ return node_path_1.default.join(node_os_1.default.homedir(), ".config", "ebag");
36
37
  }
37
38
  function readJsonFile(filePath, fallback) {
38
39
  try {
39
- const raw = node_fs_1.default.readFileSync(filePath, 'utf8');
40
+ const raw = node_fs_1.default.readFileSync(filePath, "utf8");
40
41
  return JSON.parse(raw);
41
42
  }
42
43
  catch {
@@ -45,26 +46,34 @@ function readJsonFile(filePath, fallback) {
45
46
  }
46
47
  function writeJsonFile(filePath, data) {
47
48
  ensureDir(node_path_1.default.dirname(filePath));
48
- node_fs_1.default.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8');
49
+ node_fs_1.default.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf8");
49
50
  }
50
51
  function writeSessionFile(filePath, data) {
51
52
  ensureDir(node_path_1.default.dirname(filePath));
52
- node_fs_1.default.writeFileSync(filePath, JSON.stringify(data, null, 2), { encoding: 'utf8', mode: 0o600 });
53
+ node_fs_1.default.writeFileSync(filePath, JSON.stringify(data, null, 2), {
54
+ encoding: "utf8",
55
+ mode: 0o600,
56
+ });
53
57
  }
54
58
  function getConfigPath() {
55
- return node_path_1.default.join(getConfigDir(), 'config.json');
59
+ return node_path_1.default.join(getConfigDir(), "config.json");
56
60
  }
57
61
  function getSessionPath() {
58
- return node_path_1.default.join(getConfigDir(), 'session.json');
62
+ return node_path_1.default.join(getConfigDir(), "session.json");
59
63
  }
60
64
  function getCachePath() {
61
- return node_path_1.default.join(getConfigDir(), 'cache.json');
65
+ return node_path_1.default.join(getConfigDir(), "cache.json");
66
+ }
67
+ function getLogPath() {
68
+ return node_path_1.default.join(getConfigDir(), "ebag.log");
62
69
  }
63
70
  function loadConfig() {
64
71
  const stored = readJsonFile(getConfigPath(), {});
65
72
  const algolia = {
66
- appId: stored.algolia?.appId ?? DEFAULT_CONFIG.algolia?.appId ?? 'JMJMDQ9HHX',
67
- apiKey: stored.algolia?.apiKey ?? DEFAULT_CONFIG.algolia?.apiKey ?? '42ca9458d9354298c7016ce9155d8481',
73
+ appId: stored.algolia?.appId ?? DEFAULT_CONFIG.algolia?.appId ?? "JMJMDQ9HHX",
74
+ apiKey: stored.algolia?.apiKey ??
75
+ DEFAULT_CONFIG.algolia?.apiKey ??
76
+ "42ca9458d9354298c7016ce9155d8481",
68
77
  host: stored.algolia?.host ?? DEFAULT_CONFIG.algolia?.host,
69
78
  };
70
79
  return {
@@ -4,24 +4,27 @@ exports.normalizeCookieInput = normalizeCookieInput;
4
4
  exports.validateCookieInput = validateCookieInput;
5
5
  function normalizeCookieInput(value) {
6
6
  const trimmed = value.trim();
7
- if (trimmed.toLowerCase().startsWith('cookie:')) {
8
- return trimmed.slice('cookie:'.length).trim();
7
+ if (trimmed.toLowerCase().startsWith("cookie:")) {
8
+ return trimmed.slice("cookie:".length).trim();
9
9
  }
10
10
  return trimmed;
11
11
  }
12
12
  function validateCookieInput(value) {
13
13
  if (!value) {
14
- return 'Cookie value is empty.';
14
+ return "Cookie value is empty.";
15
15
  }
16
- if (value.includes('\n') || value.includes('\r')) {
17
- return 'Cookie value should be a single header line without newlines.';
16
+ if (value.includes("\n") || value.includes("\r")) {
17
+ return "Cookie value should be a single header line without newlines.";
18
18
  }
19
- const parts = value.split(';').map((part) => part.trim()).filter(Boolean);
19
+ const parts = value
20
+ .split(";")
21
+ .map((part) => part.trim())
22
+ .filter(Boolean);
20
23
  if (parts.length === 0) {
21
24
  return 'Cookie value should look like "name=value" pairs from the Cookie header.';
22
25
  }
23
26
  for (const part of parts) {
24
- const eqIndex = part.indexOf('=');
27
+ const eqIndex = part.indexOf("=");
25
28
  if (eqIndex <= 0 || eqIndex === part.length - 1) {
26
29
  return 'Cookie value should look like "name=value" pairs from the Cookie header.';
27
30
  }
@@ -1,9 +1,9 @@
1
- export * from './auth';
2
- export * from './cart';
3
- export * from './client';
4
- export * from './config';
5
- export * from './lists';
6
- export * from './orders';
7
- export * from './products';
8
- export * from './search';
9
- export * from './types';
1
+ export * from "./auth";
2
+ export * from "./cart";
3
+ export * from "./client";
4
+ export * from "./config";
5
+ export * from "./lists";
6
+ export * from "./orders";
7
+ export * from "./products";
8
+ export * from "./search";
9
+ export * from "./types";
@@ -1,4 +1,4 @@
1
- import type { Config, ListSummary, Session } from './types';
1
+ import type { Config, ListSummary, Session } from "./types";
2
2
  export declare function getLists(config: Config, session: Session): Promise<ListSummary[]>;
3
3
  export declare function addToList(config: Config, session: Session, listId: number, productId: number, quantity: number): Promise<unknown>;
4
4
  export declare function getListItems(config: Config, session: Session, listId: number, page?: number): Promise<Record<string, unknown>>;
package/dist/lib/lists.js CHANGED
@@ -5,7 +5,7 @@ exports.addToList = addToList;
5
5
  exports.getListItems = getListItems;
6
6
  const client_1 = require("./client");
7
7
  async function getLists(config, session) {
8
- const result = await (0, client_1.requestEbag)(config, session, '/lists/json');
8
+ const result = await (0, client_1.requestEbag)(config, session, "/lists/json");
9
9
  return result.data.map((list) => ({
10
10
  id: list.id,
11
11
  name: list.name,
@@ -19,15 +19,15 @@ async function getLists(config, session) {
19
19
  }));
20
20
  }
21
21
  async function addToList(config, session, listId, productId, quantity) {
22
- const baseUrl = config.baseUrl || 'https://www.ebag.bg';
22
+ const baseUrl = config.baseUrl || "https://www.ebag.bg";
23
23
  const body = new URLSearchParams({
24
24
  product_id: String(productId),
25
25
  quantity: String(quantity),
26
26
  });
27
27
  const result = await (0, client_1.requestEbag)(config, session, `/lists/${listId}/items/update`, {
28
- method: 'POST',
28
+ method: "POST",
29
29
  headers: {
30
- 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
30
+ "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
31
31
  origin: baseUrl,
32
32
  referer: `${baseUrl}/search/`,
33
33
  },
@@ -0,0 +1,8 @@
1
+ export type LogEntry = {
2
+ event: string;
3
+ level?: "info" | "error";
4
+ [key: string]: unknown;
5
+ };
6
+ export declare function redactHeaders(headers: Record<string, string> | undefined): Record<string, string> | undefined;
7
+ export declare function sanitizeEntry(entry: Record<string, unknown>): Record<string, unknown>;
8
+ export declare function appendLog(entry: LogEntry): void;
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.redactHeaders = redactHeaders;
7
+ exports.sanitizeEntry = sanitizeEntry;
8
+ exports.appendLog = appendLog;
9
+ const node_fs_1 = __importDefault(require("node:fs"));
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const config_1 = require("./config");
12
+ const SENSITIVE_KEYS = ["cookie", "authorization", "x-csrftoken"];
13
+ function ensureDir(dir) {
14
+ node_fs_1.default.mkdirSync(dir, { recursive: true });
15
+ }
16
+ function isSensitiveKey(key) {
17
+ const lower = key.toLowerCase();
18
+ return SENSITIVE_KEYS.some((token) => lower === token || lower.includes(token));
19
+ }
20
+ function looksLikeCookie(value) {
21
+ if (value.includes("csrftoken=") ||
22
+ value.includes("sessionid=") ||
23
+ value.includes("cookie=")) {
24
+ return true;
25
+ }
26
+ return value.includes("=") && value.includes(";");
27
+ }
28
+ function redactHeaders(headers) {
29
+ if (!headers)
30
+ return undefined;
31
+ const redacted = {};
32
+ for (const [key, value] of Object.entries(headers)) {
33
+ redacted[key] = isSensitiveKey(key) ? "[redacted]" : value;
34
+ }
35
+ return redacted;
36
+ }
37
+ function sanitizeValue(value, seen) {
38
+ if (typeof value === "string") {
39
+ return looksLikeCookie(value) ? "[redacted]" : value;
40
+ }
41
+ if (!value || typeof value !== "object") {
42
+ return value;
43
+ }
44
+ if (seen.has(value)) {
45
+ return "[circular]";
46
+ }
47
+ seen.add(value);
48
+ if (Array.isArray(value)) {
49
+ return value.map((item) => sanitizeValue(item, seen));
50
+ }
51
+ const output = {};
52
+ for (const [key, entry] of Object.entries(value)) {
53
+ if (isSensitiveKey(key)) {
54
+ output[key] = "[redacted]";
55
+ continue;
56
+ }
57
+ output[key] = sanitizeValue(entry, seen);
58
+ }
59
+ return output;
60
+ }
61
+ function sanitizeEntry(entry) {
62
+ const seen = new WeakSet();
63
+ return sanitizeValue(entry, seen);
64
+ }
65
+ function formatValue(value) {
66
+ return JSON.stringify(value);
67
+ }
68
+ function formatEntry(entry) {
69
+ const base = {
70
+ ts: new Date().toISOString(),
71
+ ...entry,
72
+ };
73
+ const orderedKeys = [
74
+ "ts",
75
+ "level",
76
+ "event",
77
+ "command",
78
+ "args",
79
+ "json",
80
+ "pid",
81
+ "ppid",
82
+ "cwd",
83
+ "node",
84
+ "configDir",
85
+ "exitCode",
86
+ "durationMs",
87
+ ];
88
+ const seen = new Set(orderedKeys);
89
+ const pairs = [];
90
+ for (const key of orderedKeys) {
91
+ if (base[key] !== undefined) {
92
+ pairs.push(`${key}=${formatValue(base[key])}`);
93
+ }
94
+ }
95
+ for (const key of Object.keys(base).sort()) {
96
+ if (seen.has(key))
97
+ continue;
98
+ const value = base[key];
99
+ if (value !== undefined) {
100
+ pairs.push(`${key}=${formatValue(value)}`);
101
+ }
102
+ }
103
+ return pairs.join(" ");
104
+ }
105
+ function appendLog(entry) {
106
+ try {
107
+ const logPath = (0, config_1.getLogPath)();
108
+ ensureDir(node_path_1.default.dirname(logPath));
109
+ const line = formatEntry(sanitizeEntry(entry));
110
+ node_fs_1.default.appendFileSync(logPath, `${line}\n`, "utf8");
111
+ }
112
+ catch {
113
+ // Logging must never crash the CLI.
114
+ }
115
+ }
@@ -1,4 +1,4 @@
1
- import type { Config, OrderDetail, OrderSummary, Session } from './types';
1
+ import type { Config, OrderDetail, OrderSummary, Session } from "./types";
2
2
  export declare function getTimeSlots(config: Config, session: Session): Promise<Record<string, unknown>>;
3
3
  export declare function listOrders(config: Config, session: Session, options?: {
4
4
  limit?: number;
@@ -6,7 +6,7 @@ exports.getOrderDetail = getOrderDetail;
6
6
  const config_1 = require("./config");
7
7
  const client_1 = require("./client");
8
8
  async function getTimeSlots(config, session) {
9
- const result = await (0, client_1.requestEbag)(config, session, '/orders/get-time-slots');
9
+ const result = await (0, client_1.requestEbag)(config, session, "/orders/get-time-slots");
10
10
  return result.data;
11
11
  }
12
12
  function normalizeDate(value) {
@@ -24,25 +24,27 @@ function normalizeDate(value) {
24
24
  const parsed = new Date(value);
25
25
  if (Number.isNaN(parsed.getTime()))
26
26
  return undefined;
27
- const pad = (n) => String(n).padStart(2, '0');
27
+ const pad = (n) => String(n).padStart(2, "0");
28
28
  return `${parsed.getFullYear()}-${pad(parsed.getMonth() + 1)}-${pad(parsed.getDate())}`;
29
29
  }
30
30
  function normalizeOrderSummary(entry) {
31
31
  const parseNumber = (value) => {
32
- if (typeof value === 'number')
32
+ if (typeof value === "number")
33
33
  return value;
34
- if (typeof value === 'string' && value.trim() !== '') {
34
+ if (typeof value === "string" && value.trim() !== "") {
35
35
  const parsed = Number(value);
36
36
  return Number.isNaN(parsed) ? undefined : parsed;
37
37
  }
38
38
  return undefined;
39
39
  };
40
40
  return {
41
- id: entry.encrypted_id ? String(entry.encrypted_id) : '',
41
+ id: entry.encrypted_id ? String(entry.encrypted_id) : "",
42
42
  shippingDate: entry.shipping_date ? String(entry.shipping_date) : undefined,
43
43
  timeSlotStart: parseNumber(entry.time_slot_start),
44
44
  timeSlotEnd: parseNumber(entry.time_slot_end),
45
- timeSlotDisplay: entry.time_slot_display ? String(entry.time_slot_display) : undefined,
45
+ timeSlotDisplay: entry.time_slot_display
46
+ ? String(entry.time_slot_display)
47
+ : undefined,
46
48
  status: parseNumber(entry.order_status),
47
49
  statusText: entry.order_status_pharmacy
48
50
  ? String(entry.order_status_pharmacy)
@@ -50,8 +52,10 @@ function normalizeOrderSummary(entry) {
50
52
  ? String(entry.order_status_text)
51
53
  : null,
52
54
  finalAmount: entry.final_amount ? String(entry.final_amount) : undefined,
53
- finalAmountEur: entry.final_amount_eur ? String(entry.final_amount_eur) : undefined,
54
- additionalOrdersCount: typeof entry.additional_orders_count === 'number'
55
+ finalAmountEur: entry.final_amount_eur
56
+ ? String(entry.final_amount_eur)
57
+ : undefined,
58
+ additionalOrdersCount: typeof entry.additional_orders_count === "number"
55
59
  ? entry.additional_orders_count
56
60
  : entry.additional_orders_count !== undefined
57
61
  ? Number(entry.additional_orders_count)
@@ -64,26 +68,27 @@ function normalizeGroupedItems(groupedItems) {
64
68
  return [];
65
69
  const items = [];
66
70
  for (const group of groupedItems) {
67
- if (!group || typeof group !== 'object')
71
+ if (!group || typeof group !== "object")
68
72
  continue;
69
73
  const groupName = group.group_name;
70
74
  const groupItems = group.group_items || [];
71
75
  for (const entry of groupItems) {
72
- if (!entry || typeof entry !== 'object')
76
+ if (!entry || typeof entry !== "object")
73
77
  continue;
74
78
  const item = entry;
75
79
  const product = item.product || undefined;
76
- const productSaved = item.product_saved || undefined;
80
+ const productSaved = item.product_saved ||
81
+ undefined;
77
82
  const name = product?.name ||
78
83
  item.product_saved_name ||
79
84
  productSaved?.name_bg ||
80
85
  productSaved?.name_en ||
81
- 'Unknown';
82
- const id = typeof product?.id === 'number'
86
+ "Unknown";
87
+ const id = typeof product?.id === "number"
83
88
  ? product.id
84
89
  : product?.id
85
90
  ? Number(product.id)
86
- : typeof productSaved?.id === 'number'
91
+ : typeof productSaved?.id === "number"
87
92
  ? productSaved.id
88
93
  : productSaved?.id
89
94
  ? Number(productSaved.id)
@@ -92,11 +97,17 @@ function normalizeGroupedItems(groupedItems) {
92
97
  id,
93
98
  name,
94
99
  quantity: item.quantity ? String(item.quantity) : undefined,
95
- unit: product?.unit_weight_text ? String(product.unit_weight_text) : undefined,
100
+ unit: product?.unit_weight_text
101
+ ? String(product.unit_weight_text)
102
+ : undefined,
96
103
  price: item.price ? String(item.price) : undefined,
97
104
  priceEur: item.price_eur ? String(item.price_eur) : undefined,
98
- regularPrice: item.regular_price ? String(item.regular_price) : undefined,
99
- regularPriceEur: item.regular_price_eur ? String(item.regular_price_eur) : undefined,
105
+ regularPrice: item.regular_price
106
+ ? String(item.regular_price)
107
+ : undefined,
108
+ regularPriceEur: item.regular_price_eur
109
+ ? String(item.regular_price_eur)
110
+ : undefined,
100
111
  group: groupName ? String(groupName) : undefined,
101
112
  });
102
113
  }
@@ -106,24 +117,27 @@ function normalizeGroupedItems(groupedItems) {
106
117
  function normalizeOrderDetail(payload) {
107
118
  const order = payload.order || {};
108
119
  const parseNumber = (value) => {
109
- if (typeof value === 'number')
120
+ if (typeof value === "number")
110
121
  return value;
111
- if (typeof value === 'string' && value.trim() !== '') {
122
+ if (typeof value === "string" && value.trim() !== "") {
112
123
  const parsed = Number(value);
113
124
  return Number.isNaN(parsed) ? undefined : parsed;
114
125
  }
115
126
  return undefined;
116
127
  };
117
- const addressSerialized = order.address_serialized ? String(order.address_serialized) : '';
118
- const city = order.city ? String(order.city) : '';
119
- const neighbourhood = order.neighbourhood ? String(order.neighbourhood) : '';
120
- const street = order.street ? String(order.street) : '';
121
- const address = addressSerialized || [city, neighbourhood, street].filter(Boolean).join(', ');
128
+ const addressSerialized = order.address_serialized
129
+ ? String(order.address_serialized)
130
+ : "";
131
+ const city = order.city ? String(order.city) : "";
132
+ const neighbourhood = order.neighbourhood ? String(order.neighbourhood) : "";
133
+ const street = order.street ? String(order.street) : "";
134
+ const address = addressSerialized ||
135
+ [city, neighbourhood, street].filter(Boolean).join(", ");
122
136
  const additionalOrdersRaw = Array.isArray(order.additional_orders)
123
137
  ? order.additional_orders
124
138
  : [];
125
139
  return {
126
- id: order.encrypted_id ? String(order.encrypted_id) : '',
140
+ id: order.encrypted_id ? String(order.encrypted_id) : "",
127
141
  status: parseNumber(order.order_status),
128
142
  statusText: order.order_status_pharmacy
129
143
  ? String(order.order_status_pharmacy)
@@ -142,8 +156,12 @@ function normalizeOrderDetail(payload) {
142
156
  totals: {
143
157
  total: order.total ? String(order.total) : undefined,
144
158
  totalEur: order.total_eur ? String(order.total_eur) : undefined,
145
- totalPaid: order.total_price_paid ? String(order.total_price_paid) : undefined,
146
- totalPaidEur: order.total_price_paid_eur ? String(order.total_price_paid_eur) : undefined,
159
+ totalPaid: order.total_price_paid
160
+ ? String(order.total_price_paid)
161
+ : undefined,
162
+ totalPaidEur: order.total_price_paid_eur
163
+ ? String(order.total_price_paid_eur)
164
+ : undefined,
147
165
  discount: order.discount ? String(order.discount) : undefined,
148
166
  discountEur: order.discount_eur ? String(order.discount_eur) : undefined,
149
167
  tip: order.tip ? String(order.tip) : undefined,
@@ -205,11 +223,11 @@ async function listOrders(config, session, options = {}) {
205
223
  let page = options.page ?? 1;
206
224
  let pagesFetched = 0;
207
225
  while (results.length < limit) {
208
- const result = await (0, client_1.requestEbag)(config, session, '/orders/list/json', {
226
+ const result = await (0, client_1.requestEbag)(config, session, "/orders/list/json", {
209
227
  query: {
210
228
  page,
211
229
  year: hasRange ? year : undefined,
212
- exclude_additional_order: 'true',
230
+ exclude_additional_order: "true",
213
231
  },
214
232
  });
215
233
  const payload = result.data;
@@ -1,2 +1,2 @@
1
- import type { Config, ProductDetail, Session } from './types';
1
+ import type { Config, ProductDetail, Session } from "./types";
2
2
  export declare function getProductById(config: Config, session: Session, productId: number): Promise<ProductDetail>;
@@ -1,4 +1,4 @@
1
- import type { Config, ProductCacheEntry, SearchResult, Session } from './types';
1
+ import type { Config, ProductCacheEntry, SearchResult, Session } from "./types";
2
2
  export declare const PRODUCT_CACHE_TTL_MS: number;
3
3
  export declare function isProductCacheFresh(entry: ProductCacheEntry, now?: number, ttlMs?: number): boolean;
4
4
  export declare function searchProducts(config: Config, session: Session, query: string, options?: {
@@ -7,9 +7,9 @@ const config_1 = require("./config");
7
7
  const client_1 = require("./client");
8
8
  const lists_1 = require("./lists");
9
9
  const DEFAULT_FACETS = [
10
- 'brand_name_bg',
11
- 'country_of_origin_bg',
12
- 'hierarchical_categories_bg.lv1',
10
+ "brand_name_bg",
11
+ "country_of_origin_bg",
12
+ "hierarchical_categories_bg.lv1",
13
13
  ];
14
14
  exports.PRODUCT_CACHE_TTL_MS = 6 * 60 * 60 * 1000;
15
15
  function isProductCacheFresh(entry, now = Date.now(), ttlMs = exports.PRODUCT_CACHE_TTL_MS) {
@@ -20,12 +20,16 @@ function isProductCacheFresh(entry, now = Date.now(), ttlMs = exports.PRODUCT_CA
20
20
  }
21
21
  function normalizeProductFromDetail(data) {
22
22
  const id = Number(data.id);
23
- const name = String(data.name || '');
23
+ const name = String(data.name || "");
24
24
  const nameEn = data.name_en ? String(data.name_en) : undefined;
25
25
  const price = data.price ? String(data.price) : undefined;
26
26
  const pricePromo = data.price_promo ? String(data.price_promo) : undefined;
27
- const currentPrice = data.current_price ? String(data.current_price) : undefined;
28
- const mainImageId = data.main_image_id ? String(data.main_image_id) : undefined;
27
+ const currentPrice = data.current_price
28
+ ? String(data.current_price)
29
+ : undefined;
30
+ const mainImageId = data.main_image_id
31
+ ? String(data.main_image_id)
32
+ : undefined;
29
33
  const imageUrl = mainImageId
30
34
  ? `https://www.ebag.bg/products/images/${mainImageId}/200/webp`
31
35
  : undefined;
@@ -43,11 +47,13 @@ function normalizeProductFromDetail(data) {
43
47
  }
44
48
  function normalizeProductFromAlgolia(hit) {
45
49
  const id = Number(hit.id);
46
- const name = String(hit.name_bg || '');
50
+ const name = String(hit.name_bg || "");
47
51
  const nameEn = hit.name_en ? String(hit.name_en) : undefined;
48
52
  const price = hit.price ? String(hit.price) : undefined;
49
53
  const pricePromo = hit.price_promo ? String(hit.price_promo) : undefined;
50
- const currentPrice = hit.current_price ? String(hit.current_price) : undefined;
54
+ const currentPrice = hit.current_price
55
+ ? String(hit.current_price)
56
+ : undefined;
51
57
  const imageUrl = hit.product_image_absolute_url
52
58
  ? String(hit.product_image_absolute_url)
53
59
  : undefined;
@@ -61,11 +67,11 @@ function normalizeProductFromAlgolia(hit) {
61
67
  currentPrice,
62
68
  imageUrl,
63
69
  urlSlug,
64
- source: 'algolia',
70
+ source: "algolia",
65
71
  };
66
72
  }
67
73
  function safeLower(value) {
68
- return value.toLocaleLowerCase('bg-BG');
74
+ return value.toLocaleLowerCase("bg-BG");
69
75
  }
70
76
  async function fetchProductDetail(config, session, productId) {
71
77
  const result = await (0, client_1.requestEbag)(config, session, `/products/${productId}/json`);
@@ -88,7 +94,7 @@ async function mapWithConcurrency(items, limit, mapper) {
88
94
  async function getProductWithCache(config, session, cache, productId) {
89
95
  const cached = cache.products[String(productId)];
90
96
  if (cached) {
91
- if ('product' in cached) {
97
+ if ("product" in cached) {
92
98
  const entry = cached;
93
99
  if (isProductCacheFresh(entry)) {
94
100
  return entry.product;
@@ -123,23 +129,23 @@ async function searchInLists(config, session, query) {
123
129
  return products
124
130
  .map((product) => ({
125
131
  ...product,
126
- source: 'list',
132
+ source: "list",
127
133
  listNames: productIdToLists.get(product.id) || [],
128
134
  }))
129
135
  .filter((product) => {
130
- const name = safeLower(product.name || '');
131
- const nameEn = product.nameEn ? safeLower(product.nameEn) : '';
136
+ const name = safeLower(product.name || "");
137
+ const nameEn = product.nameEn ? safeLower(product.nameEn) : "";
132
138
  return name.includes(needle) || nameEn.includes(needle);
133
139
  });
134
140
  }
135
141
  async function searchAlgolia(config, query, page, hitsPerPage) {
136
142
  const params = new URLSearchParams({
137
- clickAnalytics: 'true',
143
+ clickAnalytics: "true",
138
144
  facets: JSON.stringify(DEFAULT_FACETS),
139
- filters: '',
140
- highlightPostTag: '__/ais-highlight__',
141
- highlightPreTag: '__ais-highlight__',
142
- maxValuesPerFacet: '50',
145
+ filters: "",
146
+ highlightPostTag: "__/ais-highlight__",
147
+ highlightPreTag: "__ais-highlight__",
148
+ maxValuesPerFacet: "50",
143
149
  page: String(page),
144
150
  query,
145
151
  hitsPerPage: String(hitsPerPage),
@@ -147,7 +153,7 @@ async function searchAlgolia(config, query, page, hitsPerPage) {
147
153
  const body = {
148
154
  requests: [
149
155
  {
150
- indexName: 'products',
156
+ indexName: "products",
151
157
  params: params.toString(),
152
158
  },
153
159
  ],
@@ -1,4 +1,4 @@
1
- import type { DeliverySlot } from './types';
1
+ import type { DeliverySlot } from "./types";
2
2
  export declare function normalizeSlots(data: Record<string, unknown>): DeliverySlot[];
3
3
  export declare function formatSlotTime(value: number): string;
4
4
  export declare function formatSlotRange(start: number, end: number): string;
package/dist/lib/slots.js CHANGED
@@ -11,7 +11,7 @@ function normalizeSlots(data) {
11
11
  if (!Array.isArray(entries))
12
12
  continue;
13
13
  for (const entry of entries) {
14
- if (!entry || typeof entry !== 'object')
14
+ if (!entry || typeof entry !== "object")
15
15
  continue;
16
16
  const slot = entry;
17
17
  const start = Number(slot.start);
@@ -34,7 +34,7 @@ function normalizeSlots(data) {
34
34
  return slots;
35
35
  }
36
36
  function formatSlotTime(value) {
37
- const padded = String(Math.trunc(value)).padStart(4, '0');
37
+ const padded = String(Math.trunc(value)).padStart(4, "0");
38
38
  const hours = padded.slice(0, 2);
39
39
  const minutes = padded.slice(2);
40
40
  return `${hours}:${minutes}`;
@@ -44,7 +44,7 @@ function formatSlotRange(start, end) {
44
44
  }
45
45
  function formatLoadPercent(value) {
46
46
  if (!Number.isFinite(value))
47
- return '0%';
47
+ return "0%";
48
48
  const rounded = Math.round(value * 10) / 10;
49
49
  return Number.isInteger(rounded) ? `${rounded}%` : `${rounded.toFixed(1)}%`;
50
50
  }
@@ -8,7 +8,7 @@ export type ProductSummary = {
8
8
  currency?: string;
9
9
  imageUrl?: string;
10
10
  urlSlug?: string;
11
- source?: 'list' | 'algolia';
11
+ source?: "list" | "algolia";
12
12
  listNames?: string[];
13
13
  };
14
14
  export type ProductDetail = Record<string, unknown>;