@yoryoboy/bi-mcp 1.4.0 → 1.5.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.
Files changed (71) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/index.js +244 -0
  3. package/dist/index.js.map +2 -2
  4. package/dist/mcp-use.json +2 -2
  5. package/dist/src/config/mercadolibre-profile-store.js +106 -0
  6. package/dist/src/config/mercadolibre-profile-store.js.map +7 -0
  7. package/dist/src/config/mercadolibre.js +153 -0
  8. package/dist/src/config/mercadolibre.js.map +7 -0
  9. package/dist/src/services/mercadolibre/mercadolibre-api.js +204 -0
  10. package/dist/src/services/mercadolibre/mercadolibre-api.js.map +7 -0
  11. package/dist/src/services/mercadolibre/mercadolibre-items.js +156 -0
  12. package/dist/src/services/mercadolibre/mercadolibre-items.js.map +7 -0
  13. package/dist/src/services/mercadolibre/mercadolibre-orders.js +65 -0
  14. package/dist/src/services/mercadolibre/mercadolibre-orders.js.map +7 -0
  15. package/dist/src/services/mercadolibre/mercadolibre-questions.js +28 -0
  16. package/dist/src/services/mercadolibre/mercadolibre-questions.js.map +7 -0
  17. package/dist/src/tools/index.js +1 -0
  18. package/dist/src/tools/index.js.map +2 -2
  19. package/dist/src/tools/mercadolibre/answer-question.js +37 -0
  20. package/dist/src/tools/mercadolibre/answer-question.js.map +7 -0
  21. package/dist/src/tools/mercadolibre/create-item.js +82 -0
  22. package/dist/src/tools/mercadolibre/create-item.js.map +7 -0
  23. package/dist/src/tools/mercadolibre/estimate-listing-fee.js +60 -0
  24. package/dist/src/tools/mercadolibre/estimate-listing-fee.js.map +7 -0
  25. package/dist/src/tools/mercadolibre/get-account-context.js +38 -0
  26. package/dist/src/tools/mercadolibre/get-account-context.js.map +7 -0
  27. package/dist/src/tools/mercadolibre/get-category-requirements.js +57 -0
  28. package/dist/src/tools/mercadolibre/get-category-requirements.js.map +7 -0
  29. package/dist/src/tools/mercadolibre/get-item-details.js +97 -0
  30. package/dist/src/tools/mercadolibre/get-item-details.js.map +7 -0
  31. package/dist/src/tools/mercadolibre/get-item-visits.js +92 -0
  32. package/dist/src/tools/mercadolibre/get-item-visits.js.map +7 -0
  33. package/dist/src/tools/mercadolibre/get-listing-quality.js +48 -0
  34. package/dist/src/tools/mercadolibre/get-listing-quality.js.map +7 -0
  35. package/dist/src/tools/mercadolibre/get-order-details.js +115 -0
  36. package/dist/src/tools/mercadolibre/get-order-details.js.map +7 -0
  37. package/dist/src/tools/mercadolibre/get-orders-summary.js +105 -0
  38. package/dist/src/tools/mercadolibre/get-orders-summary.js.map +7 -0
  39. package/dist/src/tools/mercadolibre/get-sales-by-item.js +97 -0
  40. package/dist/src/tools/mercadolibre/get-sales-by-item.js.map +7 -0
  41. package/dist/src/tools/mercadolibre/get-sales-trend.js +83 -0
  42. package/dist/src/tools/mercadolibre/get-sales-trend.js.map +7 -0
  43. package/dist/src/tools/mercadolibre/get-shipping-summary.js +115 -0
  44. package/dist/src/tools/mercadolibre/get-shipping-summary.js.map +7 -0
  45. package/dist/src/tools/mercadolibre/get-store-performance.js +102 -0
  46. package/dist/src/tools/mercadolibre/get-store-performance.js.map +7 -0
  47. package/dist/src/tools/mercadolibre/helpers.js +43 -0
  48. package/dist/src/tools/mercadolibre/helpers.js.map +7 -0
  49. package/dist/src/tools/mercadolibre/index.js +24 -0
  50. package/dist/src/tools/mercadolibre/index.js.map +7 -0
  51. package/dist/src/tools/mercadolibre/pause-or-reactivate-item.js +44 -0
  52. package/dist/src/tools/mercadolibre/pause-or-reactivate-item.js.map +7 -0
  53. package/dist/src/tools/mercadolibre/predict-category.js +49 -0
  54. package/dist/src/tools/mercadolibre/predict-category.js.map +7 -0
  55. package/dist/src/tools/mercadolibre/profile-resolution.js +56 -0
  56. package/dist/src/tools/mercadolibre/profile-resolution.js.map +7 -0
  57. package/dist/src/tools/mercadolibre/search-items.js +91 -0
  58. package/dist/src/tools/mercadolibre/search-items.js.map +7 -0
  59. package/dist/src/tools/mercadolibre/search-questions.js +71 -0
  60. package/dist/src/tools/mercadolibre/search-questions.js.map +7 -0
  61. package/dist/src/tools/mercadolibre/update-item-basic-fields.js +71 -0
  62. package/dist/src/tools/mercadolibre/update-item-basic-fields.js.map +7 -0
  63. package/dist/src/tools/mercadolibre/update-item-description.js +49 -0
  64. package/dist/src/tools/mercadolibre/update-item-description.js.map +7 -0
  65. package/dist/src/tools/mercadolibre/update-item-pictures.js +58 -0
  66. package/dist/src/tools/mercadolibre/update-item-pictures.js.map +7 -0
  67. package/dist/src/tools/mercadolibre/validate-connection.js +44 -0
  68. package/dist/src/tools/mercadolibre/validate-connection.js.map +7 -0
  69. package/dist/src/tools/mercadolibre/write-helpers.js +46 -0
  70. package/dist/src/tools/mercadolibre/write-helpers.js.map +7 -0
  71. package/package.json +1 -1
@@ -0,0 +1,106 @@
1
+ import { getDb } from "../db/client.js";
2
+ function mapMercadoLibreConnectionRow(row) {
3
+ return {
4
+ id: row.id,
5
+ profileId: row.profile_id,
6
+ profileName: row.profile_name,
7
+ sellerId: row.seller_id,
8
+ encryptedAccessToken: row.encrypted_access_token,
9
+ encryptedRefreshToken: row.encrypted_refresh_token,
10
+ expiresAt: row.expires_at,
11
+ scopes: Array.isArray(row.scopes) ? row.scopes : [],
12
+ status: row.status,
13
+ lastValidatedAt: row.last_validated_at,
14
+ lastError: row.last_error,
15
+ createdAt: row.created_at,
16
+ updatedAt: row.updated_at
17
+ };
18
+ }
19
+ async function getMercadoLibreConnectionRow(profileId) {
20
+ const result = await getDb().query(
21
+ `
22
+ select
23
+ pmc.id,
24
+ pmc.profile_id,
25
+ p.name as profile_name,
26
+ pmc.seller_id,
27
+ pmc.encrypted_access_token,
28
+ pmc.encrypted_refresh_token,
29
+ pmc.expires_at,
30
+ pmc.scopes,
31
+ pmc.status,
32
+ pmc.last_validated_at,
33
+ pmc.last_error,
34
+ pmc.created_at,
35
+ pmc.updated_at
36
+ from profile_mercadolibre_connections pmc
37
+ inner join profiles p on p.id = pmc.profile_id
38
+ where pmc.profile_id = $1
39
+ `,
40
+ [profileId]
41
+ );
42
+ const row = result.rows[0];
43
+ return row ? mapMercadoLibreConnectionRow(row) : null;
44
+ }
45
+ async function updateMercadoLibreConnectionTokens(input) {
46
+ const result = await getDb().query(
47
+ `
48
+ update profile_mercadolibre_connections
49
+ set
50
+ seller_id = $2,
51
+ encrypted_access_token = $3,
52
+ encrypted_refresh_token = $4,
53
+ expires_at = $5,
54
+ scopes = $6,
55
+ status = $7,
56
+ last_validated_at = now(),
57
+ last_error = $8,
58
+ updated_at = now()
59
+ where profile_id = $1
60
+ returning
61
+ id,
62
+ profile_id,
63
+ seller_id,
64
+ encrypted_access_token,
65
+ encrypted_refresh_token,
66
+ expires_at,
67
+ scopes,
68
+ status,
69
+ last_validated_at,
70
+ last_error,
71
+ created_at,
72
+ updated_at
73
+ `,
74
+ [
75
+ input.profileId,
76
+ input.sellerId,
77
+ input.encryptedAccessToken,
78
+ input.encryptedRefreshToken,
79
+ input.expiresAt,
80
+ input.scopes,
81
+ input.status,
82
+ input.lastError ?? null
83
+ ]
84
+ );
85
+ return mapMercadoLibreConnectionRow(result.rows[0]);
86
+ }
87
+ async function markMercadoLibreConnectionValidation(profileId, status, lastError) {
88
+ await getDb().query(
89
+ `
90
+ update profile_mercadolibre_connections
91
+ set
92
+ status = $2,
93
+ last_validated_at = now(),
94
+ last_error = $3,
95
+ updated_at = now()
96
+ where profile_id = $1
97
+ `,
98
+ [profileId, status, lastError ?? null]
99
+ );
100
+ }
101
+ export {
102
+ getMercadoLibreConnectionRow,
103
+ markMercadoLibreConnectionValidation,
104
+ updateMercadoLibreConnectionTokens
105
+ };
106
+ //# sourceMappingURL=mercadolibre-profile-store.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/config/mercadolibre-profile-store.ts"],
4
+ "sourcesContent": ["import { getDb } from \"../db/client.js\";\n\nexport type MercadoLibreConnectionStatus = \"active\" | \"disabled\" | \"expired\" | \"error\" | \"pending\";\n\nexport interface MercadoLibreConnectionRecord {\n id: string;\n profileId: string;\n profileName?: string;\n sellerId: string;\n encryptedAccessToken: string;\n encryptedRefreshToken: string | null;\n expiresAt: Date | null;\n scopes: string[];\n status: MercadoLibreConnectionStatus;\n lastValidatedAt: Date | null;\n lastError: string | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\ninterface MercadoLibreConnectionRow {\n id: string;\n profile_id: string;\n profile_name?: string;\n seller_id: string;\n encrypted_access_token: string;\n encrypted_refresh_token: string | null;\n expires_at: Date | null;\n scopes: string[] | null;\n status: MercadoLibreConnectionStatus;\n last_validated_at: Date | null;\n last_error: string | null;\n created_at: Date;\n updated_at: Date;\n}\n\nfunction mapMercadoLibreConnectionRow(\n row: MercadoLibreConnectionRow\n): MercadoLibreConnectionRecord {\n return {\n id: row.id,\n profileId: row.profile_id,\n profileName: row.profile_name,\n sellerId: row.seller_id,\n encryptedAccessToken: row.encrypted_access_token,\n encryptedRefreshToken: row.encrypted_refresh_token,\n expiresAt: row.expires_at,\n scopes: Array.isArray(row.scopes) ? row.scopes : [],\n status: row.status,\n lastValidatedAt: row.last_validated_at,\n lastError: row.last_error,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\nexport async function getMercadoLibreConnectionRow(\n profileId: string\n): Promise<MercadoLibreConnectionRecord | null> {\n const result = await getDb().query<MercadoLibreConnectionRow>(\n `\n select\n pmc.id,\n pmc.profile_id,\n p.name as profile_name,\n pmc.seller_id,\n pmc.encrypted_access_token,\n pmc.encrypted_refresh_token,\n pmc.expires_at,\n pmc.scopes,\n pmc.status,\n pmc.last_validated_at,\n pmc.last_error,\n pmc.created_at,\n pmc.updated_at\n from profile_mercadolibre_connections pmc\n inner join profiles p on p.id = pmc.profile_id\n where pmc.profile_id = $1\n `,\n [profileId]\n );\n\n const row = result.rows[0];\n return row ? mapMercadoLibreConnectionRow(row) : null;\n}\n\nexport async function updateMercadoLibreConnectionTokens(input: {\n profileId: string;\n sellerId: string;\n encryptedAccessToken: string;\n encryptedRefreshToken: string | null;\n expiresAt: Date | null;\n scopes: string[];\n status: MercadoLibreConnectionStatus;\n lastError?: string | null;\n}): Promise<MercadoLibreConnectionRecord> {\n const result = await getDb().query<MercadoLibreConnectionRow>(\n `\n update profile_mercadolibre_connections\n set\n seller_id = $2,\n encrypted_access_token = $3,\n encrypted_refresh_token = $4,\n expires_at = $5,\n scopes = $6,\n status = $7,\n last_validated_at = now(),\n last_error = $8,\n updated_at = now()\n where profile_id = $1\n returning\n id,\n profile_id,\n seller_id,\n encrypted_access_token,\n encrypted_refresh_token,\n expires_at,\n scopes,\n status,\n last_validated_at,\n last_error,\n created_at,\n updated_at\n `,\n [\n input.profileId,\n input.sellerId,\n input.encryptedAccessToken,\n input.encryptedRefreshToken,\n input.expiresAt,\n input.scopes,\n input.status,\n input.lastError ?? null,\n ]\n );\n\n return mapMercadoLibreConnectionRow(result.rows[0]);\n}\n\nexport async function markMercadoLibreConnectionValidation(\n profileId: string,\n status: MercadoLibreConnectionStatus,\n lastError?: string | null\n): Promise<void> {\n await getDb().query(\n `\n update profile_mercadolibre_connections\n set\n status = $2,\n last_validated_at = now(),\n last_error = $3,\n updated_at = now()\n where profile_id = $1\n `,\n [profileId, status, lastError ?? null]\n );\n}\n"],
5
+ "mappings": "AAAA,SAAS,aAAa;AAoCtB,SAAS,6BACP,KAC8B;AAC9B,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,WAAW,IAAI;AAAA,IACf,aAAa,IAAI;AAAA,IACjB,UAAU,IAAI;AAAA,IACd,sBAAsB,IAAI;AAAA,IAC1B,uBAAuB,IAAI;AAAA,IAC3B,WAAW,IAAI;AAAA,IACf,QAAQ,MAAM,QAAQ,IAAI,MAAM,IAAI,IAAI,SAAS,CAAC;AAAA,IAClD,QAAQ,IAAI;AAAA,IACZ,iBAAiB,IAAI;AAAA,IACrB,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,IACf,WAAW,IAAI;AAAA,EACjB;AACF;AAEA,eAAsB,6BACpB,WAC8C;AAC9C,QAAM,SAAS,MAAM,MAAM,EAAE;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAmBA,CAAC,SAAS;AAAA,EACZ;AAEA,QAAM,MAAM,OAAO,KAAK,CAAC;AACzB,SAAO,MAAM,6BAA6B,GAAG,IAAI;AACnD;AAEA,eAAsB,mCAAmC,OASf;AACxC,QAAM,SAAS,MAAM,MAAM,EAAE;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IA2BA;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM,aAAa;AAAA,IACrB;AAAA,EACF;AAEA,SAAO,6BAA6B,OAAO,KAAK,CAAC,CAAC;AACpD;AAEA,eAAsB,qCACpB,WACA,QACA,WACe;AACf,QAAM,MAAM,EAAE;AAAA,IACZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASA,CAAC,WAAW,QAAQ,aAAa,IAAI;AAAA,EACvC;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,153 @@
1
+ import { assertActiveProfile } from "./profile-store.js";
2
+ import { decryptSecret, encryptSecret } from "./vtex-crypto.js";
3
+ import {
4
+ getMercadoLibreConnectionRow,
5
+ markMercadoLibreConnectionValidation,
6
+ updateMercadoLibreConnectionTokens
7
+ } from "./mercadolibre-profile-store.js";
8
+ const TOKEN_URL = "https://api.mercadolibre.com/oauth/token";
9
+ const REFRESH_SKEW_MS = 5 * 60 * 1e3;
10
+ function env(name) {
11
+ const value = process.env[name]?.trim();
12
+ if (!value) {
13
+ throw new Error(`Missing ${name}`);
14
+ }
15
+ return value;
16
+ }
17
+ function sanitizeMercadoLibreText(value) {
18
+ const raw = typeof value === "string" ? value : String(value ?? "MercadoLibre request failed");
19
+ return raw.replace(/access_token[=:][^\s&]+/gi, "access_token=[redacted]").replace(/refresh_token[=:][^\s&]+/gi, "refresh_token=[redacted]").replace(/client_secret[=:][^\s&]+/gi, "client_secret=[redacted]").replace(/code[=:][^\s&]+/gi, "code=[redacted]").replace(/Bearer\s+[A-Za-z0-9._-]+/gi, "Bearer [redacted]").slice(0, 220);
20
+ }
21
+ function parseScopes(scope) {
22
+ if (typeof scope !== "string") {
23
+ return [];
24
+ }
25
+ return scope.split(/[ ,]+/).map((item) => item.trim()).filter(Boolean);
26
+ }
27
+ function expiresAtFrom(expiresIn) {
28
+ if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn) || expiresIn <= 0) {
29
+ return null;
30
+ }
31
+ return new Date(Date.now() + expiresIn * 1e3).toISOString();
32
+ }
33
+ function shouldRefreshMercadoLibreToken(expiresAt) {
34
+ if (!expiresAt) {
35
+ return false;
36
+ }
37
+ return expiresAt.getTime() <= Date.now() + REFRESH_SKEW_MS;
38
+ }
39
+ async function readRefreshError(response) {
40
+ const body = await response.json().catch(() => null);
41
+ if (body && typeof body === "object") {
42
+ const candidate = body;
43
+ const code = typeof candidate.error === "string" ? candidate.error : response.statusText;
44
+ const message = sanitizeMercadoLibreText(candidate.message) || sanitizeMercadoLibreText(candidate.error_description) || sanitizeMercadoLibreText(code);
45
+ return new Error(`MercadoLibre token refresh failed (${response.status}): ${message}`);
46
+ }
47
+ return new Error(
48
+ `MercadoLibre token refresh failed (${response.status}): ${sanitizeMercadoLibreText(response.statusText)}`
49
+ );
50
+ }
51
+ async function refreshMercadoLibreToken(refreshToken) {
52
+ const response = await fetch(TOKEN_URL, {
53
+ method: "POST",
54
+ headers: {
55
+ Accept: "application/json",
56
+ "Content-Type": "application/x-www-form-urlencoded"
57
+ },
58
+ body: new URLSearchParams({
59
+ grant_type: "refresh_token",
60
+ client_id: env("MERCADOLIBRE_CLIENT_ID"),
61
+ client_secret: env("MERCADOLIBRE_CLIENT_SECRET"),
62
+ refresh_token: refreshToken
63
+ })
64
+ });
65
+ if (!response.ok) {
66
+ throw await readRefreshError(response);
67
+ }
68
+ const payload = await response.json();
69
+ const accessToken = payload.access_token;
70
+ const userId = payload.user_id;
71
+ if (typeof accessToken !== "string" || !accessToken) {
72
+ throw new Error("MercadoLibre token refresh response missing access_token");
73
+ }
74
+ if (typeof userId !== "string" && typeof userId !== "number" || String(userId).length === 0) {
75
+ throw new Error("MercadoLibre token refresh response missing user_id");
76
+ }
77
+ return {
78
+ accessToken,
79
+ refreshToken: typeof payload.refresh_token === "string" && payload.refresh_token ? payload.refresh_token : refreshToken,
80
+ userId: String(userId),
81
+ expiresAt: expiresAtFrom(payload.expires_in),
82
+ scopes: parseScopes(payload.scope)
83
+ };
84
+ }
85
+ async function getMercadoLibreAccessForProfile(profileId) {
86
+ await assertActiveProfile(profileId);
87
+ const connection = await getMercadoLibreConnectionRow(profileId);
88
+ if (!connection) {
89
+ throw new Error(`MercadoLibre connection not found for profileId "${profileId}"`);
90
+ }
91
+ if (connection.status === "disabled") {
92
+ throw new Error(`MercadoLibre connection for profileId "${profileId}" is disabled`);
93
+ }
94
+ try {
95
+ let accessToken = decryptSecret(connection.encryptedAccessToken);
96
+ let refreshToken = connection.encryptedRefreshToken ? decryptSecret(connection.encryptedRefreshToken) : null;
97
+ let scopes = connection.scopes;
98
+ let expiresAt = connection.expiresAt ? connection.expiresAt.toISOString() : null;
99
+ if (shouldRefreshMercadoLibreToken(connection.expiresAt)) {
100
+ if (!refreshToken) {
101
+ await markMercadoLibreConnectionValidation(profileId, "expired", "Refresh token is missing");
102
+ throw new Error(
103
+ `MercadoLibre connection for profileId "${profileId}" is expired and has no refresh token`
104
+ );
105
+ }
106
+ const refreshedTokens = await refreshMercadoLibreToken(refreshToken);
107
+ if (refreshedTokens.userId !== connection.sellerId) {
108
+ await markMercadoLibreConnectionValidation(
109
+ profileId,
110
+ "error",
111
+ "Refreshed MercadoLibre seller_id does not match stored seller_id"
112
+ );
113
+ throw new Error(
114
+ `MercadoLibre refresh returned seller_id "${refreshedTokens.userId}" but expected "${connection.sellerId}"`
115
+ );
116
+ }
117
+ const encryptedAccessToken = encryptSecret(refreshedTokens.accessToken);
118
+ const encryptedRefreshToken = refreshedTokens.refreshToken ? encryptSecret(refreshedTokens.refreshToken) : null;
119
+ const nextExpiresAt = refreshedTokens.expiresAt ? new Date(refreshedTokens.expiresAt) : null;
120
+ await updateMercadoLibreConnectionTokens({
121
+ profileId,
122
+ sellerId: connection.sellerId,
123
+ encryptedAccessToken,
124
+ encryptedRefreshToken,
125
+ expiresAt: nextExpiresAt,
126
+ scopes: refreshedTokens.scopes,
127
+ status: "active",
128
+ lastError: null
129
+ });
130
+ accessToken = refreshedTokens.accessToken;
131
+ refreshToken = refreshedTokens.refreshToken;
132
+ scopes = refreshedTokens.scopes;
133
+ expiresAt = refreshedTokens.expiresAt;
134
+ }
135
+ await markMercadoLibreConnectionValidation(profileId, "active", null);
136
+ return {
137
+ profileId,
138
+ sellerId: connection.sellerId,
139
+ accessToken,
140
+ scopes,
141
+ expiresAt
142
+ };
143
+ } catch (error) {
144
+ const message = sanitizeMercadoLibreText(error instanceof Error ? error.message : error);
145
+ const status = message.toLowerCase().includes("expired") ? "expired" : "error";
146
+ await markMercadoLibreConnectionValidation(profileId, status, message);
147
+ throw new Error(message);
148
+ }
149
+ }
150
+ export {
151
+ getMercadoLibreAccessForProfile
152
+ };
153
+ //# sourceMappingURL=mercadolibre.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/config/mercadolibre.ts"],
4
+ "sourcesContent": ["import { assertActiveProfile } from \"./profile-store.js\";\nimport { decryptSecret, encryptSecret } from \"./vtex-crypto.js\";\nimport {\n getMercadoLibreConnectionRow,\n markMercadoLibreConnectionValidation,\n updateMercadoLibreConnectionTokens,\n} from \"./mercadolibre-profile-store.js\";\n\nconst TOKEN_URL = \"https://api.mercadolibre.com/oauth/token\";\nconst REFRESH_SKEW_MS = 5 * 60 * 1000;\n\nexport interface MercadoLibreAccessContext {\n profileId: string;\n sellerId: string;\n accessToken: string;\n scopes: string[];\n expiresAt: string | null;\n}\n\ninterface MercadoLibreTokenSet {\n accessToken: string;\n refreshToken: string | null;\n userId: string;\n expiresAt: string | null;\n scopes: string[];\n}\n\nfunction env(name: string): string {\n const value = process.env[name]?.trim();\n if (!value) {\n throw new Error(`Missing ${name}`);\n }\n\n return value;\n}\n\nfunction sanitizeMercadoLibreText(value: unknown): string {\n const raw = typeof value === \"string\" ? value : String(value ?? \"MercadoLibre request failed\");\n return raw\n .replace(/access_token[=:][^\\s&]+/gi, \"access_token=[redacted]\")\n .replace(/refresh_token[=:][^\\s&]+/gi, \"refresh_token=[redacted]\")\n .replace(/client_secret[=:][^\\s&]+/gi, \"client_secret=[redacted]\")\n .replace(/code[=:][^\\s&]+/gi, \"code=[redacted]\")\n .replace(/Bearer\\s+[A-Za-z0-9._-]+/gi, \"Bearer [redacted]\")\n .slice(0, 220);\n}\n\nfunction parseScopes(scope: unknown): string[] {\n if (typeof scope !== \"string\") {\n return [];\n }\n\n return scope\n .split(/[ ,]+/)\n .map((item) => item.trim())\n .filter(Boolean);\n}\n\nfunction expiresAtFrom(expiresIn: unknown): string | null {\n if (typeof expiresIn !== \"number\" || !Number.isFinite(expiresIn) || expiresIn <= 0) {\n return null;\n }\n\n return new Date(Date.now() + expiresIn * 1000).toISOString();\n}\n\nfunction shouldRefreshMercadoLibreToken(expiresAt: Date | null): boolean {\n if (!expiresAt) {\n return false;\n }\n\n return expiresAt.getTime() <= Date.now() + REFRESH_SKEW_MS;\n}\n\nasync function readRefreshError(response: Response): Promise<Error> {\n const body = await response.json().catch(() => null);\n if (body && typeof body === \"object\") {\n const candidate = body as {\n error?: unknown;\n message?: unknown;\n error_description?: unknown;\n };\n const code = typeof candidate.error === \"string\" ? candidate.error : response.statusText;\n const message =\n sanitizeMercadoLibreText(candidate.message) ||\n sanitizeMercadoLibreText(candidate.error_description) ||\n sanitizeMercadoLibreText(code);\n return new Error(`MercadoLibre token refresh failed (${response.status}): ${message}`);\n }\n\n return new Error(\n `MercadoLibre token refresh failed (${response.status}): ${sanitizeMercadoLibreText(response.statusText)}`\n );\n}\n\nasync function refreshMercadoLibreToken(refreshToken: string): Promise<MercadoLibreTokenSet> {\n const response = await fetch(TOKEN_URL, {\n method: \"POST\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: new URLSearchParams({\n grant_type: \"refresh_token\",\n client_id: env(\"MERCADOLIBRE_CLIENT_ID\"),\n client_secret: env(\"MERCADOLIBRE_CLIENT_SECRET\"),\n refresh_token: refreshToken,\n }),\n });\n\n if (!response.ok) {\n throw await readRefreshError(response);\n }\n\n const payload = (await response.json()) as Record<string, unknown>;\n const accessToken = payload.access_token;\n const userId = payload.user_id;\n\n if (typeof accessToken !== \"string\" || !accessToken) {\n throw new Error(\"MercadoLibre token refresh response missing access_token\");\n }\n\n if ((typeof userId !== \"string\" && typeof userId !== \"number\") || String(userId).length === 0) {\n throw new Error(\"MercadoLibre token refresh response missing user_id\");\n }\n\n return {\n accessToken,\n refreshToken:\n typeof payload.refresh_token === \"string\" && payload.refresh_token\n ? payload.refresh_token\n : refreshToken,\n userId: String(userId),\n expiresAt: expiresAtFrom(payload.expires_in),\n scopes: parseScopes(payload.scope),\n };\n}\n\nexport async function getMercadoLibreAccessForProfile(\n profileId: string\n): Promise<MercadoLibreAccessContext> {\n await assertActiveProfile(profileId);\n\n const connection = await getMercadoLibreConnectionRow(profileId);\n if (!connection) {\n throw new Error(`MercadoLibre connection not found for profileId \"${profileId}\"`);\n }\n\n if (connection.status === \"disabled\") {\n throw new Error(`MercadoLibre connection for profileId \"${profileId}\" is disabled`);\n }\n\n try {\n let accessToken = decryptSecret(connection.encryptedAccessToken);\n let refreshToken = connection.encryptedRefreshToken\n ? decryptSecret(connection.encryptedRefreshToken)\n : null;\n let scopes = connection.scopes;\n let expiresAt = connection.expiresAt ? connection.expiresAt.toISOString() : null;\n\n if (shouldRefreshMercadoLibreToken(connection.expiresAt)) {\n if (!refreshToken) {\n await markMercadoLibreConnectionValidation(profileId, \"expired\", \"Refresh token is missing\");\n throw new Error(\n `MercadoLibre connection for profileId \"${profileId}\" is expired and has no refresh token`\n );\n }\n\n const refreshedTokens = await refreshMercadoLibreToken(refreshToken);\n if (refreshedTokens.userId !== connection.sellerId) {\n await markMercadoLibreConnectionValidation(\n profileId,\n \"error\",\n \"Refreshed MercadoLibre seller_id does not match stored seller_id\"\n );\n throw new Error(\n `MercadoLibre refresh returned seller_id \"${refreshedTokens.userId}\" but expected \"${connection.sellerId}\"`\n );\n }\n\n const encryptedAccessToken = encryptSecret(refreshedTokens.accessToken);\n const encryptedRefreshToken = refreshedTokens.refreshToken\n ? encryptSecret(refreshedTokens.refreshToken)\n : null;\n const nextExpiresAt = refreshedTokens.expiresAt ? new Date(refreshedTokens.expiresAt) : null;\n\n await updateMercadoLibreConnectionTokens({\n profileId,\n sellerId: connection.sellerId,\n encryptedAccessToken,\n encryptedRefreshToken,\n expiresAt: nextExpiresAt,\n scopes: refreshedTokens.scopes,\n status: \"active\",\n lastError: null,\n });\n\n accessToken = refreshedTokens.accessToken;\n refreshToken = refreshedTokens.refreshToken;\n scopes = refreshedTokens.scopes;\n expiresAt = refreshedTokens.expiresAt;\n }\n\n await markMercadoLibreConnectionValidation(profileId, \"active\", null);\n\n return {\n profileId,\n sellerId: connection.sellerId,\n accessToken,\n scopes,\n expiresAt,\n };\n } catch (error) {\n const message = sanitizeMercadoLibreText(error instanceof Error ? error.message : error);\n\n const status = message.toLowerCase().includes(\"expired\") ? \"expired\" : \"error\";\n await markMercadoLibreConnectionValidation(profileId, status, message);\n\n throw new Error(message);\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,204 @@
1
+ import axios from "axios";
2
+ import { getMercadoLibreAccessForProfile } from "../../config/mercadolibre.js";
3
+ const BASE_URL = "https://api.mercadolibre.com";
4
+ const RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
5
+ class MercadoLibreApiError extends Error {
6
+ constructor(message, status, code, retryAfterMs) {
7
+ super(message);
8
+ this.status = status;
9
+ this.code = code;
10
+ this.retryAfterMs = retryAfterMs;
11
+ this.name = "MercadoLibreApiError";
12
+ }
13
+ }
14
+ function sanitizeMercadoLibreText(value) {
15
+ const raw = typeof value === "string" ? value : String(value ?? "MercadoLibre request failed");
16
+ return raw.replace(/access_token[=:][^\s&]+/gi, "access_token=[redacted]").replace(/refresh_token[=:][^\s&]+/gi, "refresh_token=[redacted]").replace(/client_secret[=:][^\s&]+/gi, "client_secret=[redacted]").replace(/code[=:][^\s&]+/gi, "code=[redacted]").replace(/Bearer\s+[A-Za-z0-9._-]+/gi, "Bearer [redacted]").slice(0, 220);
17
+ }
18
+ function extractRetryAfterMs(error) {
19
+ const rawHeader = error.response?.headers?.["retry-after"];
20
+ const candidate = Array.isArray(rawHeader) ? rawHeader[0] : rawHeader;
21
+ if (!candidate) {
22
+ return void 0;
23
+ }
24
+ const seconds = Number(candidate);
25
+ if (Number.isFinite(seconds) && seconds > 0) {
26
+ return seconds * 1e3;
27
+ }
28
+ const dateMs = new Date(candidate).getTime();
29
+ if (Number.isFinite(dateMs)) {
30
+ return Math.max(0, dateMs - Date.now());
31
+ }
32
+ return void 0;
33
+ }
34
+ function buildApiError(error) {
35
+ const status = error.response?.status;
36
+ const responseData = error.response?.data;
37
+ let code;
38
+ let message;
39
+ if (responseData && typeof responseData === "object") {
40
+ const candidate = responseData;
41
+ code = typeof candidate.error === "string" ? candidate.error : void 0;
42
+ message = (typeof candidate.message === "string" ? candidate.message : void 0) ?? (typeof candidate.error_description === "string" ? candidate.error_description : void 0) ?? (Array.isArray(candidate.cause) ? candidate.cause.map((entry) => typeof entry.message === "string" ? entry.message : null).filter(Boolean).join("; ") : void 0);
43
+ } else if (typeof responseData === "string") {
44
+ message = responseData;
45
+ }
46
+ const safeMessage = sanitizeMercadoLibreText(message ?? error.message ?? "MercadoLibre request failed");
47
+ return new MercadoLibreApiError(
48
+ `MercadoLibre API request failed${status ? ` (${status})` : ""}: ${safeMessage}`,
49
+ status,
50
+ code,
51
+ extractRetryAfterMs(error)
52
+ );
53
+ }
54
+ function formatMercadoLibreError(error, fallback = "Unexpected MercadoLibre API error") {
55
+ if (error instanceof MercadoLibreApiError) {
56
+ return error.message;
57
+ }
58
+ if (axios.isAxiosError(error)) {
59
+ return buildApiError(error).message;
60
+ }
61
+ if (error instanceof Error) {
62
+ return sanitizeMercadoLibreText(error.message);
63
+ }
64
+ return sanitizeMercadoLibreText(fallback);
65
+ }
66
+ function createMercadoLibreClient(accessToken) {
67
+ const client = axios.create({
68
+ baseURL: BASE_URL,
69
+ timeout: 3e4,
70
+ headers: {
71
+ Accept: "application/json",
72
+ "Content-Type": "application/json",
73
+ Authorization: `Bearer ${accessToken}`
74
+ }
75
+ });
76
+ client.interceptors.response.use(
77
+ (response) => response,
78
+ (error) => {
79
+ const method = error.config?.method?.toUpperCase() ?? "UNKNOWN";
80
+ const url = error.config?.url ?? "UNKNOWN_URL";
81
+ const status = error.response?.status ?? "NO_STATUS";
82
+ console.error(`[MERCADOLIBRE API ERROR] ${method} ${url} -> ${status}`);
83
+ return Promise.reject(buildApiError(error));
84
+ }
85
+ );
86
+ return client;
87
+ }
88
+ async function getMercadoLibreApiContext(profileId) {
89
+ const access = await getMercadoLibreAccessForProfile(profileId);
90
+ return {
91
+ ...access,
92
+ api: createMercadoLibreClient(access.accessToken)
93
+ };
94
+ }
95
+ async function getMercadoLibreUsersMe(profileId) {
96
+ const { api } = await getMercadoLibreApiContext(profileId);
97
+ const response = await api.get("/users/me");
98
+ return response.data;
99
+ }
100
+ function isRetryableMercadoLibreError(error) {
101
+ if (error instanceof MercadoLibreApiError) {
102
+ return error.status ? RETRYABLE_STATUS_CODES.has(error.status) : true;
103
+ }
104
+ return false;
105
+ }
106
+ function classifyMercadoLibreBatchFailure(id, error, attempts) {
107
+ const statusCode = error instanceof MercadoLibreApiError ? error.status : void 0;
108
+ return {
109
+ id,
110
+ message: formatMercadoLibreError(error, `Failed to fetch MercadoLibre resource ${id}`),
111
+ statusCode,
112
+ attempts,
113
+ retryable: isRetryableMercadoLibreError(error)
114
+ };
115
+ }
116
+ function sleep(ms) {
117
+ return new Promise((resolve) => setTimeout(resolve, ms));
118
+ }
119
+ async function withMercadoLibreRetry(id, fetcher, options = {}) {
120
+ const maxRetries = Math.max(0, Math.floor(options.maxRetries ?? 2));
121
+ const baseRetryDelayMs = Math.max(50, Math.floor(options.baseRetryDelayMs ?? 250));
122
+ let attempts = 0;
123
+ while (true) {
124
+ attempts += 1;
125
+ try {
126
+ const document = await fetcher(id);
127
+ return { id, document };
128
+ } catch (error) {
129
+ const retryable = isRetryableMercadoLibreError(error);
130
+ if (!retryable || attempts > maxRetries + 1) {
131
+ throw classifyMercadoLibreBatchFailure(id, error, attempts);
132
+ }
133
+ const retryAfterMs = error instanceof MercadoLibreApiError ? error.retryAfterMs : void 0;
134
+ const jitterMs = Math.floor(Math.random() * 100);
135
+ const delayMs = retryAfterMs ?? baseRetryDelayMs * 2 ** (attempts - 1) + jitterMs;
136
+ await sleep(delayMs);
137
+ }
138
+ }
139
+ }
140
+ async function runMercadoLibreBatch(ids, fetcher, options = {}) {
141
+ const maxConcurrency = Math.max(1, Math.floor(options.maxConcurrency ?? 10));
142
+ const successful = [];
143
+ const failed = [];
144
+ for (let start = 0; start < ids.length; start += maxConcurrency) {
145
+ const chunk = ids.slice(start, start + maxConcurrency);
146
+ const settled = await Promise.allSettled(
147
+ chunk.map((id) => withMercadoLibreRetry(id, fetcher, options))
148
+ );
149
+ settled.forEach((result, index) => {
150
+ const id = chunk[index];
151
+ if (result.status === "fulfilled") {
152
+ successful.push(result.value);
153
+ return;
154
+ }
155
+ const reason = result.reason;
156
+ if (reason && typeof reason === "object" && "id" in reason && "attempts" in reason && "message" in reason) {
157
+ failed.push(reason);
158
+ return;
159
+ }
160
+ failed.push(classifyMercadoLibreBatchFailure(id, reason, 1));
161
+ });
162
+ }
163
+ return { successful, failed };
164
+ }
165
+ function normalizeMercadoLibrePaging(input) {
166
+ const paging = input ?? {};
167
+ const total = Number(paging.total ?? 0);
168
+ const limit = Number(paging.limit ?? 0);
169
+ const offset = Number(paging.offset ?? 0);
170
+ return {
171
+ total: Number.isFinite(total) ? Math.max(0, total) : 0,
172
+ limit: Number.isFinite(limit) ? Math.max(0, limit) : 0,
173
+ offset: Number.isFinite(offset) ? Math.max(0, offset) : 0
174
+ };
175
+ }
176
+ function buildMercadoLibrePaginationMetadata(params) {
177
+ const effectiveLimit = params.limit > 0 ? params.limit : params.returned;
178
+ const hasMore = params.offset + params.returned < params.total;
179
+ return {
180
+ total: params.total,
181
+ limit: effectiveLimit,
182
+ offset: params.offset,
183
+ returned: params.returned,
184
+ has_more: hasMore,
185
+ continuation: hasMore && effectiveLimit > 0 ? `Reenviar la misma tool con ${params.nextField}=${params.offset + effectiveLimit} para continuar.` : "No more pages for this query."
186
+ };
187
+ }
188
+ async function parseMercadoLibreMultiget(response) {
189
+ return response.data.filter((entry) => Number(entry.code ?? 0) >= 200 && Number(entry.code ?? 0) < 300 && entry.body).map((entry) => entry.body);
190
+ }
191
+ export {
192
+ MercadoLibreApiError,
193
+ buildMercadoLibrePaginationMetadata,
194
+ createMercadoLibreClient,
195
+ formatMercadoLibreError,
196
+ getMercadoLibreApiContext,
197
+ getMercadoLibreUsersMe,
198
+ isRetryableMercadoLibreError,
199
+ normalizeMercadoLibrePaging,
200
+ parseMercadoLibreMultiget,
201
+ runMercadoLibreBatch,
202
+ sanitizeMercadoLibreText
203
+ };
204
+ //# sourceMappingURL=mercadolibre-api.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/services/mercadolibre/mercadolibre-api.ts"],
4
+ "sourcesContent": ["import axios from \"axios\";\nimport type { AxiosError, AxiosInstance, AxiosResponse } from \"axios\";\n\nimport { getMercadoLibreAccessForProfile } from \"../../config/mercadolibre.js\";\n\nconst BASE_URL = \"https://api.mercadolibre.com\";\nconst RETRYABLE_STATUS_CODES = new Set([408, 429, 500, 502, 503, 504]);\n\nexport class MercadoLibreApiError extends Error {\n constructor(\n message: string,\n public readonly status?: number,\n public readonly code?: string,\n public readonly retryAfterMs?: number\n ) {\n super(message);\n this.name = \"MercadoLibreApiError\";\n }\n}\n\nexport interface MercadoLibreBatchFetchOptions {\n maxConcurrency?: number;\n maxRetries?: number;\n baseRetryDelayMs?: number;\n}\n\nexport interface MercadoLibreDocumentSuccess<T> {\n id: string;\n document: T;\n}\n\nexport interface MercadoLibreDocumentFailure {\n id: string;\n message: string;\n statusCode?: number;\n attempts: number;\n retryable: boolean;\n}\n\nexport interface MercadoLibreDocumentsBatchResult<T> {\n successful: Array<MercadoLibreDocumentSuccess<T>>;\n failed: MercadoLibreDocumentFailure[];\n}\n\nexport function sanitizeMercadoLibreText(value: unknown): string {\n const raw = typeof value === \"string\" ? value : String(value ?? \"MercadoLibre request failed\");\n return raw\n .replace(/access_token[=:][^\\s&]+/gi, \"access_token=[redacted]\")\n .replace(/refresh_token[=:][^\\s&]+/gi, \"refresh_token=[redacted]\")\n .replace(/client_secret[=:][^\\s&]+/gi, \"client_secret=[redacted]\")\n .replace(/code[=:][^\\s&]+/gi, \"code=[redacted]\")\n .replace(/Bearer\\s+[A-Za-z0-9._-]+/gi, \"Bearer [redacted]\")\n .slice(0, 220);\n}\n\nfunction extractRetryAfterMs(error: AxiosError): number | undefined {\n const rawHeader = error.response?.headers?.[\"retry-after\"];\n const candidate = Array.isArray(rawHeader) ? rawHeader[0] : rawHeader;\n if (!candidate) {\n return undefined;\n }\n\n const seconds = Number(candidate);\n if (Number.isFinite(seconds) && seconds > 0) {\n return seconds * 1000;\n }\n\n const dateMs = new Date(candidate).getTime();\n if (Number.isFinite(dateMs)) {\n return Math.max(0, dateMs - Date.now());\n }\n\n return undefined;\n}\n\nfunction buildApiError(error: AxiosError): MercadoLibreApiError {\n const status = error.response?.status;\n const responseData = error.response?.data;\n let code: string | undefined;\n let message: string | undefined;\n\n if (responseData && typeof responseData === \"object\") {\n const candidate = responseData as {\n error?: unknown;\n message?: unknown;\n error_description?: unknown;\n cause?: Array<{ code?: unknown; message?: unknown }>;\n };\n code = typeof candidate.error === \"string\" ? candidate.error : undefined;\n message =\n (typeof candidate.message === \"string\" ? candidate.message : undefined) ??\n (typeof candidate.error_description === \"string\" ? candidate.error_description : undefined) ??\n (Array.isArray(candidate.cause)\n ? candidate.cause\n .map((entry) => (typeof entry.message === \"string\" ? entry.message : null))\n .filter(Boolean)\n .join(\"; \")\n : undefined);\n } else if (typeof responseData === \"string\") {\n message = responseData;\n }\n\n const safeMessage = sanitizeMercadoLibreText(message ?? error.message ?? \"MercadoLibre request failed\");\n return new MercadoLibreApiError(\n `MercadoLibre API request failed${status ? ` (${status})` : \"\"}: ${safeMessage}`,\n status,\n code,\n extractRetryAfterMs(error)\n );\n}\n\nexport function formatMercadoLibreError(\n error: unknown,\n fallback = \"Unexpected MercadoLibre API error\"\n): string {\n if (error instanceof MercadoLibreApiError) {\n return error.message;\n }\n\n if (axios.isAxiosError(error)) {\n return buildApiError(error).message;\n }\n\n if (error instanceof Error) {\n return sanitizeMercadoLibreText(error.message);\n }\n\n return sanitizeMercadoLibreText(fallback);\n}\n\nexport function createMercadoLibreClient(accessToken: string): AxiosInstance {\n const client = axios.create({\n baseURL: BASE_URL,\n timeout: 30000,\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${accessToken}`,\n },\n });\n\n client.interceptors.response.use(\n (response) => response,\n (error: AxiosError) => {\n const method = error.config?.method?.toUpperCase() ?? \"UNKNOWN\";\n const url = error.config?.url ?? \"UNKNOWN_URL\";\n const status = error.response?.status ?? \"NO_STATUS\";\n console.error(`[MERCADOLIBRE API ERROR] ${method} ${url} -> ${status}`);\n return Promise.reject(buildApiError(error));\n }\n );\n\n return client;\n}\n\nexport async function getMercadoLibreApiContext(profileId: string) {\n const access = await getMercadoLibreAccessForProfile(profileId);\n\n return {\n ...access,\n api: createMercadoLibreClient(access.accessToken),\n };\n}\n\nexport async function getMercadoLibreUsersMe(profileId: string): Promise<Record<string, unknown>> {\n const { api } = await getMercadoLibreApiContext(profileId);\n const response = await api.get<Record<string, unknown>>(\"/users/me\");\n return response.data;\n}\n\nexport function isRetryableMercadoLibreError(error: unknown): boolean {\n if (error instanceof MercadoLibreApiError) {\n return error.status ? RETRYABLE_STATUS_CODES.has(error.status) : true;\n }\n\n return false;\n}\n\nfunction classifyMercadoLibreBatchFailure(\n id: string,\n error: unknown,\n attempts: number\n): MercadoLibreDocumentFailure {\n const statusCode = error instanceof MercadoLibreApiError ? error.status : undefined;\n return {\n id,\n message: formatMercadoLibreError(error, `Failed to fetch MercadoLibre resource ${id}`),\n statusCode,\n attempts,\n retryable: isRetryableMercadoLibreError(error),\n };\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function withMercadoLibreRetry<T>(\n id: string,\n fetcher: (id: string) => Promise<T>,\n options: MercadoLibreBatchFetchOptions = {}\n): Promise<MercadoLibreDocumentSuccess<T>> {\n const maxRetries = Math.max(0, Math.floor(options.maxRetries ?? 2));\n const baseRetryDelayMs = Math.max(50, Math.floor(options.baseRetryDelayMs ?? 250));\n let attempts = 0;\n\n while (true) {\n attempts += 1;\n\n try {\n const document = await fetcher(id);\n return { id, document };\n } catch (error) {\n const retryable = isRetryableMercadoLibreError(error);\n if (!retryable || attempts > maxRetries + 1) {\n throw classifyMercadoLibreBatchFailure(id, error, attempts);\n }\n\n const retryAfterMs = error instanceof MercadoLibreApiError ? error.retryAfterMs : undefined;\n const jitterMs = Math.floor(Math.random() * 100);\n const delayMs = retryAfterMs ?? baseRetryDelayMs * 2 ** (attempts - 1) + jitterMs;\n await sleep(delayMs);\n }\n }\n}\n\nexport async function runMercadoLibreBatch<T>(\n ids: string[],\n fetcher: (id: string) => Promise<T>,\n options: MercadoLibreBatchFetchOptions = {}\n): Promise<MercadoLibreDocumentsBatchResult<T>> {\n const maxConcurrency = Math.max(1, Math.floor(options.maxConcurrency ?? 10));\n const successful: Array<MercadoLibreDocumentSuccess<T>> = [];\n const failed: MercadoLibreDocumentFailure[] = [];\n\n for (let start = 0; start < ids.length; start += maxConcurrency) {\n const chunk = ids.slice(start, start + maxConcurrency);\n const settled = await Promise.allSettled(\n chunk.map((id) => withMercadoLibreRetry(id, fetcher, options))\n );\n\n settled.forEach((result, index) => {\n const id = chunk[index];\n if (result.status === \"fulfilled\") {\n successful.push(result.value);\n return;\n }\n\n const reason = result.reason;\n if (\n reason &&\n typeof reason === \"object\" &&\n \"id\" in reason &&\n \"attempts\" in reason &&\n \"message\" in reason\n ) {\n failed.push(reason as MercadoLibreDocumentFailure);\n return;\n }\n\n failed.push(classifyMercadoLibreBatchFailure(id, reason, 1));\n });\n }\n\n return { successful, failed };\n}\n\nexport function normalizeMercadoLibrePaging(input: Record<string, unknown> | null | undefined) {\n const paging = input ?? {};\n const total = Number((paging.total as number | undefined) ?? 0);\n const limit = Number((paging.limit as number | undefined) ?? 0);\n const offset = Number((paging.offset as number | undefined) ?? 0);\n\n return {\n total: Number.isFinite(total) ? Math.max(0, total) : 0,\n limit: Number.isFinite(limit) ? Math.max(0, limit) : 0,\n offset: Number.isFinite(offset) ? Math.max(0, offset) : 0,\n };\n}\n\nexport function buildMercadoLibrePaginationMetadata(params: {\n total: number;\n limit: number;\n offset: number;\n returned: number;\n nextField: string;\n}) {\n const effectiveLimit = params.limit > 0 ? params.limit : params.returned;\n const hasMore = params.offset + params.returned < params.total;\n\n return {\n total: params.total,\n limit: effectiveLimit,\n offset: params.offset,\n returned: params.returned,\n has_more: hasMore,\n continuation:\n hasMore && effectiveLimit > 0\n ? `Reenviar la misma tool con ${params.nextField}=${params.offset + effectiveLimit} para continuar.`\n : \"No more pages for this query.\",\n };\n}\n\nexport async function parseMercadoLibreMultiget<T>(\n response: AxiosResponse<Array<{ code?: number; body?: T }>>\n): Promise<T[]> {\n return response.data\n .filter((entry) => Number(entry.code ?? 0) >= 200 && Number(entry.code ?? 0) < 300 && entry.body)\n .map((entry) => entry.body as T);\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }