2026-05-29 13:35:09 -05:00
# Caching, Retries, and Failure Semantics
## GET lifecycle
1. Build full URL from `baseUrl` , `path` , and query params.
2. Check cache by full URL.
3. Fetch with auth headers and timeout on cache miss.
4. Retry network errors, timeout errors, `429` , and `5xx` .
5. Parse JSON.
6. Throw `SocialhoseError` for unsupported non-OK responses.
7. Store successful parsed response in cache.
## POST lifecycle
1. Build URL.
2. Fetch JSON body with auth headers and timeout.
3. Retry network errors, timeout errors, `429` , and `5xx` .
4. Parse JSON.
5. Return `{ status, data }` .
6. Never cache.
## Retry policy
Defaults:
``` ts
retries : 3 ,
retryDelayMs : ( attempt ) = > 400 * 2 * * attempt + Math . random ( ) * 200 ,
timeoutMs : 8_000 ,
```
Retries apply to transient conditions only:
- network failures
- SDK timeout/abort failures
- `429 Too Many Requests`
- `5xx` server errors
Retries do not apply to normal client errors such as `400` , `401` , `403` , and `404` .
## Cache contract
``` ts
interface Cache {
get ( key : string ) : Promise < unknown | undefined > ;
set ( key : string , value : unknown , ttlMs : number ) : Promise < void > ;
delete ( key : string ) : Promise < void > ;
}
```
2026-05-29 14:39:04 -05:00
The cache key is the full request URL. `ttlMs` is in milliseconds and comes from `cacheTtlMs` or per-request `revalidateSeconds` . A non-positive `ttlMs` means "do not cache"; built-in caches delete/skip the entry rather than storing it forever.
2026-05-29 13:35:09 -05:00
## Redis-style cache example
``` ts
import { SocialhoseClient , type Cache } from '@socialhose/api' ;
class RedisJsonCache implements Cache {
constructor ( private redis : {
get ( k : string ) : Promise < string | null > ;
set ( k : string , v : string , mode : 'PX' , ttl : number ) : Promise < unknown > ;
del ( k : string ) : Promise < unknown > ;
} ) { }
async get ( key : string ) {
const value = await this . redis . get ( key ) ;
return value == null ? undefined : JSON . parse ( value ) ;
}
async set ( key : string , value : unknown , ttlMs : number ) {
if ( ttlMs <= 0 ) return ;
await this . redis . set ( key , JSON . stringify ( value ) , 'PX' , ttlMs ) ;
}
async delete ( key : string ) {
await this . redis . del ( key ) ;
}
}
const socialhose = new SocialhoseClient ( {
apiKey : process.env.SOCIALHOSE_API_KEY ! ,
cache : new RedisJsonCache ( redis ) ,
} ) ;
```
## Method failure semantics
Campaign, analytics, mention, and list methods:
- Throw `SocialhoseError` after retry exhaustion or unsupported non-OK responses.
- Return normalized arrays when the API wraps arrays in properties such as `series` , `platforms` , or `keywords` .
`inviteMailingListMember()` :
- `201 + status: invited` -> `outcome: 'invited'` .
- `409` -> `outcome: 'already'` .
- Unexpected success/conflict shapes -> `outcome: 'error'` .
- Other non-OK statuses throw.
Entity methods:
- `getEntityBrief()` throws if the mention search fails.
- `getEntityStats()` requires the initial brief but treats later subrequests as best-effort.
- `getEntityBriefs()` skips failed terms and returns successful entries.