105 lines
2.8 KiB
Markdown
105 lines
2.8 KiB
Markdown
# 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>;
|
|
}
|
|
```
|
|
|
|
The cache key is the full request URL. `ttlMs` is in milliseconds and comes from `cacheTtlMs` or per-request `revalidateSeconds`.
|
|
|
|
## 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.
|