Przejdź do głównej zawartości

Retry i obsluga bledow

Pakiet @ksefnik/http zawiera dedykowany mechanizm retry (withHttpRetry) zoptymalizowany pod limity KSeF API — obsluguje naglowek Retry-After dla bledow 429 i exponential backoff z jitterem dla bledow 5xx.

import { withHttpRetry } from '@ksefnik/http'
const result = await withHttpRetry(
() => fetchInvoicesHttp(http, params),
{ maxAttempts: 5, baseDelayMs: 2000 },
)

Wrapper withHttpRetry opakowuje dowolna funkcje asynchroniczna i automatycznie ponawia ja w przypadku bledow ponawianych.

interface HttpRetryOptions {
maxAttempts?: number // domyslnie 3
baseDelayMs?: number // domyslnie 1000 ms
maxDelayMs?: number // domyslnie 10000 ms
maxRetryAfterMs?: number // domyslnie 30000 ms
}
ParametrDomyslnieOpis
maxAttempts3Maksymalna liczba prob (lacznie z pierwsza)
baseDelayMs1000 msBazowe opoznienie miedzy probami
maxDelayMs10000 msMaksymalne opoznienie exponential backoff
maxRetryAfterMs30000 msMaksymalny czas oczekiwania na Retry-After

Mechanizm stosuje rozne strategie w zaleznosci od typu bledu:

Gdy KSeF zwroci status 429, blad KsefRateLimitError zawiera pole retryAfter (w sekundach) z naglowka Retry-After. Klient czeka dokladnie tyle, ile wskazuje serwer (z clampem do maxRetryAfterMs):

waitMs = min(retryAfter * 1000, maxRetryAfterMs)
waitMs = max(waitMs, baseDelayMs)

Dla bledow 500-599 i bledow sieciowych (brak statusCode) stosowany jest exponential backoff z jitterem:

delay = min(baseDelayMs * 2^(attempt-1) + jitter, maxDelayMs)
jitter = Math.random() * baseDelayMs * 0.1
ProbaBazowe opoznienieZ jitterem (przyklad)
10 ms (natychmiast)—
21000 ms~1080 ms
32000 ms~2050 ms
4*4000 ms~4030 ms

*Przy domyslnym maxAttempts: 3 czwarta proba nie nastapi.

Bledy klienta (4xx z wyjatkiem 429) i nieznane bledy rzucane sa natychmiast — ponawianie nie pomoze:

Status HTTPPonawianyStrategia
429TakRetry-After z naglowka
500-599TakExponential backoff + jitter
Brak (siec)TakExponential backoff + jitter
400-428, 430-499NieRzucony natychmiast
Nieznany bladNieRzucony natychmiast

Pakiet eksportuje trzy klasy bledow, dziedziczace po KsefApiError z @ksefnik/shared:

Bazowa klasa dla wszystkich bledow HTTP. Rozszerza KsefApiError z @ksefnik/shared o pola specyficzne dla klienta HTTP:

class KsefApiError extends SharedKsefApiError {
statusCode?: number
requestId?: string
detailCode?: string
}
PoleTypOpis
messagestringOpis bledu
statusCodenumber | undefinedKod statusu HTTP
requestIdstring | undefinedID zapytania (jesli serwer zwrocil)
detailCodestring | undefinedKod szczegolowy (np. QUERY_TRUNCATED, AUTH_FAILED)
contextRecord<string, unknown> | undefinedDodatkowy kontekst

Blad autentykacji — dziedziczy po KsefApiError:

class KsefAuthError extends KsefApiError {
// Uzyty przy bledach w procesie challenge-response
}

Blad rate limitingu (HTTP 429) — zawiera pole retryAfter:

class KsefRateLimitError extends KsefApiError {
retryAfter: number // sekundy do nastepnej proby
}
EndpointLimit
/invoices/query/metadata8 req/s, 16 req/min
Pozostale endpointyLimity ogolne API KSeF
import { createHttpAdapter } from '@ksefnik/http'
// Retry wbudowany w adapter -- domyslnie 3 proby
const adapter = createHttpAdapter({
nip: '5213456784',
token: process.env.KSEF_TOKEN!,
environment: 'production',
})
import { createHttpAdapter, KsefApiError, KsefAuthError, KsefRateLimitError } from '@ksefnik/http'
import { createKsefnik } from '@ksefnik/core'
const adapter = createHttpAdapter({
nip: '5213456784',
token: process.env.KSEF_TOKEN!,
environment: 'production',
})
const ksefnik = createKsefnik({ nip: '5213456784', adapter })
try {
await ksefnik.invoices.sync({
dateFrom: '2026-03-01',
dateTo: '2026-03-31',
})
} catch (error) {
if (error instanceof KsefRateLimitError) {
console.error(`Rate limit -- ponow za ${error.retryAfter}s`)
} else if (error instanceof KsefAuthError) {
console.error('Blad autentykacji -- sprawdz token KSeF')
} else if (error instanceof KsefApiError) {
if (error.detailCode === 'QUERY_TRUNCATED') {
console.error('Za duzo faktur -- zawez zakres dat')
} else {
console.error(`Blad KSeF ${error.statusCode}: ${error.message}`)
}
}
} finally {
await adapter.closeSession()
}