@open-mercato/sync-akeneo 0.6.6-develop.5654.1.ca21e35f26 → 0.6.6-develop.5672.1.11e27afad2

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.
@@ -1,3 +1,4 @@
1
+ import { fetchWithTimeout } from "@open-mercato/shared/lib/http/fetchWithTimeout";
1
2
  import { dedupeStrings, labelFromLocalizedRecord } from "./shared.js";
2
3
  const DEFAULT_ALLOWED_AKENEO_HOST_PATTERNS = ["*.cloud.akeneo.com", "*.akeneo.cloud"];
3
4
  const ALLOWED_AKENEO_HOSTS_ENV_KEYS = [
@@ -179,9 +180,9 @@ function createAkeneoClient(credentialsInput) {
179
180
  return resolveAkeneoRequestUrl(nextUrl);
180
181
  }
181
182
  async function acquirePasswordGrantToken() {
182
- const response = await fetch(tokenEndpointUrl, {
183
+ const response = await fetchWithTimeout(tokenEndpointUrl, {
183
184
  method: "POST",
184
- signal: AbortSignal.timeout(resolveAkeneoRequestTimeoutMs()),
185
+ timeoutMs: resolveAkeneoRequestTimeoutMs(),
185
186
  headers: {
186
187
  accept: "application/json",
187
188
  "content-type": "application/json"
@@ -210,9 +211,9 @@ function createAkeneoClient(credentialsInput) {
210
211
  }
211
212
  async function refreshAccessToken(current) {
212
213
  if (!current.refreshToken) return acquirePasswordGrantToken();
213
- const response = await fetch(tokenEndpointUrl, {
214
+ const response = await fetchWithTimeout(tokenEndpointUrl, {
214
215
  method: "POST",
215
- signal: AbortSignal.timeout(resolveAkeneoRequestTimeoutMs()),
216
+ timeoutMs: resolveAkeneoRequestTimeoutMs(),
216
217
  headers: {
217
218
  accept: "application/json",
218
219
  "content-type": "application/json"
@@ -245,9 +246,9 @@ function createAkeneoClient(credentialsInput) {
245
246
  async function request(pathOrUrl, init = {}, retried = false, rateLimitAttempts = 0) {
246
247
  const url = resolveAkeneoRequestUrl(pathOrUrl);
247
248
  const token = await ensureToken();
248
- const response = await fetch(url, {
249
+ const response = await fetchWithTimeout(url, {
249
250
  ...init,
250
- signal: init.signal ?? AbortSignal.timeout(resolveAkeneoRequestTimeoutMs()),
251
+ timeoutMs: resolveAkeneoRequestTimeoutMs(),
251
252
  headers: {
252
253
  accept: "application/json",
253
254
  authorization: `Bearer ${token}`,
@@ -278,9 +279,9 @@ function createAkeneoClient(credentialsInput) {
278
279
  async function requestBinary(pathOrUrl, init = {}, retried = false, rateLimitAttempts = 0) {
279
280
  const url = resolveAkeneoRequestUrl(pathOrUrl);
280
281
  const token = await ensureToken();
281
- const response = await fetch(url, {
282
+ const response = await fetchWithTimeout(url, {
282
283
  ...init,
283
- signal: init.signal ?? AbortSignal.timeout(resolveAkeneoRequestTimeoutMs()),
284
+ timeoutMs: resolveAkeneoRequestTimeoutMs(),
284
285
  headers: {
285
286
  authorization: `Bearer ${token}`,
286
287
  ...init.headers
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/sync_akeneo/lib/client.ts"],
4
- "sourcesContent": ["import { dedupeStrings, labelFromLocalizedRecord, safeRecord, type AkeneoAttribute, type AkeneoAttributeOption, type AkeneoCategory, type AkeneoChannel, type AkeneoCredentialShape, type AkeneoFamily, type AkeneoFamilyVariant, type AkeneoLocale, type AkeneoProduct, type AkeneoProductModel } from './shared'\n\ntype TokenState = {\n accessToken: string\n refreshToken?: string | null\n expiresAt: number\n}\n\ntype AkeneoListResponse<T> = {\n _embedded?: {\n items?: T[]\n }\n _links?: {\n next?: {\n href?: string\n }\n }\n items_count?: number\n}\n\ntype AkeneoMediaFile = {\n code: string\n original_filename?: string | null\n mime_type?: string | null\n size?: number | null\n extension?: string | null\n _links?: {\n download?: {\n href?: string\n }\n }\n}\n\ntype AkeneoClient = ReturnType<typeof createAkeneoClient>\n\nconst DEFAULT_ALLOWED_AKENEO_HOST_PATTERNS = ['*.cloud.akeneo.com', '*.akeneo.cloud'] as const\nconst ALLOWED_AKENEO_HOSTS_ENV_KEYS = [\n 'OM_INTEGRATION_AKENEO_ALLOWED_HOSTS',\n 'OPENMERCATO_AKENEO_ALLOWED_HOSTS',\n 'AKENEO_ALLOWED_HOSTS',\n] as const\n\nfunction normalizeBaseUrl(url: string): string {\n return url.trim().replace(/\\/+$/g, '')\n}\n\nfunction readAllowedAkeneoHostPatterns(env: NodeJS.ProcessEnv = process.env): string[] {\n for (const key of ALLOWED_AKENEO_HOSTS_ENV_KEYS) {\n const raw = env[key]\n if (typeof raw !== 'string') continue\n const patterns = raw\n .split(',')\n .map((entry) => entry.trim().toLowerCase())\n .filter((entry) => entry.length > 0)\n if (patterns.length > 0) {\n return patterns\n }\n }\n\n return [...DEFAULT_ALLOWED_AKENEO_HOST_PATTERNS]\n}\n\nfunction matchesAkeneoHostPattern(hostname: string, pattern: string): boolean {\n if (pattern.startsWith('*.')) {\n const suffix = pattern.slice(2)\n return hostname === suffix || hostname.endsWith(`.${suffix}`)\n }\n\n return hostname === pattern\n}\n\nexport function validateAkeneoApiUrl(rawUrl: string, env: NodeJS.ProcessEnv = process.env): string {\n let parsed: URL\n try {\n parsed = new URL(rawUrl.trim())\n } catch {\n throw new Error('Akeneo URL must be a valid absolute URL')\n }\n\n if (parsed.protocol !== 'https:') {\n throw new Error('Akeneo URL must use https')\n }\n if (parsed.username || parsed.password) {\n throw new Error('Akeneo URL must not include embedded credentials')\n }\n if (parsed.port && parsed.port !== '443') {\n throw new Error('Akeneo URL must not use a custom port')\n }\n if (parsed.pathname !== '/' && parsed.pathname !== '') {\n throw new Error('Akeneo URL must not include a path')\n }\n if (parsed.search || parsed.hash) {\n throw new Error('Akeneo URL must not include query parameters or fragments')\n }\n\n const hostname = parsed.hostname.toLowerCase()\n const allowedPatterns = readAllowedAkeneoHostPatterns(env)\n if (!allowedPatterns.some((pattern) => matchesAkeneoHostPattern(hostname, pattern))) {\n throw new Error(`Akeneo URL host is not allowed: ${hostname}`)\n }\n\n return normalizeBaseUrl(parsed.origin)\n}\n\nexport function normalizeAkeneoDateTime(value: string | null | undefined): string | null {\n if (typeof value !== 'string' || value.trim().length === 0) return null\n const trimmed = value.trim()\n if (/^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$/.test(trimmed)) {\n return trimmed\n }\n\n const parsed = new Date(trimmed)\n if (Number.isNaN(parsed.getTime())) {\n return null\n }\n\n const year = parsed.getUTCFullYear()\n const month = String(parsed.getUTCMonth() + 1).padStart(2, '0')\n const day = String(parsed.getUTCDate()).padStart(2, '0')\n const hours = String(parsed.getUTCHours()).padStart(2, '0')\n const minutes = String(parsed.getUTCMinutes()).padStart(2, '0')\n const seconds = String(parsed.getUTCSeconds()).padStart(2, '0')\n return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`\n}\n\nexport function encodeAkeneoPathParam(value: string): string {\n return value\n .split('/')\n .map((segment) => encodeURIComponent(segment))\n .join('/')\n}\n\nexport function sanitizeAkeneoProductNextUrl(nextUrl: string): string {\n let url: URL\n try {\n url = new URL(nextUrl)\n } catch {\n return nextUrl\n }\n\n const rawSearch = url.searchParams.get('search')\n if (!rawSearch) {\n return url.toString()\n }\n\n try {\n const parsed = JSON.parse(rawSearch) as Record<string, unknown>\n const updatedFilters = Array.isArray(parsed.updated) ? parsed.updated : []\n const sanitizedUpdated = updatedFilters\n .map((entry) => {\n if (!entry || typeof entry !== 'object') return null\n const record = entry as Record<string, unknown>\n const normalizedValue = normalizeAkeneoDateTime(\n typeof record.value === 'string' ? record.value : null,\n )\n if (!normalizedValue) return null\n return {\n ...record,\n value: normalizedValue,\n }\n })\n .filter((entry) => entry !== null) as Record<string, unknown>[]\n\n if (sanitizedUpdated.length === 0) {\n delete parsed.updated\n } else {\n parsed.updated = sanitizedUpdated\n }\n\n if (Object.keys(parsed).length === 0) {\n url.searchParams.delete('search')\n } else {\n url.searchParams.set('search', JSON.stringify(parsed))\n }\n return url.toString()\n } catch {\n return nextUrl\n }\n}\n\nfunction coerceCredentials(credentials: Record<string, unknown>): AkeneoCredentialShape {\n const apiUrl = typeof credentials.apiUrl === 'string' ? credentials.apiUrl.trim() : ''\n const clientId = typeof credentials.clientId === 'string' ? credentials.clientId.trim() : ''\n const clientSecret = typeof credentials.clientSecret === 'string' ? credentials.clientSecret : ''\n const username = typeof credentials.username === 'string' ? credentials.username.trim() : ''\n const password = typeof credentials.password === 'string' ? credentials.password : ''\n if (!apiUrl || !clientId || !clientSecret || !username || !password) {\n throw new Error('Akeneo credentials are incomplete')\n }\n return {\n apiUrl: validateAkeneoApiUrl(apiUrl),\n clientId,\n clientSecret,\n username,\n password,\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\nconst DEFAULT_AKENEO_REQUEST_TIMEOUT_MS = 30_000\nconst DEFAULT_AKENEO_MAX_RATE_LIMIT_RETRIES = 5\nconst DEFAULT_AKENEO_RETRY_AFTER_CAP_MS = 60_000\n\nfunction resolvePositiveIntEnv(raw: string | undefined, fallback: number): number {\n const parsed = Number.parseInt(raw ?? '', 10)\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback\n}\n\nfunction resolveAkeneoRequestTimeoutMs(env: NodeJS.ProcessEnv = process.env): number {\n return resolvePositiveIntEnv(env.OM_INTEGRATION_AKENEO_REQUEST_TIMEOUT_MS, DEFAULT_AKENEO_REQUEST_TIMEOUT_MS)\n}\n\nfunction resolveAkeneoMaxRateLimitRetries(env: NodeJS.ProcessEnv = process.env): number {\n return resolvePositiveIntEnv(env.OM_INTEGRATION_AKENEO_MAX_RATE_LIMIT_RETRIES, DEFAULT_AKENEO_MAX_RATE_LIMIT_RETRIES)\n}\n\nfunction clampAkeneoRetryAfterMs(retryAfterHeader: string | null, env: NodeJS.ProcessEnv = process.env): number {\n const retryAfter = Number(retryAfterHeader ?? '1')\n const requestedMs = Number.isFinite(retryAfter) ? retryAfter * 1000 : 1000\n const cap = resolvePositiveIntEnv(env.OM_INTEGRATION_AKENEO_RETRY_AFTER_CAP_MS, DEFAULT_AKENEO_RETRY_AFTER_CAP_MS)\n return Math.min(Math.max(requestedMs, 0), cap)\n}\n\nexport function createAkeneoClient(credentialsInput: Record<string, unknown>) {\n const credentials = coerceCredentials(credentialsInput)\n const akeneoBaseUrl = new URL(`${credentials.apiUrl}/`)\n const tokenEndpointUrl = new URL('/api/oauth/v1/token', akeneoBaseUrl).toString()\n let tokenState: TokenState | null = null\n let lastAttributeOptionRequestAt = 0\n const familyCache = new Map<string, Promise<AkeneoFamily | null>>()\n const familyVariantCache = new Map<string, Promise<AkeneoFamilyVariant | null>>()\n const productModelCache = new Map<string, Promise<AkeneoProductModel | null>>()\n const attributeCache = new Map<string, Promise<AkeneoAttribute | null>>()\n const categoryCache = new Map<string, Promise<AkeneoCategory | null>>()\n const mediaFileCache = new Map<string, Promise<AkeneoMediaFile | null>>()\n\n function resolveAkeneoRequestUrl(pathOrUrl: string): string {\n const resolved = new URL(pathOrUrl, akeneoBaseUrl)\n if (resolved.origin !== akeneoBaseUrl.origin) {\n throw new Error(`Akeneo request URL must stay on the configured host: ${resolved.origin}`)\n }\n return resolved.toString()\n }\n\n function normalizeAkeneoNextUrl(nextUrl: string): string {\n return resolveAkeneoRequestUrl(nextUrl)\n }\n\n async function acquirePasswordGrantToken(): Promise<TokenState> {\n const response = await fetch(tokenEndpointUrl, {\n method: 'POST',\n signal: AbortSignal.timeout(resolveAkeneoRequestTimeoutMs()),\n headers: {\n accept: 'application/json',\n 'content-type': 'application/json',\n },\n body: JSON.stringify({\n grant_type: 'password',\n client_id: credentials.clientId,\n client_secret: credentials.clientSecret,\n username: credentials.username,\n password: credentials.password,\n }),\n })\n\n if (!response.ok) {\n const message = await response.text()\n throw new Error(`Akeneo authentication failed (${response.status}): ${message}`)\n }\n\n const payload = await response.json() as {\n access_token?: string\n refresh_token?: string\n expires_in?: number\n }\n if (!payload.access_token) {\n throw new Error('Akeneo authentication did not return an access token')\n }\n\n return {\n accessToken: payload.access_token,\n refreshToken: payload.refresh_token ?? null,\n expiresAt: Date.now() + ((payload.expires_in ?? 3600) * 1000) - 10_000,\n }\n }\n\n async function refreshAccessToken(current: TokenState): Promise<TokenState> {\n if (!current.refreshToken) return acquirePasswordGrantToken()\n const response = await fetch(tokenEndpointUrl, {\n method: 'POST',\n signal: AbortSignal.timeout(resolveAkeneoRequestTimeoutMs()),\n headers: {\n accept: 'application/json',\n 'content-type': 'application/json',\n },\n body: JSON.stringify({\n grant_type: 'refresh_token',\n client_id: credentials.clientId,\n client_secret: credentials.clientSecret,\n refresh_token: current.refreshToken,\n }),\n })\n\n if (!response.ok) {\n return acquirePasswordGrantToken()\n }\n\n const payload = await response.json() as {\n access_token?: string\n refresh_token?: string\n expires_in?: number\n }\n if (!payload.access_token) return acquirePasswordGrantToken()\n return {\n accessToken: payload.access_token,\n refreshToken: payload.refresh_token ?? current.refreshToken ?? null,\n expiresAt: Date.now() + ((payload.expires_in ?? 3600) * 1000) - 10_000,\n }\n }\n\n async function ensureToken(forceRefresh = false): Promise<string> {\n if (!forceRefresh && tokenState && tokenState.expiresAt > Date.now()) {\n return tokenState.accessToken\n }\n tokenState = tokenState ? await refreshAccessToken(tokenState) : await acquirePasswordGrantToken()\n return tokenState.accessToken\n }\n\n async function request<T>(\n pathOrUrl: string,\n init: RequestInit = {},\n retried = false,\n rateLimitAttempts = 0,\n ): Promise<T> {\n const url = resolveAkeneoRequestUrl(pathOrUrl)\n const token = await ensureToken()\n\n const response = await fetch(url, {\n ...init,\n signal: init.signal ?? AbortSignal.timeout(resolveAkeneoRequestTimeoutMs()),\n headers: {\n accept: 'application/json',\n authorization: `Bearer ${token}`,\n ...init.headers,\n },\n })\n\n if (response.status === 401 && !retried) {\n await ensureToken(true)\n return request<T>(pathOrUrl, init, true, rateLimitAttempts)\n }\n\n if (response.status === 429) {\n if (rateLimitAttempts >= resolveAkeneoMaxRateLimitRetries()) {\n const body = await response.text()\n throw new Error(`Akeneo request failed (429): ${body}`)\n }\n await sleep(clampAkeneoRetryAfterMs(response.headers.get('retry-after')))\n return request<T>(pathOrUrl, init, retried, rateLimitAttempts + 1)\n }\n\n if (!response.ok) {\n const body = await response.text()\n throw new Error(`Akeneo request failed (${response.status}): ${body}`)\n }\n\n if (response.status === 204) {\n return null as T\n }\n\n return response.json() as Promise<T>\n }\n\n async function requestBinary(\n pathOrUrl: string,\n init: RequestInit = {},\n retried = false,\n rateLimitAttempts = 0,\n ): Promise<{\n buffer: Buffer\n contentType: string | null\n contentLength: number | null\n }> {\n const url = resolveAkeneoRequestUrl(pathOrUrl)\n const token = await ensureToken()\n\n const response = await fetch(url, {\n ...init,\n signal: init.signal ?? AbortSignal.timeout(resolveAkeneoRequestTimeoutMs()),\n headers: {\n authorization: `Bearer ${token}`,\n ...init.headers,\n },\n })\n\n if (response.status === 401 && !retried) {\n await ensureToken(true)\n return requestBinary(pathOrUrl, init, true, rateLimitAttempts)\n }\n\n if (response.status === 429) {\n if (rateLimitAttempts >= resolveAkeneoMaxRateLimitRetries()) {\n const body = await response.text()\n throw new Error(`Akeneo request failed (429): ${body}`)\n }\n await sleep(clampAkeneoRetryAfterMs(response.headers.get('retry-after')))\n return requestBinary(pathOrUrl, init, retried, rateLimitAttempts + 1)\n }\n\n if (!response.ok) {\n const body = await response.text()\n throw new Error(`Akeneo request failed (${response.status}): ${body}`)\n }\n\n const arrayBuffer = await response.arrayBuffer()\n return {\n buffer: Buffer.from(arrayBuffer),\n contentType: response.headers.get('content-type'),\n contentLength: Number(response.headers.get('content-length') ?? '') || arrayBuffer.byteLength || null,\n }\n }\n\n function buildUrl(path: string, params: Record<string, string | number | boolean | undefined | null>): string {\n const url = new URL(resolveAkeneoRequestUrl(path))\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null || value === '') continue\n url.searchParams.set(key, String(value))\n }\n return url.toString()\n }\n\n async function readList<T>(pathOrUrl: string, params?: Record<string, string | number | boolean | undefined | null>): Promise<{\n items: T[]\n nextUrl: string | null\n totalEstimate: number | null\n }> {\n const payload = await request<AkeneoListResponse<T>>(\n params ? buildUrl(pathOrUrl, params) : pathOrUrl,\n )\n return {\n items: Array.isArray(payload?._embedded?.items) ? payload._embedded.items : [],\n nextUrl: typeof payload?._links?.next?.href === 'string'\n ? normalizeAkeneoNextUrl(payload._links.next.href)\n : null,\n totalEstimate: typeof payload?.items_count === 'number' ? payload.items_count : null,\n }\n }\n\n async function countProducts(updatedAfter?: string | null): Promise<number | null> {\n const params: Record<string, string | number | boolean | undefined | null> = {\n limit: 1,\n pagination_type: 'page',\n with_count: true,\n }\n const normalizedUpdatedAfter = normalizeAkeneoDateTime(updatedAfter)\n if (normalizedUpdatedAfter) {\n params.search = JSON.stringify({\n updated: [\n {\n operator: '>',\n value: normalizedUpdatedAfter,\n },\n ],\n })\n }\n\n const page = await readList<AkeneoProduct>('/api/rest/v1/products-uuid', params)\n return page.totalEstimate\n }\n\n async function getSystemProbe(): Promise<{ version: string | null }> {\n try {\n const result = await request<Record<string, unknown>>('/api/rest/v1/system-information')\n return {\n version: typeof result.pim_version === 'string' ? result.pim_version : null,\n }\n } catch {\n const attrs = await readList<AkeneoAttribute>('/api/rest/v1/attributes', {\n limit: 1,\n pagination_type: 'page',\n })\n return { version: attrs.items.length >= 0 ? null : null }\n }\n }\n\n async function listProducts(options: {\n nextUrl?: string | null\n batchSize: number\n updatedAfter?: string | null\n }): Promise<{ items: AkeneoProduct[]; nextUrl: string | null; totalEstimate: number | null }> {\n if (options.nextUrl) {\n const page = await readList<AkeneoProduct>(normalizeAkeneoNextUrl(sanitizeAkeneoProductNextUrl(options.nextUrl)))\n return {\n ...page,\n nextUrl: page.nextUrl ? normalizeAkeneoNextUrl(sanitizeAkeneoProductNextUrl(page.nextUrl)) : null,\n }\n }\n const params: Record<string, string | number | boolean | undefined | null> = {\n limit: Math.min(Math.max(options.batchSize, 1), 100),\n pagination_type: 'search_after',\n with_count: false,\n }\n const normalizedUpdatedAfter = normalizeAkeneoDateTime(options.updatedAfter)\n if (normalizedUpdatedAfter) {\n params.search = JSON.stringify({\n updated: [\n {\n operator: '>',\n value: normalizedUpdatedAfter,\n },\n ],\n })\n }\n const [page, totalEstimate] = await Promise.all([\n readList<AkeneoProduct>('/api/rest/v1/products-uuid', params),\n countProducts(normalizedUpdatedAfter).catch(() => null),\n ])\n return {\n ...page,\n totalEstimate: totalEstimate ?? page.totalEstimate,\n nextUrl: page.nextUrl ? normalizeAkeneoNextUrl(sanitizeAkeneoProductNextUrl(page.nextUrl)) : null,\n }\n }\n\n async function listCategories(nextUrl?: string | null, batchSize = 100): Promise<{ items: AkeneoCategory[]; nextUrl: string | null; totalEstimate: number | null }> {\n if (nextUrl) return readList<AkeneoCategory>(nextUrl)\n return readList<AkeneoCategory>('/api/rest/v1/categories', {\n limit: Math.min(Math.max(batchSize, 1), 100),\n pagination_type: 'page',\n with_count: true,\n })\n }\n\n async function listAttributes(nextUrl?: string | null, batchSize = 100): Promise<{ items: AkeneoAttribute[]; nextUrl: string | null; totalEstimate: number | null }> {\n if (nextUrl) return readList<AkeneoAttribute>(nextUrl)\n return readList<AkeneoAttribute>('/api/rest/v1/attributes', {\n limit: Math.min(Math.max(batchSize, 1), 100),\n pagination_type: 'page',\n with_count: true,\n })\n }\n\n async function listFamilies(nextUrl?: string | null, batchSize = 100): Promise<{ items: AkeneoFamily[]; nextUrl: string | null; totalEstimate: number | null }> {\n if (nextUrl) return readList<AkeneoFamily>(nextUrl)\n return readList<AkeneoFamily>('/api/rest/v1/families', {\n limit: Math.min(Math.max(batchSize, 1), 100),\n pagination_type: 'page',\n with_count: true,\n })\n }\n\n async function listFamilyVariants(\n familyCode: string,\n nextUrl?: string | null,\n batchSize = 100,\n ): Promise<{ items: AkeneoFamilyVariant[]; nextUrl: string | null; totalEstimate: number | null }> {\n if (nextUrl) return readList<AkeneoFamilyVariant>(nextUrl)\n return readList<AkeneoFamilyVariant>(`/api/rest/v1/families/${encodeURIComponent(familyCode)}/variants`, {\n limit: Math.min(Math.max(batchSize, 1), 100),\n with_count: false,\n })\n }\n\n async function listChannels(): Promise<AkeneoChannel[]> {\n const response = await readList<AkeneoChannel>('/api/rest/v1/channels', {\n limit: 100,\n pagination_type: 'page',\n with_count: true,\n })\n return response.items\n }\n\n async function listLocales(): Promise<AkeneoLocale[]> {\n const response = await readList<AkeneoLocale>('/api/rest/v1/locales', {\n limit: 100,\n pagination_type: 'page',\n with_count: true,\n })\n return response.items\n }\n\n async function getCategory(code: string): Promise<AkeneoCategory | null> {\n if (!categoryCache.has(code)) {\n categoryCache.set(code, request<AkeneoCategory>(`/api/rest/v1/categories/${encodeURIComponent(code)}`).catch(() => null))\n }\n return categoryCache.get(code) ?? null\n }\n\n async function getAttribute(code: string): Promise<AkeneoAttribute | null> {\n if (!attributeCache.has(code)) {\n attributeCache.set(code, request<AkeneoAttribute>(`/api/rest/v1/attributes/${encodeURIComponent(code)}`).catch(() => null))\n }\n return attributeCache.get(code) ?? null\n }\n\n async function listAttributeOptions(attributeCode: string): Promise<AkeneoAttributeOption[]> {\n const options: AkeneoAttributeOption[] = []\n let nextUrl: string | null | undefined = null\n do {\n const now = Date.now()\n const waitMs = Math.max(0, 350 - (now - lastAttributeOptionRequestAt))\n if (waitMs > 0) await sleep(waitMs)\n lastAttributeOptionRequestAt = Date.now()\n const page: { items: AkeneoAttributeOption[]; nextUrl: string | null; totalEstimate: number | null } = await readList<AkeneoAttributeOption>(\n nextUrl ?? `/api/rest/v1/attributes/${encodeURIComponent(attributeCode)}/options`,\n nextUrl\n ? undefined\n : {\n limit: 100,\n pagination_type: 'page',\n with_count: true,\n },\n )\n options.push(...page.items)\n nextUrl = page.nextUrl\n } while (nextUrl)\n return options\n }\n\n async function getFamily(code: string): Promise<AkeneoFamily | null> {\n if (!familyCache.has(code)) {\n familyCache.set(code, request<AkeneoFamily>(`/api/rest/v1/families/${encodeURIComponent(code)}`).catch(() => null))\n }\n return familyCache.get(code) ?? null\n }\n\n async function getFamilyVariant(familyCode: string, familyVariantCode: string): Promise<AkeneoFamilyVariant | null> {\n const cacheKey = `${familyCode}:${familyVariantCode}`\n if (!familyVariantCache.has(cacheKey)) {\n familyVariantCache.set(\n cacheKey,\n request<AkeneoFamilyVariant>(`/api/rest/v1/families/${encodeURIComponent(familyCode)}/variants/${encodeURIComponent(familyVariantCode)}`).catch(() => null),\n )\n }\n return familyVariantCache.get(cacheKey) ?? null\n }\n\n async function getProductModel(code: string): Promise<AkeneoProductModel | null> {\n if (!productModelCache.has(code)) {\n productModelCache.set(code, request<AkeneoProductModel>(`/api/rest/v1/product-models/${encodeURIComponent(code)}`).catch(() => null))\n }\n return productModelCache.get(code) ?? null\n }\n\n async function getMediaFile(code: string): Promise<AkeneoMediaFile | null> {\n if (!mediaFileCache.has(code)) {\n mediaFileCache.set(code, request<AkeneoMediaFile>(`/api/rest/v1/media-files/${encodeAkeneoPathParam(code)}`).catch(() => null))\n }\n return mediaFileCache.get(code) ?? null\n }\n\n async function downloadMediaFile(codeOrUrl: string): Promise<{\n buffer: Buffer\n contentType: string | null\n contentLength: number | null\n fileName: string | null\n code: string | null\n }> {\n if (codeOrUrl.startsWith('http://') || codeOrUrl.startsWith('https://') || codeOrUrl.startsWith('/')) {\n const binary = await requestBinary(codeOrUrl)\n return {\n ...binary,\n fileName: null,\n code: null,\n }\n }\n\n const mediaFile = await getMediaFile(codeOrUrl)\n if (!mediaFile) {\n throw new Error(`Akeneo media file ${codeOrUrl} was not found`)\n }\n const downloadHref = mediaFile._links?.download?.href\n const binary = await requestBinary(downloadHref || `/api/rest/v1/media-files/${encodeAkeneoPathParam(codeOrUrl)}/download`)\n return {\n ...binary,\n fileName: typeof mediaFile.original_filename === 'string' && mediaFile.original_filename.trim().length > 0\n ? mediaFile.original_filename.trim()\n : null,\n code: mediaFile.code ?? codeOrUrl,\n }\n }\n\n async function collectDiscoveryData(): Promise<{\n locales: Array<{ code: string; label: string; enabled: boolean }>\n channels: Array<{ code: string; label: string; locales: string[] }>\n attributes: Array<{ code: string; type: string; label: string; localizable: boolean; scopable: boolean; group?: string; metricFamily?: string }>\n families: Array<{ code: string; label: string; attributeCount: number }>\n familyVariants: Array<{ familyCode: string; code: string; label: string; axes: string[]; attributes: string[] }>\n version: string | null\n }> {\n const [locales, channels, attributes, families, probe] = await Promise.all([\n listLocales().catch(() => []),\n listChannels().catch(() => []),\n listAttributes(null, 100),\n listFamilies(null, 100),\n getSystemProbe().catch(() => ({ version: null })),\n ])\n\n const familyVariants: Array<{ familyCode: string; code: string; label: string; axes: string[]; attributes: string[] }> = []\n for (const family of families.items) {\n let nextUrl: string | null = null\n do {\n const page: { items: AkeneoFamilyVariant[]; nextUrl: string | null; totalEstimate: number | null } = await listFamilyVariants(family.code, nextUrl, 100).catch(() => ({\n items: [] as AkeneoFamilyVariant[],\n nextUrl: null,\n totalEstimate: null,\n }))\n familyVariants.push(...page.items.map((familyVariant) => ({\n familyCode: family.code,\n code: familyVariant.code,\n label: labelFromLocalizedRecord(familyVariant.labels ?? null, null, familyVariant.code),\n axes: dedupeStrings(familyVariant.variant_attribute_sets?.flatMap((set: { axes?: string[] }) => Array.isArray(set.axes) ? set.axes : []) ?? []),\n attributes: dedupeStrings(familyVariant.variant_attribute_sets?.flatMap((set: { attributes?: string[] }) => Array.isArray(set.attributes) ? set.attributes : []) ?? []),\n })))\n nextUrl = page.nextUrl\n } while (nextUrl)\n }\n\n return {\n locales: locales.map((locale) => ({\n code: locale.code,\n label: labelFromLocalizedRecord(locale.labels ?? null, null, locale.code),\n enabled: locale.enabled ?? false,\n })),\n channels: channels.map((channel) => ({\n code: channel.code,\n label: labelFromLocalizedRecord(channel.labels ?? null, null, channel.code),\n locales: dedupeStrings(channel.locales ?? []),\n })),\n attributes: attributes.items.map((attribute) => ({\n code: attribute.code,\n type: attribute.type,\n label: labelFromLocalizedRecord(attribute.labels ?? null, null, attribute.code),\n localizable: Boolean(attribute.localizable),\n scopable: Boolean(attribute.scopable),\n group: typeof attribute.group === 'string' && attribute.group.trim().length > 0 ? attribute.group.trim() : undefined,\n metricFamily: typeof attribute.metric_family === 'string' && attribute.metric_family.trim().length > 0\n ? attribute.metric_family.trim()\n : undefined,\n })),\n families: families.items.map((family) => ({\n code: family.code,\n label: labelFromLocalizedRecord(family.labels ?? null, null, family.code),\n attributeCount: Array.isArray(family.attributes) ? family.attributes.length : 0,\n })),\n familyVariants,\n version: probe.version,\n }\n }\n\n return {\n credentials,\n getSystemProbe,\n collectDiscoveryData,\n listProducts,\n listCategories,\n listAttributes,\n listFamilies,\n listFamilyVariants,\n listChannels,\n listLocales,\n getCategory,\n getAttribute,\n listAttributeOptions,\n getFamily,\n getFamilyVariant,\n getProductModel,\n getMediaFile,\n downloadMediaFile,\n }\n}\n\nexport type { AkeneoClient }\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { fetchWithTimeout } from '@open-mercato/shared/lib/http/fetchWithTimeout'\nimport { dedupeStrings, labelFromLocalizedRecord, safeRecord, type AkeneoAttribute, type AkeneoAttributeOption, type AkeneoCategory, type AkeneoChannel, type AkeneoCredentialShape, type AkeneoFamily, type AkeneoFamilyVariant, type AkeneoLocale, type AkeneoProduct, type AkeneoProductModel } from './shared'\n\ntype TokenState = {\n accessToken: string\n refreshToken?: string | null\n expiresAt: number\n}\n\ntype AkeneoListResponse<T> = {\n _embedded?: {\n items?: T[]\n }\n _links?: {\n next?: {\n href?: string\n }\n }\n items_count?: number\n}\n\ntype AkeneoMediaFile = {\n code: string\n original_filename?: string | null\n mime_type?: string | null\n size?: number | null\n extension?: string | null\n _links?: {\n download?: {\n href?: string\n }\n }\n}\n\ntype AkeneoClient = ReturnType<typeof createAkeneoClient>\n\nconst DEFAULT_ALLOWED_AKENEO_HOST_PATTERNS = ['*.cloud.akeneo.com', '*.akeneo.cloud'] as const\nconst ALLOWED_AKENEO_HOSTS_ENV_KEYS = [\n 'OM_INTEGRATION_AKENEO_ALLOWED_HOSTS',\n 'OPENMERCATO_AKENEO_ALLOWED_HOSTS',\n 'AKENEO_ALLOWED_HOSTS',\n] as const\n\nfunction normalizeBaseUrl(url: string): string {\n return url.trim().replace(/\\/+$/g, '')\n}\n\nfunction readAllowedAkeneoHostPatterns(env: NodeJS.ProcessEnv = process.env): string[] {\n for (const key of ALLOWED_AKENEO_HOSTS_ENV_KEYS) {\n const raw = env[key]\n if (typeof raw !== 'string') continue\n const patterns = raw\n .split(',')\n .map((entry) => entry.trim().toLowerCase())\n .filter((entry) => entry.length > 0)\n if (patterns.length > 0) {\n return patterns\n }\n }\n\n return [...DEFAULT_ALLOWED_AKENEO_HOST_PATTERNS]\n}\n\nfunction matchesAkeneoHostPattern(hostname: string, pattern: string): boolean {\n if (pattern.startsWith('*.')) {\n const suffix = pattern.slice(2)\n return hostname === suffix || hostname.endsWith(`.${suffix}`)\n }\n\n return hostname === pattern\n}\n\nexport function validateAkeneoApiUrl(rawUrl: string, env: NodeJS.ProcessEnv = process.env): string {\n let parsed: URL\n try {\n parsed = new URL(rawUrl.trim())\n } catch {\n throw new Error('Akeneo URL must be a valid absolute URL')\n }\n\n if (parsed.protocol !== 'https:') {\n throw new Error('Akeneo URL must use https')\n }\n if (parsed.username || parsed.password) {\n throw new Error('Akeneo URL must not include embedded credentials')\n }\n if (parsed.port && parsed.port !== '443') {\n throw new Error('Akeneo URL must not use a custom port')\n }\n if (parsed.pathname !== '/' && parsed.pathname !== '') {\n throw new Error('Akeneo URL must not include a path')\n }\n if (parsed.search || parsed.hash) {\n throw new Error('Akeneo URL must not include query parameters or fragments')\n }\n\n const hostname = parsed.hostname.toLowerCase()\n const allowedPatterns = readAllowedAkeneoHostPatterns(env)\n if (!allowedPatterns.some((pattern) => matchesAkeneoHostPattern(hostname, pattern))) {\n throw new Error(`Akeneo URL host is not allowed: ${hostname}`)\n }\n\n return normalizeBaseUrl(parsed.origin)\n}\n\nexport function normalizeAkeneoDateTime(value: string | null | undefined): string | null {\n if (typeof value !== 'string' || value.trim().length === 0) return null\n const trimmed = value.trim()\n if (/^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$/.test(trimmed)) {\n return trimmed\n }\n\n const parsed = new Date(trimmed)\n if (Number.isNaN(parsed.getTime())) {\n return null\n }\n\n const year = parsed.getUTCFullYear()\n const month = String(parsed.getUTCMonth() + 1).padStart(2, '0')\n const day = String(parsed.getUTCDate()).padStart(2, '0')\n const hours = String(parsed.getUTCHours()).padStart(2, '0')\n const minutes = String(parsed.getUTCMinutes()).padStart(2, '0')\n const seconds = String(parsed.getUTCSeconds()).padStart(2, '0')\n return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`\n}\n\nexport function encodeAkeneoPathParam(value: string): string {\n return value\n .split('/')\n .map((segment) => encodeURIComponent(segment))\n .join('/')\n}\n\nexport function sanitizeAkeneoProductNextUrl(nextUrl: string): string {\n let url: URL\n try {\n url = new URL(nextUrl)\n } catch {\n return nextUrl\n }\n\n const rawSearch = url.searchParams.get('search')\n if (!rawSearch) {\n return url.toString()\n }\n\n try {\n const parsed = JSON.parse(rawSearch) as Record<string, unknown>\n const updatedFilters = Array.isArray(parsed.updated) ? parsed.updated : []\n const sanitizedUpdated = updatedFilters\n .map((entry) => {\n if (!entry || typeof entry !== 'object') return null\n const record = entry as Record<string, unknown>\n const normalizedValue = normalizeAkeneoDateTime(\n typeof record.value === 'string' ? record.value : null,\n )\n if (!normalizedValue) return null\n return {\n ...record,\n value: normalizedValue,\n }\n })\n .filter((entry) => entry !== null) as Record<string, unknown>[]\n\n if (sanitizedUpdated.length === 0) {\n delete parsed.updated\n } else {\n parsed.updated = sanitizedUpdated\n }\n\n if (Object.keys(parsed).length === 0) {\n url.searchParams.delete('search')\n } else {\n url.searchParams.set('search', JSON.stringify(parsed))\n }\n return url.toString()\n } catch {\n return nextUrl\n }\n}\n\nfunction coerceCredentials(credentials: Record<string, unknown>): AkeneoCredentialShape {\n const apiUrl = typeof credentials.apiUrl === 'string' ? credentials.apiUrl.trim() : ''\n const clientId = typeof credentials.clientId === 'string' ? credentials.clientId.trim() : ''\n const clientSecret = typeof credentials.clientSecret === 'string' ? credentials.clientSecret : ''\n const username = typeof credentials.username === 'string' ? credentials.username.trim() : ''\n const password = typeof credentials.password === 'string' ? credentials.password : ''\n if (!apiUrl || !clientId || !clientSecret || !username || !password) {\n throw new Error('Akeneo credentials are incomplete')\n }\n return {\n apiUrl: validateAkeneoApiUrl(apiUrl),\n clientId,\n clientSecret,\n username,\n password,\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\nconst DEFAULT_AKENEO_REQUEST_TIMEOUT_MS = 30_000\nconst DEFAULT_AKENEO_MAX_RATE_LIMIT_RETRIES = 5\nconst DEFAULT_AKENEO_RETRY_AFTER_CAP_MS = 60_000\n\nfunction resolvePositiveIntEnv(raw: string | undefined, fallback: number): number {\n const parsed = Number.parseInt(raw ?? '', 10)\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback\n}\n\nfunction resolveAkeneoRequestTimeoutMs(env: NodeJS.ProcessEnv = process.env): number {\n return resolvePositiveIntEnv(env.OM_INTEGRATION_AKENEO_REQUEST_TIMEOUT_MS, DEFAULT_AKENEO_REQUEST_TIMEOUT_MS)\n}\n\nfunction resolveAkeneoMaxRateLimitRetries(env: NodeJS.ProcessEnv = process.env): number {\n return resolvePositiveIntEnv(env.OM_INTEGRATION_AKENEO_MAX_RATE_LIMIT_RETRIES, DEFAULT_AKENEO_MAX_RATE_LIMIT_RETRIES)\n}\n\nfunction clampAkeneoRetryAfterMs(retryAfterHeader: string | null, env: NodeJS.ProcessEnv = process.env): number {\n const retryAfter = Number(retryAfterHeader ?? '1')\n const requestedMs = Number.isFinite(retryAfter) ? retryAfter * 1000 : 1000\n const cap = resolvePositiveIntEnv(env.OM_INTEGRATION_AKENEO_RETRY_AFTER_CAP_MS, DEFAULT_AKENEO_RETRY_AFTER_CAP_MS)\n return Math.min(Math.max(requestedMs, 0), cap)\n}\n\nexport function createAkeneoClient(credentialsInput: Record<string, unknown>) {\n const credentials = coerceCredentials(credentialsInput)\n const akeneoBaseUrl = new URL(`${credentials.apiUrl}/`)\n const tokenEndpointUrl = new URL('/api/oauth/v1/token', akeneoBaseUrl).toString()\n let tokenState: TokenState | null = null\n let lastAttributeOptionRequestAt = 0\n const familyCache = new Map<string, Promise<AkeneoFamily | null>>()\n const familyVariantCache = new Map<string, Promise<AkeneoFamilyVariant | null>>()\n const productModelCache = new Map<string, Promise<AkeneoProductModel | null>>()\n const attributeCache = new Map<string, Promise<AkeneoAttribute | null>>()\n const categoryCache = new Map<string, Promise<AkeneoCategory | null>>()\n const mediaFileCache = new Map<string, Promise<AkeneoMediaFile | null>>()\n\n function resolveAkeneoRequestUrl(pathOrUrl: string): string {\n const resolved = new URL(pathOrUrl, akeneoBaseUrl)\n if (resolved.origin !== akeneoBaseUrl.origin) {\n throw new Error(`Akeneo request URL must stay on the configured host: ${resolved.origin}`)\n }\n return resolved.toString()\n }\n\n function normalizeAkeneoNextUrl(nextUrl: string): string {\n return resolveAkeneoRequestUrl(nextUrl)\n }\n\n async function acquirePasswordGrantToken(): Promise<TokenState> {\n const response = await fetchWithTimeout(tokenEndpointUrl, {\n method: 'POST',\n timeoutMs: resolveAkeneoRequestTimeoutMs(),\n headers: {\n accept: 'application/json',\n 'content-type': 'application/json',\n },\n body: JSON.stringify({\n grant_type: 'password',\n client_id: credentials.clientId,\n client_secret: credentials.clientSecret,\n username: credentials.username,\n password: credentials.password,\n }),\n })\n\n if (!response.ok) {\n const message = await response.text()\n throw new Error(`Akeneo authentication failed (${response.status}): ${message}`)\n }\n\n const payload = await response.json() as {\n access_token?: string\n refresh_token?: string\n expires_in?: number\n }\n if (!payload.access_token) {\n throw new Error('Akeneo authentication did not return an access token')\n }\n\n return {\n accessToken: payload.access_token,\n refreshToken: payload.refresh_token ?? null,\n expiresAt: Date.now() + ((payload.expires_in ?? 3600) * 1000) - 10_000,\n }\n }\n\n async function refreshAccessToken(current: TokenState): Promise<TokenState> {\n if (!current.refreshToken) return acquirePasswordGrantToken()\n const response = await fetchWithTimeout(tokenEndpointUrl, {\n method: 'POST',\n timeoutMs: resolveAkeneoRequestTimeoutMs(),\n headers: {\n accept: 'application/json',\n 'content-type': 'application/json',\n },\n body: JSON.stringify({\n grant_type: 'refresh_token',\n client_id: credentials.clientId,\n client_secret: credentials.clientSecret,\n refresh_token: current.refreshToken,\n }),\n })\n\n if (!response.ok) {\n return acquirePasswordGrantToken()\n }\n\n const payload = await response.json() as {\n access_token?: string\n refresh_token?: string\n expires_in?: number\n }\n if (!payload.access_token) return acquirePasswordGrantToken()\n return {\n accessToken: payload.access_token,\n refreshToken: payload.refresh_token ?? current.refreshToken ?? null,\n expiresAt: Date.now() + ((payload.expires_in ?? 3600) * 1000) - 10_000,\n }\n }\n\n async function ensureToken(forceRefresh = false): Promise<string> {\n if (!forceRefresh && tokenState && tokenState.expiresAt > Date.now()) {\n return tokenState.accessToken\n }\n tokenState = tokenState ? await refreshAccessToken(tokenState) : await acquirePasswordGrantToken()\n return tokenState.accessToken\n }\n\n async function request<T>(\n pathOrUrl: string,\n init: RequestInit = {},\n retried = false,\n rateLimitAttempts = 0,\n ): Promise<T> {\n const url = resolveAkeneoRequestUrl(pathOrUrl)\n const token = await ensureToken()\n\n const response = await fetchWithTimeout(url, {\n ...init,\n timeoutMs: resolveAkeneoRequestTimeoutMs(),\n headers: {\n accept: 'application/json',\n authorization: `Bearer ${token}`,\n ...init.headers,\n },\n })\n\n if (response.status === 401 && !retried) {\n await ensureToken(true)\n return request<T>(pathOrUrl, init, true, rateLimitAttempts)\n }\n\n if (response.status === 429) {\n if (rateLimitAttempts >= resolveAkeneoMaxRateLimitRetries()) {\n const body = await response.text()\n throw new Error(`Akeneo request failed (429): ${body}`)\n }\n await sleep(clampAkeneoRetryAfterMs(response.headers.get('retry-after')))\n return request<T>(pathOrUrl, init, retried, rateLimitAttempts + 1)\n }\n\n if (!response.ok) {\n const body = await response.text()\n throw new Error(`Akeneo request failed (${response.status}): ${body}`)\n }\n\n if (response.status === 204) {\n return null as T\n }\n\n return response.json() as Promise<T>\n }\n\n async function requestBinary(\n pathOrUrl: string,\n init: RequestInit = {},\n retried = false,\n rateLimitAttempts = 0,\n ): Promise<{\n buffer: Buffer\n contentType: string | null\n contentLength: number | null\n }> {\n const url = resolveAkeneoRequestUrl(pathOrUrl)\n const token = await ensureToken()\n\n const response = await fetchWithTimeout(url, {\n ...init,\n timeoutMs: resolveAkeneoRequestTimeoutMs(),\n headers: {\n authorization: `Bearer ${token}`,\n ...init.headers,\n },\n })\n\n if (response.status === 401 && !retried) {\n await ensureToken(true)\n return requestBinary(pathOrUrl, init, true, rateLimitAttempts)\n }\n\n if (response.status === 429) {\n if (rateLimitAttempts >= resolveAkeneoMaxRateLimitRetries()) {\n const body = await response.text()\n throw new Error(`Akeneo request failed (429): ${body}`)\n }\n await sleep(clampAkeneoRetryAfterMs(response.headers.get('retry-after')))\n return requestBinary(pathOrUrl, init, retried, rateLimitAttempts + 1)\n }\n\n if (!response.ok) {\n const body = await response.text()\n throw new Error(`Akeneo request failed (${response.status}): ${body}`)\n }\n\n const arrayBuffer = await response.arrayBuffer()\n return {\n buffer: Buffer.from(arrayBuffer),\n contentType: response.headers.get('content-type'),\n contentLength: Number(response.headers.get('content-length') ?? '') || arrayBuffer.byteLength || null,\n }\n }\n\n function buildUrl(path: string, params: Record<string, string | number | boolean | undefined | null>): string {\n const url = new URL(resolveAkeneoRequestUrl(path))\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null || value === '') continue\n url.searchParams.set(key, String(value))\n }\n return url.toString()\n }\n\n async function readList<T>(pathOrUrl: string, params?: Record<string, string | number | boolean | undefined | null>): Promise<{\n items: T[]\n nextUrl: string | null\n totalEstimate: number | null\n }> {\n const payload = await request<AkeneoListResponse<T>>(\n params ? buildUrl(pathOrUrl, params) : pathOrUrl,\n )\n return {\n items: Array.isArray(payload?._embedded?.items) ? payload._embedded.items : [],\n nextUrl: typeof payload?._links?.next?.href === 'string'\n ? normalizeAkeneoNextUrl(payload._links.next.href)\n : null,\n totalEstimate: typeof payload?.items_count === 'number' ? payload.items_count : null,\n }\n }\n\n async function countProducts(updatedAfter?: string | null): Promise<number | null> {\n const params: Record<string, string | number | boolean | undefined | null> = {\n limit: 1,\n pagination_type: 'page',\n with_count: true,\n }\n const normalizedUpdatedAfter = normalizeAkeneoDateTime(updatedAfter)\n if (normalizedUpdatedAfter) {\n params.search = JSON.stringify({\n updated: [\n {\n operator: '>',\n value: normalizedUpdatedAfter,\n },\n ],\n })\n }\n\n const page = await readList<AkeneoProduct>('/api/rest/v1/products-uuid', params)\n return page.totalEstimate\n }\n\n async function getSystemProbe(): Promise<{ version: string | null }> {\n try {\n const result = await request<Record<string, unknown>>('/api/rest/v1/system-information')\n return {\n version: typeof result.pim_version === 'string' ? result.pim_version : null,\n }\n } catch {\n const attrs = await readList<AkeneoAttribute>('/api/rest/v1/attributes', {\n limit: 1,\n pagination_type: 'page',\n })\n return { version: attrs.items.length >= 0 ? null : null }\n }\n }\n\n async function listProducts(options: {\n nextUrl?: string | null\n batchSize: number\n updatedAfter?: string | null\n }): Promise<{ items: AkeneoProduct[]; nextUrl: string | null; totalEstimate: number | null }> {\n if (options.nextUrl) {\n const page = await readList<AkeneoProduct>(normalizeAkeneoNextUrl(sanitizeAkeneoProductNextUrl(options.nextUrl)))\n return {\n ...page,\n nextUrl: page.nextUrl ? normalizeAkeneoNextUrl(sanitizeAkeneoProductNextUrl(page.nextUrl)) : null,\n }\n }\n const params: Record<string, string | number | boolean | undefined | null> = {\n limit: Math.min(Math.max(options.batchSize, 1), 100),\n pagination_type: 'search_after',\n with_count: false,\n }\n const normalizedUpdatedAfter = normalizeAkeneoDateTime(options.updatedAfter)\n if (normalizedUpdatedAfter) {\n params.search = JSON.stringify({\n updated: [\n {\n operator: '>',\n value: normalizedUpdatedAfter,\n },\n ],\n })\n }\n const [page, totalEstimate] = await Promise.all([\n readList<AkeneoProduct>('/api/rest/v1/products-uuid', params),\n countProducts(normalizedUpdatedAfter).catch(() => null),\n ])\n return {\n ...page,\n totalEstimate: totalEstimate ?? page.totalEstimate,\n nextUrl: page.nextUrl ? normalizeAkeneoNextUrl(sanitizeAkeneoProductNextUrl(page.nextUrl)) : null,\n }\n }\n\n async function listCategories(nextUrl?: string | null, batchSize = 100): Promise<{ items: AkeneoCategory[]; nextUrl: string | null; totalEstimate: number | null }> {\n if (nextUrl) return readList<AkeneoCategory>(nextUrl)\n return readList<AkeneoCategory>('/api/rest/v1/categories', {\n limit: Math.min(Math.max(batchSize, 1), 100),\n pagination_type: 'page',\n with_count: true,\n })\n }\n\n async function listAttributes(nextUrl?: string | null, batchSize = 100): Promise<{ items: AkeneoAttribute[]; nextUrl: string | null; totalEstimate: number | null }> {\n if (nextUrl) return readList<AkeneoAttribute>(nextUrl)\n return readList<AkeneoAttribute>('/api/rest/v1/attributes', {\n limit: Math.min(Math.max(batchSize, 1), 100),\n pagination_type: 'page',\n with_count: true,\n })\n }\n\n async function listFamilies(nextUrl?: string | null, batchSize = 100): Promise<{ items: AkeneoFamily[]; nextUrl: string | null; totalEstimate: number | null }> {\n if (nextUrl) return readList<AkeneoFamily>(nextUrl)\n return readList<AkeneoFamily>('/api/rest/v1/families', {\n limit: Math.min(Math.max(batchSize, 1), 100),\n pagination_type: 'page',\n with_count: true,\n })\n }\n\n async function listFamilyVariants(\n familyCode: string,\n nextUrl?: string | null,\n batchSize = 100,\n ): Promise<{ items: AkeneoFamilyVariant[]; nextUrl: string | null; totalEstimate: number | null }> {\n if (nextUrl) return readList<AkeneoFamilyVariant>(nextUrl)\n return readList<AkeneoFamilyVariant>(`/api/rest/v1/families/${encodeURIComponent(familyCode)}/variants`, {\n limit: Math.min(Math.max(batchSize, 1), 100),\n with_count: false,\n })\n }\n\n async function listChannels(): Promise<AkeneoChannel[]> {\n const response = await readList<AkeneoChannel>('/api/rest/v1/channels', {\n limit: 100,\n pagination_type: 'page',\n with_count: true,\n })\n return response.items\n }\n\n async function listLocales(): Promise<AkeneoLocale[]> {\n const response = await readList<AkeneoLocale>('/api/rest/v1/locales', {\n limit: 100,\n pagination_type: 'page',\n with_count: true,\n })\n return response.items\n }\n\n async function getCategory(code: string): Promise<AkeneoCategory | null> {\n if (!categoryCache.has(code)) {\n categoryCache.set(code, request<AkeneoCategory>(`/api/rest/v1/categories/${encodeURIComponent(code)}`).catch(() => null))\n }\n return categoryCache.get(code) ?? null\n }\n\n async function getAttribute(code: string): Promise<AkeneoAttribute | null> {\n if (!attributeCache.has(code)) {\n attributeCache.set(code, request<AkeneoAttribute>(`/api/rest/v1/attributes/${encodeURIComponent(code)}`).catch(() => null))\n }\n return attributeCache.get(code) ?? null\n }\n\n async function listAttributeOptions(attributeCode: string): Promise<AkeneoAttributeOption[]> {\n const options: AkeneoAttributeOption[] = []\n let nextUrl: string | null | undefined = null\n do {\n const now = Date.now()\n const waitMs = Math.max(0, 350 - (now - lastAttributeOptionRequestAt))\n if (waitMs > 0) await sleep(waitMs)\n lastAttributeOptionRequestAt = Date.now()\n const page: { items: AkeneoAttributeOption[]; nextUrl: string | null; totalEstimate: number | null } = await readList<AkeneoAttributeOption>(\n nextUrl ?? `/api/rest/v1/attributes/${encodeURIComponent(attributeCode)}/options`,\n nextUrl\n ? undefined\n : {\n limit: 100,\n pagination_type: 'page',\n with_count: true,\n },\n )\n options.push(...page.items)\n nextUrl = page.nextUrl\n } while (nextUrl)\n return options\n }\n\n async function getFamily(code: string): Promise<AkeneoFamily | null> {\n if (!familyCache.has(code)) {\n familyCache.set(code, request<AkeneoFamily>(`/api/rest/v1/families/${encodeURIComponent(code)}`).catch(() => null))\n }\n return familyCache.get(code) ?? null\n }\n\n async function getFamilyVariant(familyCode: string, familyVariantCode: string): Promise<AkeneoFamilyVariant | null> {\n const cacheKey = `${familyCode}:${familyVariantCode}`\n if (!familyVariantCache.has(cacheKey)) {\n familyVariantCache.set(\n cacheKey,\n request<AkeneoFamilyVariant>(`/api/rest/v1/families/${encodeURIComponent(familyCode)}/variants/${encodeURIComponent(familyVariantCode)}`).catch(() => null),\n )\n }\n return familyVariantCache.get(cacheKey) ?? null\n }\n\n async function getProductModel(code: string): Promise<AkeneoProductModel | null> {\n if (!productModelCache.has(code)) {\n productModelCache.set(code, request<AkeneoProductModel>(`/api/rest/v1/product-models/${encodeURIComponent(code)}`).catch(() => null))\n }\n return productModelCache.get(code) ?? null\n }\n\n async function getMediaFile(code: string): Promise<AkeneoMediaFile | null> {\n if (!mediaFileCache.has(code)) {\n mediaFileCache.set(code, request<AkeneoMediaFile>(`/api/rest/v1/media-files/${encodeAkeneoPathParam(code)}`).catch(() => null))\n }\n return mediaFileCache.get(code) ?? null\n }\n\n async function downloadMediaFile(codeOrUrl: string): Promise<{\n buffer: Buffer\n contentType: string | null\n contentLength: number | null\n fileName: string | null\n code: string | null\n }> {\n if (codeOrUrl.startsWith('http://') || codeOrUrl.startsWith('https://') || codeOrUrl.startsWith('/')) {\n const binary = await requestBinary(codeOrUrl)\n return {\n ...binary,\n fileName: null,\n code: null,\n }\n }\n\n const mediaFile = await getMediaFile(codeOrUrl)\n if (!mediaFile) {\n throw new Error(`Akeneo media file ${codeOrUrl} was not found`)\n }\n const downloadHref = mediaFile._links?.download?.href\n const binary = await requestBinary(downloadHref || `/api/rest/v1/media-files/${encodeAkeneoPathParam(codeOrUrl)}/download`)\n return {\n ...binary,\n fileName: typeof mediaFile.original_filename === 'string' && mediaFile.original_filename.trim().length > 0\n ? mediaFile.original_filename.trim()\n : null,\n code: mediaFile.code ?? codeOrUrl,\n }\n }\n\n async function collectDiscoveryData(): Promise<{\n locales: Array<{ code: string; label: string; enabled: boolean }>\n channels: Array<{ code: string; label: string; locales: string[] }>\n attributes: Array<{ code: string; type: string; label: string; localizable: boolean; scopable: boolean; group?: string; metricFamily?: string }>\n families: Array<{ code: string; label: string; attributeCount: number }>\n familyVariants: Array<{ familyCode: string; code: string; label: string; axes: string[]; attributes: string[] }>\n version: string | null\n }> {\n const [locales, channels, attributes, families, probe] = await Promise.all([\n listLocales().catch(() => []),\n listChannels().catch(() => []),\n listAttributes(null, 100),\n listFamilies(null, 100),\n getSystemProbe().catch(() => ({ version: null })),\n ])\n\n const familyVariants: Array<{ familyCode: string; code: string; label: string; axes: string[]; attributes: string[] }> = []\n for (const family of families.items) {\n let nextUrl: string | null = null\n do {\n const page: { items: AkeneoFamilyVariant[]; nextUrl: string | null; totalEstimate: number | null } = await listFamilyVariants(family.code, nextUrl, 100).catch(() => ({\n items: [] as AkeneoFamilyVariant[],\n nextUrl: null,\n totalEstimate: null,\n }))\n familyVariants.push(...page.items.map((familyVariant) => ({\n familyCode: family.code,\n code: familyVariant.code,\n label: labelFromLocalizedRecord(familyVariant.labels ?? null, null, familyVariant.code),\n axes: dedupeStrings(familyVariant.variant_attribute_sets?.flatMap((set: { axes?: string[] }) => Array.isArray(set.axes) ? set.axes : []) ?? []),\n attributes: dedupeStrings(familyVariant.variant_attribute_sets?.flatMap((set: { attributes?: string[] }) => Array.isArray(set.attributes) ? set.attributes : []) ?? []),\n })))\n nextUrl = page.nextUrl\n } while (nextUrl)\n }\n\n return {\n locales: locales.map((locale) => ({\n code: locale.code,\n label: labelFromLocalizedRecord(locale.labels ?? null, null, locale.code),\n enabled: locale.enabled ?? false,\n })),\n channels: channels.map((channel) => ({\n code: channel.code,\n label: labelFromLocalizedRecord(channel.labels ?? null, null, channel.code),\n locales: dedupeStrings(channel.locales ?? []),\n })),\n attributes: attributes.items.map((attribute) => ({\n code: attribute.code,\n type: attribute.type,\n label: labelFromLocalizedRecord(attribute.labels ?? null, null, attribute.code),\n localizable: Boolean(attribute.localizable),\n scopable: Boolean(attribute.scopable),\n group: typeof attribute.group === 'string' && attribute.group.trim().length > 0 ? attribute.group.trim() : undefined,\n metricFamily: typeof attribute.metric_family === 'string' && attribute.metric_family.trim().length > 0\n ? attribute.metric_family.trim()\n : undefined,\n })),\n families: families.items.map((family) => ({\n code: family.code,\n label: labelFromLocalizedRecord(family.labels ?? null, null, family.code),\n attributeCount: Array.isArray(family.attributes) ? family.attributes.length : 0,\n })),\n familyVariants,\n version: probe.version,\n }\n }\n\n return {\n credentials,\n getSystemProbe,\n collectDiscoveryData,\n listProducts,\n listCategories,\n listAttributes,\n listFamilies,\n listFamilyVariants,\n listChannels,\n listLocales,\n getCategory,\n getAttribute,\n listAttributeOptions,\n getFamily,\n getFamilyVariant,\n getProductModel,\n getMediaFile,\n downloadMediaFile,\n }\n}\n\nexport type { AkeneoClient }\n"],
5
+ "mappings": "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",
6
6
  "names": ["page", "binary"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/sync-akeneo",
3
- "version": "0.6.6-develop.5654.1.ca21e35f26",
3
+ "version": "0.6.6-develop.5672.1.11e27afad2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -52,18 +52,18 @@
52
52
  }
53
53
  },
54
54
  "dependencies": {
55
- "@open-mercato/core": "0.6.6-develop.5654.1.ca21e35f26",
56
- "@open-mercato/ui": "0.6.6-develop.5654.1.ca21e35f26",
55
+ "@open-mercato/core": "0.6.6-develop.5672.1.11e27afad2",
56
+ "@open-mercato/ui": "0.6.6-develop.5672.1.11e27afad2",
57
57
  "node-html-markdown": "^2.0.0"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "@mikro-orm/postgresql": "^7.0.14",
61
- "@open-mercato/shared": "0.6.6-develop.5654.1.ca21e35f26",
61
+ "@open-mercato/shared": "0.6.6-develop.5672.1.11e27afad2",
62
62
  "react": "^19.0.0",
63
63
  "react-dom": "^19.0.0"
64
64
  },
65
65
  "devDependencies": {
66
- "@open-mercato/shared": "0.6.6-develop.5654.1.ca21e35f26",
66
+ "@open-mercato/shared": "0.6.6-develop.5672.1.11e27afad2",
67
67
  "@types/jest": "^30.0.0",
68
68
  "@types/react": "^19.2.17",
69
69
  "@types/react-dom": "^19.2.3",
@@ -1,3 +1,4 @@
1
+ import { FetchTimeoutError } from '@open-mercato/shared/lib/http/fetchWithTimeout'
1
2
  import { createAkeneoClient, encodeAkeneoPathParam, normalizeAkeneoDateTime, sanitizeAkeneoProductNextUrl, validateAkeneoApiUrl } from '../lib/client'
2
3
 
3
4
  const validCredentials = {
@@ -222,6 +223,10 @@ describe('akeneo client security', () => {
222
223
  })
223
224
 
224
225
  describe('akeneo client resilience (issue #2976)', () => {
226
+ // Mirrors DEFAULT_AKENEO_REQUEST_TIMEOUT_MS in client.ts: the per-request
227
+ // timeout `fetchWithTimeout` schedules when no
228
+ // OM_INTEGRATION_AKENEO_REQUEST_TIMEOUT_MS override is set (none of these tests set it).
229
+ const AKENEO_DEFAULT_REQUEST_TIMEOUT_MS = 30_000
225
230
  const originalFetch = global.fetch
226
231
  const originalSetTimeout = global.setTimeout
227
232
  const originalMaxRetries = process.env.OM_INTEGRATION_AKENEO_MAX_RATE_LIMIT_RETRIES
@@ -236,8 +241,15 @@ describe('akeneo client resilience (issue #2976)', () => {
236
241
  global.fetch = jest.fn() as typeof fetch
237
242
  capturedDelays = []
238
243
  // Fire backoff/retry-after sleeps synchronously so the retry loop runs
239
- // without real timers while we assert the requested wait durations.
244
+ // without real timers while we assert the requested wait durations. The
245
+ // shared `fetchWithTimeout` helper also schedules a per-request timeout timer
246
+ // (`AKENEO_DEFAULT_REQUEST_TIMEOUT_MS`); delegate that one to the real timer —
247
+ // the helper clears it in its `finally` before the mocked fetch resolves, so
248
+ // it never fires and never pollutes the recorded backoff delays.
240
249
  global.setTimeout = ((callback: () => void, ms?: number) => {
250
+ if (ms === AKENEO_DEFAULT_REQUEST_TIMEOUT_MS) {
251
+ return originalSetTimeout(callback, ms)
252
+ }
241
253
  capturedDelays.push(typeof ms === 'number' ? ms : 0)
242
254
  callback()
243
255
  return 0 as unknown as ReturnType<typeof setTimeout>
@@ -305,4 +317,14 @@ describe('akeneo client resilience (issue #2976)', () => {
305
317
  expect(tokenInit.signal).toBeInstanceOf(AbortSignal)
306
318
  expect(requestInit.signal).toBeInstanceOf(AbortSignal)
307
319
  })
320
+
321
+ it('surfaces the shared-helper FetchTimeoutError when an authenticated request times out (issue #3068)', async () => {
322
+ const fetchMock = global.fetch as jest.MockedFunction<typeof fetch>
323
+ fetchMock
324
+ .mockResolvedValueOnce(tokenResponse())
325
+ .mockRejectedValueOnce(new FetchTimeoutError('https://tenant.cloud.akeneo.com/api/rest/v1/channels', 30_000))
326
+
327
+ const client = createAkeneoClient(validCredentials)
328
+ await expect(client.listChannels()).rejects.toBeInstanceOf(FetchTimeoutError)
329
+ })
308
330
  })
@@ -1,3 +1,4 @@
1
+ import { fetchWithTimeout } from '@open-mercato/shared/lib/http/fetchWithTimeout'
1
2
  import { dedupeStrings, labelFromLocalizedRecord, safeRecord, type AkeneoAttribute, type AkeneoAttributeOption, type AkeneoCategory, type AkeneoChannel, type AkeneoCredentialShape, type AkeneoFamily, type AkeneoFamilyVariant, type AkeneoLocale, type AkeneoProduct, type AkeneoProductModel } from './shared'
2
3
 
3
4
  type TokenState = {
@@ -250,9 +251,9 @@ export function createAkeneoClient(credentialsInput: Record<string, unknown>) {
250
251
  }
251
252
 
252
253
  async function acquirePasswordGrantToken(): Promise<TokenState> {
253
- const response = await fetch(tokenEndpointUrl, {
254
+ const response = await fetchWithTimeout(tokenEndpointUrl, {
254
255
  method: 'POST',
255
- signal: AbortSignal.timeout(resolveAkeneoRequestTimeoutMs()),
256
+ timeoutMs: resolveAkeneoRequestTimeoutMs(),
256
257
  headers: {
257
258
  accept: 'application/json',
258
259
  'content-type': 'application/json',
@@ -289,9 +290,9 @@ export function createAkeneoClient(credentialsInput: Record<string, unknown>) {
289
290
 
290
291
  async function refreshAccessToken(current: TokenState): Promise<TokenState> {
291
292
  if (!current.refreshToken) return acquirePasswordGrantToken()
292
- const response = await fetch(tokenEndpointUrl, {
293
+ const response = await fetchWithTimeout(tokenEndpointUrl, {
293
294
  method: 'POST',
294
- signal: AbortSignal.timeout(resolveAkeneoRequestTimeoutMs()),
295
+ timeoutMs: resolveAkeneoRequestTimeoutMs(),
295
296
  headers: {
296
297
  accept: 'application/json',
297
298
  'content-type': 'application/json',
@@ -338,9 +339,9 @@ export function createAkeneoClient(credentialsInput: Record<string, unknown>) {
338
339
  const url = resolveAkeneoRequestUrl(pathOrUrl)
339
340
  const token = await ensureToken()
340
341
 
341
- const response = await fetch(url, {
342
+ const response = await fetchWithTimeout(url, {
342
343
  ...init,
343
- signal: init.signal ?? AbortSignal.timeout(resolveAkeneoRequestTimeoutMs()),
344
+ timeoutMs: resolveAkeneoRequestTimeoutMs(),
344
345
  headers: {
345
346
  accept: 'application/json',
346
347
  authorization: `Bearer ${token}`,
@@ -387,9 +388,9 @@ export function createAkeneoClient(credentialsInput: Record<string, unknown>) {
387
388
  const url = resolveAkeneoRequestUrl(pathOrUrl)
388
389
  const token = await ensureToken()
389
390
 
390
- const response = await fetch(url, {
391
+ const response = await fetchWithTimeout(url, {
391
392
  ...init,
392
- signal: init.signal ?? AbortSignal.timeout(resolveAkeneoRequestTimeoutMs()),
393
+ timeoutMs: resolveAkeneoRequestTimeoutMs(),
393
394
  headers: {
394
395
  authorization: `Bearer ${token}`,
395
396
  ...init.headers,