docs: expand JavaScript SDK documentation
This commit is contained in:
@@ -29,6 +29,15 @@ const mentions = await socialhose.getMentions({
|
||||
console.log(mentions.count);
|
||||
```
|
||||
|
||||
Documentation lives with the package:
|
||||
|
||||
- [Quick README](./sdks/javascript/README.md)
|
||||
- [Usage guide](./sdks/javascript/docs/GUIDE.md)
|
||||
- [API reference](./sdks/javascript/docs/API.md)
|
||||
- [Entity analytics](./sdks/javascript/docs/ENTITY_ANALYTICS.md)
|
||||
- [Caching and retries](./sdks/javascript/docs/CACHING_AND_RETRIES.md)
|
||||
- [Examples](./sdks/javascript/examples)
|
||||
|
||||
## Development
|
||||
|
||||
Use pnpm 9.x.
|
||||
|
||||
+31
-114
@@ -2,13 +2,15 @@
|
||||
|
||||
TypeScript SDK for the Socialhose Public API.
|
||||
|
||||
Use it from backend TypeScript/JavaScript to fetch campaigns, analytics, mentions, mailing lists, and SDK-composed entity analytics with authentication, retries, timeouts, and caching handled consistently.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install @socialhose/api
|
||||
```
|
||||
|
||||
Node 18+ is required because the SDK uses the built-in `fetch`, `Response`, and `AbortSignal.timeout` APIs. You can pass a custom `fetch` implementation if needed.
|
||||
Node 18+ is required because the SDK uses built-in `fetch`, `Response`, and `AbortSignal.timeout`. You can pass a custom `fetch` implementation if needed.
|
||||
|
||||
## Quickstart
|
||||
|
||||
@@ -28,6 +30,14 @@ const mentions = await socialhose.getMentions({
|
||||
console.log(mentions.count, mentions.results[0]?.content);
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Usage guide](./docs/GUIDE.md) — what the SDK is, setup, common workflows, operational guidance.
|
||||
- [API reference](./docs/API.md) — every public class, function, method, option, and exported type.
|
||||
- [Entity analytics](./docs/ENTITY_ANALYTICS.md) — how term-level analytics are built, accuracy safeguards, rate-limit guidance.
|
||||
- [Caching and retries](./docs/CACHING_AND_RETRIES.md) — cache contract, retry policy, failure semantics.
|
||||
- [Examples](./examples) — runnable TypeScript examples.
|
||||
|
||||
## Configuration
|
||||
|
||||
```ts
|
||||
@@ -42,127 +52,29 @@ const socialhose = new SocialhoseClient({
|
||||
|
||||
The SDK sends `Authorization: Api-Key <key>` and a browser-like `User-Agent` by default. The user-agent is intentional: the current Socialhose API edge rejects some non-browser requests.
|
||||
|
||||
## Endpoints
|
||||
## Main capabilities
|
||||
|
||||
### Campaigns
|
||||
- `getCampaigns()`
|
||||
- `getCampaign(id)`
|
||||
- Campaign discovery: `getCampaigns()`, `getCampaign(id)`, `getCampaignIdByMatch(match)`.
|
||||
- Analytics: `getOverview()`, `getTimeline()`, `getSentiment()`, `getShareOfVoice()`, `getPlatforms()`, `getTopKeywords()`, `getTrending()`, `getTopMentions()`.
|
||||
- Mentions: `getMentions()` with campaign, date, platform, sentiment, text search, pagination, and ordering filters.
|
||||
- Mailing lists: `getMailingLists()`, `inviteMailingListMember()`.
|
||||
- Entity analytics: `getEntityBrief()`, `getEntityStats()`, `getEntityBriefs()`.
|
||||
- Low-level access: `get(path, params)`, `post(path, body)`.
|
||||
- Caching: built-in `MemoryCache`, disabling via `NoopCache`, or any custom `Cache` implementation.
|
||||
|
||||
### Analytics
|
||||
- `getOverview(filters)`
|
||||
- `getTimeline(filters)`
|
||||
- `getSentiment(filters)`
|
||||
- `getShareOfVoice(filters)`
|
||||
- `getPlatforms(filters)`
|
||||
- `getTopKeywords(filters)`
|
||||
- `getTrending(filters)`
|
||||
- `getTopMentions(filters)`
|
||||
## Entity analytics warning
|
||||
|
||||
### Mentions
|
||||
- `getMentions(filters)`
|
||||
|
||||
### Mailing Lists
|
||||
- `getMailingLists()`
|
||||
- `inviteMailingListMember(listId, invite)`
|
||||
|
||||
### Entity Analytics
|
||||
- `getEntityBrief(term, campaignId?)` — one request: count + top-20 engagement sample with derived sentiment/platform
|
||||
- `getEntityStats(term, campaignId?)` — full dashboard: exact sentiment faceting, exact platform mix, 14-day cumulative-differenced timeline, 7d momentum
|
||||
- `getEntityBriefs(terms, campaignId?, concurrency?)` — batch entity resolution with bounded concurrency (default 20)
|
||||
- `getCampaignIdByMatch(substring)` — resolve a live campaign ID by matching its name
|
||||
|
||||
### Low-Level
|
||||
- `get(path, params)` for direct GET access
|
||||
- `post(path, body)` for direct POST access
|
||||
|
||||
## Filtering examples
|
||||
Entity analytics fan out multiple `/mentions/` requests per term. Use `cacheTtlMs`, `revalidateSeconds`, or a persistent shared cache to stay under the approximate 60 req/min API-key rate limit.
|
||||
|
||||
```ts
|
||||
await socialhose.getOverview({
|
||||
campaign_ids: 'campaign-id',
|
||||
date_from: '2026-05-01',
|
||||
date_to: '2026-05-29',
|
||||
platforms: 'twitter,reddit',
|
||||
sentiments: 'negative',
|
||||
});
|
||||
|
||||
await socialhose.getTimeline({
|
||||
campaign_ids: 'campaign-id',
|
||||
interval: 'day',
|
||||
const stats = await socialhose.getEntityStats('RSF', 'campaign-id', {
|
||||
revalidateSeconds: 900,
|
||||
});
|
||||
```
|
||||
|
||||
## Custom Caching
|
||||
|
||||
The SDK ships with `MemoryCache` (in-memory, per-entry TTL) and `NoopCache` (no caching). You can inject your own by implementing the `Cache` interface:
|
||||
|
||||
```ts
|
||||
import { SocialhoseClient, Cache } from '@socialhose/api';
|
||||
|
||||
class RedisCache implements Cache {
|
||||
async get(key: string) { /* redis.get(key) */ }
|
||||
async set(key: string, value: unknown, ttlMs: number) { /* redis.set(key, value, 'PX', ttlMs) */ }
|
||||
async delete(key: string) { /* redis.del(key) */ }
|
||||
}
|
||||
|
||||
const socialhose = new SocialhoseClient({
|
||||
apiKey: process.env.SOCIALHOSE_API_KEY!,
|
||||
cache: new RedisCache(),
|
||||
});
|
||||
```
|
||||
|
||||
Pass `revalidateSeconds` per request to control per-call TTL in your cache implementation:
|
||||
|
||||
```ts
|
||||
await socialhose.getMentions(
|
||||
{ content_search: 'ozempic' },
|
||||
{ revalidateSeconds: 3600 },
|
||||
);
|
||||
```
|
||||
|
||||
## Entity Analytics
|
||||
|
||||
Search across all mentions for a specific term, person, or organization:
|
||||
|
||||
```ts
|
||||
// Quick count + top mentions
|
||||
const brief = await socialhose.getEntityBrief('Burhan');
|
||||
console.log(brief.total, brief.sentiment, brief.platformMix);
|
||||
|
||||
// Full dashboard: exact distributions, timeline, momentum
|
||||
const stats = await socialhose.getEntityStats('RSF', 'campaign-id');
|
||||
console.log(
|
||||
stats.total,
|
||||
stats.momentumPct, // last 7 days vs prior 7
|
||||
stats.sentiment, // exact (facets reconcile) or estimated (from sample)
|
||||
stats.sparkline, // 14-day daily volume
|
||||
);
|
||||
|
||||
// Batch resolve many entities with bounded concurrency
|
||||
const briefs = await socialhose.getEntityBriefs(
|
||||
['Burhan', 'Hemedti', 'SAF', 'RSF'],
|
||||
'campaign-id',
|
||||
10, // concurrency
|
||||
);
|
||||
```
|
||||
|
||||
Entity analytics fan out multiple requests per entity (sentiment faceting: 3 calls; platform mix: 6 calls; timeline: 15 calls). Set `cacheTtlMs` or inject a persistent cache to stay under the ~60 req/min rate limit.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
**Cloudflare UA blocking.** The Socialhose API sits behind Cloudflare, which rejects some non-browser User-Agent headers. The SDK defaults to a Chrome 124 UA — don't change it unless you've verified the new UA works.
|
||||
|
||||
**Entity timeline uses cumulative differencing.** The analytics timeline endpoint is campaign-scoped and ignores `content_search`. The SDK facets `/mentions/` by day using cumulative `date_from`-only queries and subtracts consecutive counts. This avoids the API's `date_to` inclusivity bug: overlapping `[date_from, date_to]` windows share a day and double-count it. Don't "simplify" this to day windows.
|
||||
|
||||
**Sentiment reconciliation checks.** The `getEntityStats` exact sentiment and platform distributions validate that facet counts reconcile with the known total (e.g., positive + negative + neutral === total). If they don't match, the API silently dropped the `content_search` filter — the SDK falls back to estimates from the brief's sample rather than showing wrong data.
|
||||
|
||||
**Rate limit ~60 req/min per API key.** Entity analytics fan out many parallel requests. Use the `cache` option with a persistent store (Redis, Next.js Data Cache) to keep warm loads under the limit.
|
||||
|
||||
**Never expose the API key to the browser.** This SDK is designed for server-side use. Always set `SOCIALHOSE_API_KEY` as a server-side environment variable.
|
||||
|
||||
## Errors
|
||||
|
||||
Failed requests throw `SocialhoseError` with `status`, `path`, and response `body` when available.
|
||||
Failed requests throw `SocialhoseError` with `status`, `path`, `body`, and `cause` when available.
|
||||
|
||||
```ts
|
||||
import { SocialhoseError } from '@socialhose/api';
|
||||
@@ -176,12 +88,17 @@ try {
|
||||
}
|
||||
```
|
||||
|
||||
## Publishing
|
||||
## Development
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm test
|
||||
pnpm typecheck
|
||||
pnpm build
|
||||
npm publish --access public --provenance
|
||||
```
|
||||
|
||||
Publishing:
|
||||
|
||||
```bash
|
||||
npm publish --access public --provenance
|
||||
```
|
||||
|
||||
@@ -0,0 +1,366 @@
|
||||
# API Reference
|
||||
|
||||
Public API reference for `@socialhose/api`, the TypeScript SDK for the Socialhose Public API.
|
||||
|
||||
The SDK is intended for server-side JavaScript/TypeScript. It authenticates requests with an API key, wraps Socialhose REST endpoints, normalizes common response shapes, retries transient failures, applies request timeouts, and caches GET responses.
|
||||
|
||||
## Package exports
|
||||
|
||||
```ts
|
||||
import {
|
||||
SocialhoseClient,
|
||||
createSocialhoseClient,
|
||||
SocialhoseError,
|
||||
MemoryCache,
|
||||
NoopCache,
|
||||
type Cache,
|
||||
type SocialhoseClientOptions,
|
||||
type RequestOptions,
|
||||
type AnalyticsFilters,
|
||||
type MentionFilters,
|
||||
} from '@socialhose/api';
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
Every request sends:
|
||||
|
||||
- `Authorization: Api-Key <apiKey>`
|
||||
- `Accept: application/json`
|
||||
- a browser-like `User-Agent` by default
|
||||
- `Content-Type: application/json` for `POST`
|
||||
|
||||
Do not use this SDK directly in browser code. Keep `SOCIALHOSE_API_KEY` on the server.
|
||||
|
||||
## `SocialhoseClientOptions`
|
||||
|
||||
Configuration object for `new SocialhoseClient(options)` and `createSocialhoseClient(options)`.
|
||||
|
||||
- `apiKey: string` — Socialhose API key. Required; empty strings fail on first request.
|
||||
- `baseUrl?: string` — API root. Default: `https://socialhose.net/api/public/v1`.
|
||||
- `userAgent?: string` — request user-agent. Default is Chrome-like because the API edge may reject generic Node clients.
|
||||
- `fetch?: typeof fetch` — custom fetch implementation for tests, instrumentation, or runtimes without global fetch.
|
||||
- `timeoutMs?: number` — per-attempt timeout. Default: `8000`.
|
||||
- `retries?: number` — retry count after the first attempt. Default: `3`.
|
||||
- `retryDelayMs?: (attempt: number) => number` — retry backoff function. Default: exponential backoff with jitter.
|
||||
- `cacheTtlMs?: number` — default GET cache TTL in milliseconds. Default: `60000`; set `0` to disable the built-in memory cache.
|
||||
- `cache?: Cache` — custom cache implementation. If supplied, it replaces the internal cache.
|
||||
- `defaultHeaders?: Record<string, string>` — extra headers sent with every request.
|
||||
|
||||
## `RequestOptions`
|
||||
|
||||
Per-request options.
|
||||
|
||||
- `revalidateSeconds?: number` — override cache TTL for this call, in seconds. Passed only to `Cache.set`; not sent to the API.
|
||||
- `signal?: AbortSignal` — caller abort signal combined with the SDK timeout.
|
||||
- `headers?: Record<string, string>` — request-specific headers.
|
||||
|
||||
## `new SocialhoseClient(options)`
|
||||
|
||||
Creates an authenticated API client.
|
||||
|
||||
```ts
|
||||
const socialhose = new SocialhoseClient({
|
||||
apiKey: process.env.SOCIALHOSE_API_KEY!,
|
||||
timeoutMs: 8_000,
|
||||
retries: 3,
|
||||
cacheTtlMs: 60_000,
|
||||
});
|
||||
```
|
||||
|
||||
## `createSocialhoseClient(options)`
|
||||
|
||||
Factory wrapper around the constructor.
|
||||
|
||||
```ts
|
||||
const socialhose = createSocialhoseClient({ apiKey });
|
||||
```
|
||||
|
||||
Returns `SocialhoseClient`.
|
||||
|
||||
## Low-level HTTP methods
|
||||
|
||||
### `client.get<T>(path, params?, options?)`
|
||||
|
||||
Performs an authenticated cached GET request.
|
||||
|
||||
- `path: string` — endpoint path, with or without leading slash.
|
||||
- `params?: QueryParams` — query parameters. `undefined`, `null`, and `''` values are omitted.
|
||||
- `options?: RequestOptions` — cache TTL, abort signal, and extra headers.
|
||||
|
||||
Returns `Promise<T>`.
|
||||
|
||||
Behavior:
|
||||
|
||||
- Builds the URL from `baseUrl`, `path`, and `params`.
|
||||
- Uses the full URL as the cache key.
|
||||
- Retries network errors, timeouts, `429`, and `5xx` responses.
|
||||
- Throws `SocialhoseError` for non-OK responses after retries.
|
||||
|
||||
### `client.post<T>(path, body, options?)`
|
||||
|
||||
Performs an authenticated JSON POST request.
|
||||
|
||||
- `path: string` — endpoint path.
|
||||
- `body: unknown` — JSON-serialized request body.
|
||||
- `options?: RequestOptions` — abort signal and extra headers.
|
||||
|
||||
Returns `Promise<{ status: number; data: T | null }>`.
|
||||
|
||||
Behavior:
|
||||
|
||||
- Never uses the GET cache.
|
||||
- Sends `Content-Type: application/json`.
|
||||
- Returns the HTTP status so higher-level methods can normalize status-specific outcomes.
|
||||
- Lets POST `409` responses through for conflict normalization.
|
||||
|
||||
## Campaign methods
|
||||
|
||||
### `client.getCampaigns(options?)`
|
||||
|
||||
Fetches first-page campaigns.
|
||||
|
||||
Endpoint: `GET /campaigns/?page=1`
|
||||
|
||||
Returns `Promise<Campaign[]>`.
|
||||
|
||||
Use it to list accessible campaigns or populate campaign selectors. The helper reads page 1 only; use `get<Paginated<Campaign>>()` for manual pagination.
|
||||
|
||||
### `client.getCampaign(id, options?)`
|
||||
|
||||
Fetches one campaign by ID.
|
||||
|
||||
Endpoint: `GET /campaigns/{id}/`
|
||||
|
||||
Returns `Promise<Campaign>`.
|
||||
|
||||
Throws `SocialhoseError` when the campaign is missing or inaccessible.
|
||||
|
||||
### `client.getCampaignIdByMatch(match, options?)`
|
||||
|
||||
Returns the first campaign ID whose name contains `match`, case-insensitively.
|
||||
|
||||
Returns `Promise<string | undefined>`.
|
||||
|
||||
If campaign fetching fails or no campaign matches, returns `undefined`. Use exact IDs when names may collide.
|
||||
|
||||
## Analytics filters
|
||||
|
||||
`AnalyticsFilters` are passed through as query params:
|
||||
|
||||
- `campaign_ids?: string` — one ID or comma-separated IDs.
|
||||
- `date_from?: string` — lower date bound, commonly `YYYY-MM-DD`.
|
||||
- `date_to?: string` — upper date bound, commonly `YYYY-MM-DD`.
|
||||
- `platforms?: string` — platform or comma-separated platforms.
|
||||
- `sentiments?: string` — sentiment or comma-separated sentiments.
|
||||
|
||||
## Analytics methods
|
||||
|
||||
### `client.getOverview(filters?, options?)`
|
||||
|
||||
Endpoint: `GET /analytics/overview/`
|
||||
|
||||
Returns `Promise<Overview>` with total mentions, authors, estimated reach, sentiment distribution, platform breakdown, engagement, and growth metrics.
|
||||
|
||||
The SDK coerces `total_mentions` and `total_authors` to numbers because some API responses may return numeric strings.
|
||||
|
||||
### `client.getTimeline(filters?, options?)`
|
||||
|
||||
Endpoint: `GET /analytics/timeline/`
|
||||
|
||||
Filters include `interval?: 'day' | 'week' | 'month'`, defaulting to `day`.
|
||||
|
||||
Returns `Promise<TimelinePoint[]>`. If the API response lacks `series`, returns `[]`.
|
||||
|
||||
### `client.getSentiment(filters?, options?)`
|
||||
|
||||
Endpoint: `GET /analytics/sentiment/`
|
||||
|
||||
Returns `Promise<{ distribution: SentimentSplit; by_platform: Record<string, SentimentSplit> }>`.
|
||||
|
||||
### `client.getShareOfVoice(filters?, options?)`
|
||||
|
||||
Endpoint: `GET /analytics/share-of-voice/`
|
||||
|
||||
Returns `Promise<{ total_mentions: number; campaigns: ShareOfVoiceItem[] }>`.
|
||||
|
||||
Each campaign row includes mention count, share percentage, engagement, and sentiment.
|
||||
|
||||
### `client.getPlatforms(filters?, options?)`
|
||||
|
||||
Endpoint: `GET /analytics/platforms/`
|
||||
|
||||
Returns `Promise<PlatformStat[]>`. If the API response lacks `platforms`, returns `[]`.
|
||||
|
||||
### `client.getTopKeywords(filters?, options?)`
|
||||
|
||||
Endpoint: `GET /analytics/top-keywords/`
|
||||
|
||||
Filters include `limit?: number`, defaulting to `12`.
|
||||
|
||||
Returns `Promise<KeywordStat[]>`. If the API response lacks `keywords`, returns `[]`.
|
||||
|
||||
### `client.getTrending(filters?, options?)`
|
||||
|
||||
Endpoint: `GET /analytics/trending/`
|
||||
|
||||
Filters include `limit?: number`, defaulting to `8`.
|
||||
|
||||
Returns `Promise<TrendingItem[]>`. If the API response lacks `trending`, returns `[]`.
|
||||
|
||||
### `client.getTopMentions(filters?, options?)`
|
||||
|
||||
Endpoint: `GET /analytics/top-mentions/`
|
||||
|
||||
Filters include `limit?: number`, defaulting to `6`.
|
||||
|
||||
Returns `Promise<TopMention[]>`. If the API response lacks `mentions`, returns `[]`.
|
||||
|
||||
## Mention methods
|
||||
|
||||
### `client.getMentions(filters?, optionsOrRevalidate?)`
|
||||
|
||||
Endpoint: `GET /mentions/`
|
||||
|
||||
Returns `Promise<Paginated<Mention>>`.
|
||||
|
||||
`MentionFilters` include all `AnalyticsFilters` plus:
|
||||
|
||||
- `page?: number` — page number, defaulting to `1`.
|
||||
- `content_search?: string` — text search term.
|
||||
- `ordering?: string` — sort key, such as `-published_at` or `-engagement_count`.
|
||||
|
||||
The second argument may be `RequestOptions` or the legacy shorthand `number` for `revalidateSeconds`.
|
||||
|
||||
## Mailing-list methods
|
||||
|
||||
### `client.getMailingLists(options?)`
|
||||
|
||||
Endpoint: `GET /mailing-lists/?page=1`
|
||||
|
||||
Returns `Promise<MailingList[]>` from the first page.
|
||||
|
||||
### `client.inviteMailingListMember(listId, invite, options?)`
|
||||
|
||||
Endpoint: `POST /mailing-lists/{listId}/members/`
|
||||
|
||||
`invite` fields:
|
||||
|
||||
- `email: string`
|
||||
- `first_name?: string`
|
||||
- `last_name?: string`
|
||||
- `invitation_message?: string`
|
||||
|
||||
Returns `Promise<InviteResult>`:
|
||||
|
||||
- `{ outcome: 'invited', invitation }` for `201` plus `status: 'invited'`.
|
||||
- `{ outcome: 'already', detail }` for `409` conflicts.
|
||||
- `{ outcome: 'error', detail }` for unexpected successful/conflict response shapes.
|
||||
|
||||
Other non-OK statuses throw `SocialhoseError`.
|
||||
|
||||
## Entity analytics methods
|
||||
|
||||
Entity analytics are SDK-composed views built on `/mentions/` with `content_search`. They provide term/person/org/topic analytics when native analytics endpoints are campaign-scoped.
|
||||
|
||||
### `client.getEntityBrief(term, campaignId?, options?)`
|
||||
|
||||
Runs one mention search ordered by engagement.
|
||||
|
||||
Returns `Promise<EntityBrief>`:
|
||||
|
||||
- `term` — requested search term.
|
||||
- `total` — exact API count.
|
||||
- `exact` — true when the sample covers the full population.
|
||||
- `sentiment` — sample-derived sentiment split.
|
||||
- `platformMix` — sample-derived platform counts.
|
||||
- `sample` — first page ordered by engagement.
|
||||
|
||||
`total` is exact. Sentiment and platform mix are exact only when `exact` is true.
|
||||
|
||||
### `client.getEntityStats(term, campaignId?, options?)`
|
||||
|
||||
Builds a richer dashboard for one term.
|
||||
|
||||
Returns `Promise<EntityStats>` with `EntityBrief` fields plus:
|
||||
|
||||
- `recent` — newest mentions.
|
||||
- `recent7d` — latest seven-day count from the sparkline.
|
||||
- `prev7d` — previous seven-day count.
|
||||
- `momentumPct` — seven-day percentage change, or `null` if not computable.
|
||||
- `sparkline` — daily counts for the last 14 days when date faceting is trustworthy.
|
||||
|
||||
Failure model:
|
||||
|
||||
- The initial brief must succeed.
|
||||
- Follow-up subrequests are best-effort.
|
||||
- Exact facets are used only when reconciliation checks pass; otherwise sample-derived values are retained.
|
||||
|
||||
### `client.getEntityBriefs(terms, campaignId?, concurrency?, options?)`
|
||||
|
||||
Fetches many entity briefs with bounded concurrency.
|
||||
|
||||
Returns `Promise<Map<string, EntityBrief>>`.
|
||||
|
||||
Failed terms are omitted from the map. Default concurrency is `20`; reduce it under rate limits.
|
||||
|
||||
## Cache API
|
||||
|
||||
### `Cache`
|
||||
|
||||
```ts
|
||||
interface Cache {
|
||||
get(key: string): Promise<unknown | undefined>;
|
||||
set(key: string, value: unknown, ttlMs: number): Promise<void>;
|
||||
delete(key: string): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
The key is the full GET URL. `get()` returns `undefined` on cache miss. `ttlMs` is in milliseconds.
|
||||
|
||||
### `MemoryCache`
|
||||
|
||||
In-memory `Map` cache with per-entry TTL. Good for tests, scripts, and single-process services. Not enough for distributed rate-limit protection.
|
||||
|
||||
### `NoopCache`
|
||||
|
||||
Cache implementation that never stores values. Use it to force fresh data or disable caching explicitly.
|
||||
|
||||
### `client.clearCache()`
|
||||
|
||||
Deletes cache keys used by the current client instance. It is not a global purge for shared cache backends.
|
||||
|
||||
## `SocialhoseError`
|
||||
|
||||
Structured request failure.
|
||||
|
||||
Fields:
|
||||
|
||||
- `name: 'SocialhoseError'`
|
||||
- `status?: number`
|
||||
- `path: string`
|
||||
- `body?: string`
|
||||
- `cause?: unknown`
|
||||
|
||||
Thrown after retry exhaustion or unsupported non-OK HTTP responses.
|
||||
|
||||
## Core data types
|
||||
|
||||
- `Sentiment` — `'positive' | 'negative' | 'neutral'`.
|
||||
- `SentimentSplit` — `{ positive: number; negative: number; neutral: number }`.
|
||||
- `Paginated<T>` — `{ count, next, previous, results }`.
|
||||
- `Campaign` — campaign metadata.
|
||||
- `Overview` — aggregate analytics.
|
||||
- `TimelinePoint` — one time-series bucket.
|
||||
- `ShareOfVoiceItem` — campaign comparison row.
|
||||
- `PlatformStat` — platform count and engagement.
|
||||
- `KeywordStat` — keyword count and sentiment.
|
||||
- `TrendingItem` — keyword momentum.
|
||||
- `TopMention` — compact high-impact mention.
|
||||
- `Mention` — full mention record with content, engagement, metadata, and author profile.
|
||||
- `MailingList` — mailing-list metadata.
|
||||
- `MailingListInvitation` — mailing-list invitation state.
|
||||
- `InviteResult` — normalized invite result.
|
||||
- `PlatformShare` — entity analytics platform count.
|
||||
- `EntityBrief` — compact term analytics.
|
||||
- `EntityStats` — full term dashboard.
|
||||
@@ -0,0 +1,104 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,85 @@
|
||||
# Entity Analytics
|
||||
|
||||
Entity analytics are SDK-composed analytics for a single term, person, organization, hashtag, product, or incident.
|
||||
|
||||
They exist because Socialhose analytics endpoints are campaign-scoped, while `/mentions/` supports `content_search` and returns an exact `count`.
|
||||
|
||||
## `getEntityBrief()`
|
||||
|
||||
`getEntityBrief(term, campaignId?)` performs one mention search ordered by engagement.
|
||||
|
||||
It returns:
|
||||
|
||||
- exact total mention count
|
||||
- top mention sample
|
||||
- sample-derived sentiment split
|
||||
- sample-derived platform mix
|
||||
- `exact` flag indicating whether the sample covers the full population
|
||||
|
||||
This is cheap enough for grids, tables, and autocomplete-like entity lists.
|
||||
|
||||
## `getEntityStats()`
|
||||
|
||||
`getEntityStats(term, campaignId?)` expands a brief into a detail dashboard.
|
||||
|
||||
It fetches:
|
||||
|
||||
- engagement-ordered sample
|
||||
- newest mentions
|
||||
- sentiment facets
|
||||
- platform facets
|
||||
- cumulative date boundaries for a 14-day sparkline
|
||||
|
||||
The first brief request is required. Subrequests are best-effort; failures return fallbacks instead of zeroing out the entity.
|
||||
|
||||
## Accuracy safeguards
|
||||
|
||||
### Sentiment reconciliation
|
||||
|
||||
The SDK facets `/mentions/` by sentiment and accepts exact sentiment only when:
|
||||
|
||||
```txt
|
||||
positive + negative + neutral === total
|
||||
```
|
||||
|
||||
If the sum does not match, the SDK assumes a filter was ignored or misapplied and falls back to sample-derived sentiment.
|
||||
|
||||
### Platform reconciliation
|
||||
|
||||
The SDK facets `/mentions/` over known platform keys. If the platform facet sum exceeds the entity total, the SDK assumes the API ignored a filter and falls back to sample-derived platform mix.
|
||||
|
||||
A sum below total is allowed because some mentions may be on platforms outside the SDK's known list.
|
||||
|
||||
### Timeline cumulative differencing
|
||||
|
||||
The SDK avoids adjacent `[date_from, date_to]` daily windows because inclusive `date_to` can double-count boundary days.
|
||||
|
||||
Instead it requests cumulative counts with `date_from` only:
|
||||
|
||||
```txt
|
||||
count(on/after day N) - count(on/after day N+1) = day N count
|
||||
```
|
||||
|
||||
If the earliest cumulative count exceeds the known entity total, the SDK treats date-filtered `content_search` as untrustworthy and returns an empty sparkline rather than wrong bars.
|
||||
|
||||
## Rate-limit guidance
|
||||
|
||||
A single `getEntityStats()` can perform approximately:
|
||||
|
||||
- 1 brief request
|
||||
- 1 recent request
|
||||
- 3 sentiment facet requests
|
||||
- 6 platform facet requests
|
||||
- 15 timeline boundary requests
|
||||
|
||||
Use caching and keep batch concurrency low.
|
||||
|
||||
Recommended pattern:
|
||||
|
||||
```ts
|
||||
const stats = await socialhose.getEntityStats('cholera', campaignId, {
|
||||
revalidateSeconds: 900,
|
||||
});
|
||||
```
|
||||
|
||||
Use `getEntityBriefs()` for lists and call `getEntityStats()` only for selected detail views.
|
||||
@@ -0,0 +1,173 @@
|
||||
# Usage Guide
|
||||
|
||||
## What this SDK is
|
||||
|
||||
`@socialhose/api` is a TypeScript client for the Socialhose Public API. It is built for backend services, scripts, serverless functions, and dashboards that need campaign analytics, mention search, mailing-list management, and term-level entity analytics.
|
||||
|
||||
The SDK handles:
|
||||
|
||||
1. API-key authentication.
|
||||
2. Typed endpoint helpers.
|
||||
3. Timeouts, retries, and request cancellation.
|
||||
4. GET caching with an injectable cache interface.
|
||||
5. Entity analytics assembled from `/mentions/` when native analytics endpoints are campaign-scoped.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install @socialhose/api
|
||||
```
|
||||
|
||||
Requires Node 18+ or a custom `fetch` implementation.
|
||||
|
||||
## Basic setup
|
||||
|
||||
```ts
|
||||
import { SocialhoseClient } from '@socialhose/api';
|
||||
|
||||
const socialhose = new SocialhoseClient({
|
||||
apiKey: process.env.SOCIALHOSE_API_KEY!,
|
||||
});
|
||||
```
|
||||
|
||||
Keep the API key server-side. Do not instantiate this client in browser code.
|
||||
|
||||
## Production setup
|
||||
|
||||
```ts
|
||||
const socialhose = new SocialhoseClient({
|
||||
apiKey: process.env.SOCIALHOSE_API_KEY!,
|
||||
timeoutMs: 10_000,
|
||||
retries: 3,
|
||||
cacheTtlMs: 60_000,
|
||||
});
|
||||
```
|
||||
|
||||
Recommendations:
|
||||
|
||||
- Keep the default browser-like user-agent unless you have verified an alternative works.
|
||||
- Use a shared cache such as Redis for multi-process or serverless deployments.
|
||||
- Reduce entity batch concurrency if you see `429` responses.
|
||||
- Use `revalidateSeconds` for dashboards so repeated views do not fan out fresh requests.
|
||||
|
||||
## List campaigns
|
||||
|
||||
```ts
|
||||
const campaigns = await socialhose.getCampaigns();
|
||||
for (const campaign of campaigns) {
|
||||
console.log(campaign.id, campaign.name, campaign.status);
|
||||
}
|
||||
```
|
||||
|
||||
## Search mentions
|
||||
|
||||
```ts
|
||||
const page = await socialhose.getMentions({
|
||||
campaign_ids: 'campaign-id',
|
||||
content_search: 'hospital',
|
||||
platforms: 'twitter,reddit',
|
||||
sentiments: 'negative',
|
||||
ordering: '-published_at',
|
||||
});
|
||||
|
||||
console.log(page.count);
|
||||
console.log(page.results[0]?.content);
|
||||
```
|
||||
|
||||
## Fetch campaign dashboard analytics
|
||||
|
||||
```ts
|
||||
const filters = { campaign_ids: 'campaign-id', date_from: '2026-05-01' };
|
||||
|
||||
const [overview, timeline, sentiment, platforms, topMentions] = await Promise.all([
|
||||
socialhose.getOverview(filters),
|
||||
socialhose.getTimeline({ ...filters, interval: 'day' }),
|
||||
socialhose.getSentiment(filters),
|
||||
socialhose.getPlatforms(filters),
|
||||
socialhose.getTopMentions({ ...filters, limit: 10 }),
|
||||
]);
|
||||
```
|
||||
|
||||
## Analyze a term or entity
|
||||
|
||||
```ts
|
||||
const stats = await socialhose.getEntityStats('RSF', 'campaign-id', {
|
||||
revalidateSeconds: 900,
|
||||
});
|
||||
|
||||
console.log({
|
||||
total: stats.total,
|
||||
sentiment: stats.sentiment,
|
||||
platformMix: stats.platformMix,
|
||||
momentumPct: stats.momentumPct,
|
||||
topUrls: stats.sample.slice(0, 3).map((m) => m.url),
|
||||
});
|
||||
```
|
||||
|
||||
Use `getEntityBrief()` for cheap cards/lists and `getEntityStats()` for detail pages.
|
||||
|
||||
## Batch entity briefs
|
||||
|
||||
```ts
|
||||
const briefs = await socialhose.getEntityBriefs(
|
||||
['Burhan', 'Hemedti', 'SAF', 'RSF'],
|
||||
'campaign-id',
|
||||
5,
|
||||
{ revalidateSeconds: 3600 },
|
||||
);
|
||||
```
|
||||
|
||||
Failed terms are skipped. Lower concurrency if rate limits are visible.
|
||||
|
||||
## Pagination
|
||||
|
||||
`getCampaigns()` and `getMailingLists()` return first-page arrays. `getMentions()` exposes a `page` filter.
|
||||
|
||||
For manual pagination, use `get()`:
|
||||
|
||||
```ts
|
||||
let page = 1;
|
||||
while (true) {
|
||||
const response = await socialhose.get('/mentions/', { page, content_search: 'cholera' });
|
||||
// process response.results
|
||||
if (!response.next) break;
|
||||
page += 1;
|
||||
}
|
||||
```
|
||||
|
||||
## Error handling
|
||||
|
||||
```ts
|
||||
import { SocialhoseError } from '@socialhose/api';
|
||||
|
||||
try {
|
||||
await socialhose.getOverview({ campaign_ids: 'bad-id' });
|
||||
} catch (error) {
|
||||
if (error instanceof SocialhoseError) {
|
||||
console.error({ status: error.status, path: error.path, body: error.body });
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Caching
|
||||
|
||||
GET requests are cached; POST requests are not.
|
||||
|
||||
```ts
|
||||
await socialhose.getMentions(
|
||||
{ content_search: 'sudan' },
|
||||
{ revalidateSeconds: 900 },
|
||||
);
|
||||
```
|
||||
|
||||
The default cache is process-local. Use a persistent/shared cache for production dashboards and rate-limit control.
|
||||
|
||||
## Operational pitfalls
|
||||
|
||||
- Entity analytics are request-heavy. One `getEntityStats()` call can issue roughly 20+ requests.
|
||||
- The API rate limit is roughly 60 requests/minute per API key.
|
||||
- The entity timeline uses cumulative differencing intentionally to avoid inclusive `date_to` double-counting.
|
||||
- Exact entity sentiment/platform values are used only when facet counts reconcile with the known total.
|
||||
- `409` mailing-list conflicts are normalized to `{ outcome: 'already' }`; other non-OK responses throw.
|
||||
@@ -0,0 +1,29 @@
|
||||
import { SocialhoseClient, SocialhoseError } from '@socialhose/api';
|
||||
|
||||
const socialhose = new SocialhoseClient({
|
||||
apiKey: process.env.SOCIALHOSE_API_KEY!,
|
||||
});
|
||||
|
||||
try {
|
||||
const campaigns = await socialhose.getCampaigns();
|
||||
const campaignId = campaigns[0]?.id;
|
||||
|
||||
const mentions = await socialhose.getMentions({
|
||||
campaign_ids: campaignId,
|
||||
content_search: 'hospital',
|
||||
ordering: '-published_at',
|
||||
});
|
||||
|
||||
console.log({
|
||||
campaigns: campaigns.length,
|
||||
mentionCount: mentions.count,
|
||||
firstMention: mentions.results[0]?.url,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof SocialhoseError) {
|
||||
console.error({ status: error.status, path: error.path, body: error.body });
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { SocialhoseClient } from '@socialhose/api';
|
||||
|
||||
const socialhose = new SocialhoseClient({
|
||||
apiKey: process.env.SOCIALHOSE_API_KEY!,
|
||||
cacheTtlMs: 15 * 60 * 1000,
|
||||
});
|
||||
|
||||
const campaignId = process.argv[2];
|
||||
const term = process.argv[3] ?? 'RSF';
|
||||
|
||||
const stats = await socialhose.getEntityStats(term, campaignId, {
|
||||
revalidateSeconds: 15 * 60,
|
||||
});
|
||||
|
||||
console.log(JSON.stringify({
|
||||
term: stats.term,
|
||||
total: stats.total,
|
||||
exact: stats.exact,
|
||||
sentiment: stats.sentiment,
|
||||
platformMix: stats.platformMix,
|
||||
recent7d: stats.recent7d,
|
||||
prev7d: stats.prev7d,
|
||||
momentumPct: stats.momentumPct,
|
||||
sparkline: stats.sparkline,
|
||||
topUrls: stats.sample.slice(0, 5).map((m) => m.url),
|
||||
}, null, 2));
|
||||
@@ -7,6 +7,8 @@
|
||||
"sideEffects": false,
|
||||
"files": [
|
||||
"dist",
|
||||
"docs",
|
||||
"examples",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
/**
|
||||
* Pluggable cache contract used by SocialhoseClient for GET responses.
|
||||
* The SDK uses the full request URL as the key.
|
||||
*/
|
||||
export interface Cache {
|
||||
/** Return a cached value, or `undefined` on miss/expiry. */
|
||||
get(key: string): Promise<unknown | undefined>;
|
||||
/** Store a value for the given TTL in milliseconds. */
|
||||
set(key: string, value: unknown, ttlMs: number): Promise<void>;
|
||||
/** Delete one cache entry. */
|
||||
delete(key: string): Promise<void>;
|
||||
}
|
||||
|
||||
type Entry = { at: number; value: unknown; ttlMs: number };
|
||||
|
||||
/** In-memory Map-backed cache with per-entry TTL. */
|
||||
/**
|
||||
* In-memory Map-backed cache with per-entry TTL.
|
||||
* Best for tests, scripts, and single-process services.
|
||||
*/
|
||||
export class MemoryCache implements Cache {
|
||||
private readonly map = new Map<string, Entry>();
|
||||
|
||||
@@ -29,7 +39,7 @@ export class MemoryCache implements Cache {
|
||||
}
|
||||
}
|
||||
|
||||
/** No-op cache — all operations are no-ops; useful for disabling caching. */
|
||||
/** Cache implementation that never stores values; useful for disabling caching. */
|
||||
export class NoopCache implements Cache {
|
||||
async get(_key: string): Promise<undefined> {
|
||||
return undefined;
|
||||
|
||||
@@ -2,11 +2,13 @@ export type { Cache } from './cache';
|
||||
export { MemoryCache, NoopCache } from './cache';
|
||||
import { type Cache, MemoryCache, NoopCache } from './cache';
|
||||
|
||||
/** Sentiment label assigned to a mention by Socialhose. */
|
||||
export type Sentiment = 'positive' | 'negative' | 'neutral';
|
||||
export type SentimentSplit = { positive: number; negative: number; neutral: number };
|
||||
export type QueryValue = string | number | boolean | null | undefined;
|
||||
export type QueryParams = Record<string, QueryValue>;
|
||||
|
||||
/** Configuration for {@link SocialhoseClient}. */
|
||||
export interface SocialhoseClientOptions {
|
||||
apiKey: string;
|
||||
baseUrl?: string;
|
||||
@@ -21,6 +23,7 @@ export interface SocialhoseClientOptions {
|
||||
defaultHeaders?: Record<string, string>;
|
||||
}
|
||||
|
||||
/** Per-call controls for cache TTL, cancellation, and extra headers. */
|
||||
export interface RequestOptions {
|
||||
/**
|
||||
* Reserved for cache-implementation use; not forwarded to fetch.
|
||||
@@ -226,6 +229,7 @@ export interface EntityStats extends EntityBrief {
|
||||
sparkline: { date: string; count: number }[];
|
||||
}
|
||||
|
||||
/** Structured error thrown for failed Socialhose requests. */
|
||||
export class SocialhoseError extends Error {
|
||||
readonly status?: number;
|
||||
readonly path: string;
|
||||
@@ -312,6 +316,7 @@ function platformMixOf(mentions: Mention[]): PlatformShare[] {
|
||||
.sort((a, b) => b.count - a.count);
|
||||
}
|
||||
|
||||
/** Authenticated server-side client for the Socialhose Public API. */
|
||||
export class SocialhoseClient {
|
||||
readonly apiKey: string;
|
||||
readonly baseUrl: string;
|
||||
@@ -326,6 +331,7 @@ export class SocialhoseClient {
|
||||
private readonly cacheImpl: Cache;
|
||||
private readonly usedCacheKeys = new Set<string>();
|
||||
|
||||
/** Create a client with authentication, retry, timeout, and cache settings. */
|
||||
constructor(options: SocialhoseClientOptions) {
|
||||
if (!options.fetch && typeof fetch === 'undefined') throw new Error('A fetch implementation is required');
|
||||
this.apiKey = options.apiKey;
|
||||
@@ -340,12 +346,14 @@ export class SocialhoseClient {
|
||||
this.cacheImpl = options.cache ?? (this.cacheTtlMs > 0 ? new MemoryCache() : new NoopCache());
|
||||
}
|
||||
|
||||
/** Delete cache entries used by this client instance. */
|
||||
async clearCache(): Promise<void> {
|
||||
const keys = [...this.usedCacheKeys];
|
||||
this.usedCacheKeys.clear();
|
||||
await Promise.all(keys.map((k) => this.cacheImpl.delete(k)));
|
||||
}
|
||||
|
||||
/** Perform an authenticated cached GET request. */
|
||||
async get<T>(path: string, params: QueryParams = {}, options: RequestOptions = {}): Promise<T> {
|
||||
const query = encodeParams(params);
|
||||
const url = `${joinUrl(this.baseUrl, path)}${query ? `?${query}` : ''}`;
|
||||
@@ -360,20 +368,24 @@ export class SocialhoseClient {
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Perform an authenticated JSON POST request. */
|
||||
async post<T>(path: string, body: unknown, options: RequestOptions = {}): Promise<{ status: number; data: T | null }> {
|
||||
const url = joinUrl(this.baseUrl, path);
|
||||
return this.requestWithStatus<T>('POST', path, url, body, options);
|
||||
}
|
||||
|
||||
/** Fetch the first page of campaigns. */
|
||||
async getCampaigns(options: RequestOptions = {}): Promise<Campaign[]> {
|
||||
const d = await this.get<Paginated<Campaign>>('/campaigns/', { page: 1 }, options);
|
||||
return d.results;
|
||||
}
|
||||
|
||||
/** Fetch a single campaign by ID. */
|
||||
getCampaign(id: string, options: RequestOptions = {}): Promise<Campaign> {
|
||||
return this.get<Campaign>(`/campaigns/${id}/`, {}, options);
|
||||
}
|
||||
|
||||
/** Fetch aggregate overview analytics. */
|
||||
async getOverview(filters: AnalyticsFilters = {}, options: RequestOptions = {}): Promise<Overview> {
|
||||
const d = await this.get<Overview>('/analytics/overview/', filters as QueryParams, options);
|
||||
return { ...d, total_mentions: num(d.total_mentions), total_authors: num(d.total_authors) };
|
||||
@@ -745,6 +757,7 @@ export class SocialhoseClient {
|
||||
}
|
||||
}
|
||||
|
||||
/** Create a {@link SocialhoseClient}; convenience wrapper around the constructor. */
|
||||
export function createSocialhoseClient(options: SocialhoseClientOptions): SocialhoseClient {
|
||||
return new SocialhoseClient(options);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user