chore: prepare JavaScript SDK for npm publish

This commit is contained in:
Mo Elzubeir
2026-05-29 13:46:11 -05:00
parent c860cf6d88
commit d820a39223
4 changed files with 91 additions and 3 deletions
+6 -1
View File
@@ -99,6 +99,11 @@ pnpm build
Publishing: Publishing:
Preferred publish path is the release workflow, which runs with npm provenance from CI. Before publishing, verify npm authentication and `@socialhose` scope access:
```bash ```bash
npm publish --access public --provenance npm whoami
npm publish --dry-run --access public --provenance
``` ```
For the actual release, publish from CI with provenance enabled. If you must publish locally, first confirm whether npm provenance is supported in that environment.
+44 -1
View File
@@ -4,7 +4,7 @@ Public API reference for `@socialhose/api`, the TypeScript SDK for the Socialhos
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. 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 ## Common imports
```ts ```ts
import { import {
@@ -18,9 +18,52 @@ import {
type RequestOptions, type RequestOptions,
type AnalyticsFilters, type AnalyticsFilters,
type MentionFilters, type MentionFilters,
type Mention,
type Paginated,
type EntityBrief,
type EntityStats,
} from '@socialhose/api'; } from '@socialhose/api';
``` ```
## Public exports
Runtime exports:
- `SocialhoseClient`
- `createSocialhoseClient`
- `SocialhoseError`
- `MemoryCache`
- `NoopCache`
Type exports:
- `AnalyticsFilters`
- `Cache`
- `Campaign`
- `EntityBrief`
- `EntityStats`
- `InviteOutcome`
- `InviteResult`
- `KeywordStat`
- `MailingList`
- `MailingListInvitation`
- `Mention`
- `MentionFilters`
- `Overview`
- `Paginated`
- `PlatformShare`
- `PlatformStat`
- `QueryParams`
- `QueryValue`
- `RequestOptions`
- `Sentiment`
- `SentimentSplit`
- `ShareOfVoiceItem`
- `SocialhoseClientOptions`
- `TimelinePoint`
- `TopMention`
- `TrendingItem`
## Authentication ## Authentication
Every request sends: Every request sends:
+9 -1
View File
@@ -3,8 +3,16 @@
"version": "0.1.0", "version": "0.1.0",
"description": "TypeScript SDK for the Socialhose Public API.", "description": "TypeScript SDK for the Socialhose Public API.",
"license": "MIT", "license": "MIT",
"author": "Socialhose",
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,
"packageManager": "pnpm@9.15.0",
"repository": {
"type": "git",
"url": "git+ssh://git@git.elzubeir.com/socialhose/sdk.git",
"directory": "sdks/javascript"
},
"homepage": "https://socialhose.net",
"files": [ "files": [
"dist", "dist",
"docs", "docs",
@@ -29,7 +37,7 @@
"build": "tsup src/index.ts --format esm,cjs --dts --sourcemap --clean", "build": "tsup src/index.ts --format esm,cjs --dts --sourcemap --clean",
"test": "vitest run", "test": "vitest run",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"prepublishOnly": "pnpm test && pnpm typecheck && pnpm build" "prepublishOnly": "npm run test && npm run typecheck && npm run build"
}, },
"keywords": [ "keywords": [
"socialhose", "socialhose",
+32
View File
@@ -4,8 +4,11 @@ import { type Cache, MemoryCache, NoopCache } from './cache';
/** Sentiment label assigned to a mention by Socialhose. */ /** Sentiment label assigned to a mention by Socialhose. */
export type Sentiment = 'positive' | 'negative' | 'neutral'; export type Sentiment = 'positive' | 'negative' | 'neutral';
/** Count distribution across positive, negative, and neutral sentiment labels. */
export type SentimentSplit = { positive: number; negative: number; neutral: number }; export type SentimentSplit = { positive: number; negative: number; neutral: number };
/** Primitive value accepted as a query-string parameter. Nullish and empty-string values are omitted. */
export type QueryValue = string | number | boolean | null | undefined; export type QueryValue = string | number | boolean | null | undefined;
/** Query-string parameter map used by low-level GET calls. */
export type QueryParams = Record<string, QueryValue>; export type QueryParams = Record<string, QueryValue>;
/** Configuration for {@link SocialhoseClient}. */ /** Configuration for {@link SocialhoseClient}. */
@@ -34,6 +37,7 @@ export interface RequestOptions {
headers?: Record<string, string>; headers?: Record<string, string>;
} }
/** Campaign metadata returned by `/campaigns/`. */
export interface Campaign { export interface Campaign {
id: string; id: string;
name: string; name: string;
@@ -44,6 +48,7 @@ export interface Campaign {
tags: string[]; tags: string[];
} }
/** Aggregate metrics returned by `/analytics/overview/`. */
export interface Overview { export interface Overview {
total_mentions: number; total_mentions: number;
total_authors: number; total_authors: number;
@@ -66,6 +71,7 @@ export interface Overview {
}; };
} }
/** One time bucket returned by `/analytics/timeline/`. */
export interface TimelinePoint { export interface TimelinePoint {
date: string; date: string;
count: number; count: number;
@@ -73,6 +79,7 @@ export interface TimelinePoint {
engagement: number; engagement: number;
} }
/** Campaign comparison row returned by `/analytics/share-of-voice/`. */
export interface ShareOfVoiceItem { export interface ShareOfVoiceItem {
campaign_id: string; campaign_id: string;
name: string; name: string;
@@ -82,18 +89,21 @@ export interface ShareOfVoiceItem {
sentiment: SentimentSplit; sentiment: SentimentSplit;
} }
/** Mention and engagement totals for one platform. */
export interface PlatformStat { export interface PlatformStat {
platform: string; platform: string;
count: number; count: number;
engagement: number; engagement: number;
} }
/** Top-keyword row with mention count and sentiment split. */
export interface KeywordStat { export interface KeywordStat {
keyword: string; keyword: string;
count: number; count: number;
sentiment: SentimentSplit; sentiment: SentimentSplit;
} }
/** Trending-keyword row comparing current and previous mention volume. */
export interface TrendingItem { export interface TrendingItem {
keyword: string; keyword: string;
count: number; count: number;
@@ -101,6 +111,7 @@ export interface TrendingItem {
change_pct: number; change_pct: number;
} }
/** Compact high-impact mention returned by the analytics top-mentions endpoint. */
export interface TopMention { export interface TopMention {
id: string; id: string;
platform: string; platform: string;
@@ -115,6 +126,7 @@ export interface TopMention {
content_preview: string; content_preview: string;
} }
/** Full mention record returned by `/mentions/`, including content, engagement, metadata, and author profile. */
export interface Mention { export interface Mention {
id: string; id: string;
platform: string; platform: string;
@@ -147,6 +159,7 @@ export interface Mention {
}; };
} }
/** Mailing-list metadata returned by `/mailing-lists/`. */
export interface MailingList { export interface MailingList {
id: string; id: string;
campaign_id: string; campaign_id: string;
@@ -157,6 +170,7 @@ export interface MailingList {
email_template: string; email_template: string;
} }
/** Invitation state returned when adding a mailing-list member. */
export interface MailingListInvitation { export interface MailingListInvitation {
id: string; id: string;
email: string; email: string;
@@ -169,14 +183,17 @@ export interface MailingListInvitation {
accepted_at: string | null; accepted_at: string | null;
} }
/** Normalized mailing-list invite outcome. */
export type InviteOutcome = 'invited' | 'already' | 'error'; export type InviteOutcome = 'invited' | 'already' | 'error';
/** Normalized response from {@link SocialhoseClient.inviteMailingListMember}. */
export interface InviteResult { export interface InviteResult {
outcome: InviteOutcome; outcome: InviteOutcome;
invitation?: MailingListInvitation; invitation?: MailingListInvitation;
detail?: string; detail?: string;
} }
/** Standard paginated response envelope used by list endpoints. */
export interface Paginated<T> { export interface Paginated<T> {
count: number; count: number;
next: string | null; next: string | null;
@@ -184,6 +201,7 @@ export interface Paginated<T> {
results: T[]; results: T[];
} }
/** Common filters accepted by analytics endpoints. Values are passed through as query parameters. */
export interface AnalyticsFilters { export interface AnalyticsFilters {
campaign_ids?: string; campaign_ids?: string;
date_from?: string; date_from?: string;
@@ -192,6 +210,7 @@ export interface AnalyticsFilters {
sentiments?: string; sentiments?: string;
} }
/** Filters accepted by `/mentions/`, extending common analytics filters with pagination, search, and ordering. */
export interface MentionFilters extends AnalyticsFilters { export interface MentionFilters extends AnalyticsFilters {
page?: number; page?: number;
content_search?: string; content_search?: string;
@@ -207,11 +226,13 @@ export interface MentionFilters extends AnalyticsFilters {
// when count <= 20, which covers most entities at current volume); a few // when count <= 20, which covers most entities at current volume); a few
// count-only requests add precise week-over-week momentum. // count-only requests add precise week-over-week momentum.
/** Platform count pair used by entity analytics. */
export interface PlatformShare { export interface PlatformShare {
platform: string; platform: string;
count: number; count: number;
} }
/** Compact analytics for one term from a single mention search. */
export interface EntityBrief { export interface EntityBrief {
term: string; term: string;
total: number; total: number;
@@ -221,6 +242,7 @@ export interface EntityBrief {
sample: Mention[]; // up to 20, ordered by engagement sample: Mention[]; // up to 20, ordered by engagement
} }
/** Rich entity dashboard assembled from multiple mention-search facets. */
export interface EntityStats extends EntityBrief { export interface EntityStats extends EntityBrief {
recent: Mention[]; // up to 20, newest first recent: Mention[]; // up to 20, newest first
recent7d: number; recent7d: number;
@@ -391,6 +413,7 @@ export class SocialhoseClient {
return { ...d, total_mentions: num(d.total_mentions), total_authors: num(d.total_authors) }; return { ...d, total_mentions: num(d.total_mentions), total_authors: num(d.total_authors) };
} }
/** Fetch analytics time-series buckets; defaults to daily interval. */
async getTimeline( async getTimeline(
filters: AnalyticsFilters & { interval?: 'day' | 'week' | 'month' } = {}, filters: AnalyticsFilters & { interval?: 'day' | 'week' | 'month' } = {},
options: RequestOptions = {}, options: RequestOptions = {},
@@ -403,6 +426,7 @@ export class SocialhoseClient {
return d.series ?? []; return d.series ?? [];
} }
/** Fetch overall and platform-specific sentiment distributions. */
getSentiment( getSentiment(
filters: AnalyticsFilters = {}, filters: AnalyticsFilters = {},
options: RequestOptions = {}, options: RequestOptions = {},
@@ -410,6 +434,7 @@ export class SocialhoseClient {
return this.get('/analytics/sentiment/', filters as QueryParams, options); return this.get('/analytics/sentiment/', filters as QueryParams, options);
} }
/** Fetch campaign share-of-voice comparison metrics. */
getShareOfVoice( getShareOfVoice(
filters: AnalyticsFilters = {}, filters: AnalyticsFilters = {},
options: RequestOptions = {}, options: RequestOptions = {},
@@ -417,11 +442,13 @@ export class SocialhoseClient {
return this.get('/analytics/share-of-voice/', filters as QueryParams, options); return this.get('/analytics/share-of-voice/', filters as QueryParams, options);
} }
/** Fetch mention and engagement totals grouped by platform. */
async getPlatforms(filters: AnalyticsFilters = {}, options: RequestOptions = {}): Promise<PlatformStat[]> { async getPlatforms(filters: AnalyticsFilters = {}, options: RequestOptions = {}): Promise<PlatformStat[]> {
const d = await this.get<{ platforms?: PlatformStat[] }>('/analytics/platforms/', filters as QueryParams, options); const d = await this.get<{ platforms?: PlatformStat[] }>('/analytics/platforms/', filters as QueryParams, options);
return d.platforms ?? []; return d.platforms ?? [];
} }
/** Fetch top keywords; defaults to 12 items. */
async getTopKeywords( async getTopKeywords(
filters: AnalyticsFilters & { limit?: number } = {}, filters: AnalyticsFilters & { limit?: number } = {},
options: RequestOptions = {}, options: RequestOptions = {},
@@ -434,6 +461,7 @@ export class SocialhoseClient {
return d.keywords ?? []; return d.keywords ?? [];
} }
/** Fetch trending keywords; defaults to 8 items. */
async getTrending( async getTrending(
filters: AnalyticsFilters & { limit?: number } = {}, filters: AnalyticsFilters & { limit?: number } = {},
options: RequestOptions = {}, options: RequestOptions = {},
@@ -446,6 +474,7 @@ export class SocialhoseClient {
return d.trending ?? []; return d.trending ?? [];
} }
/** Fetch top mentions by impact; defaults to 6 items. */
async getTopMentions( async getTopMentions(
filters: AnalyticsFilters & { limit?: number } = {}, filters: AnalyticsFilters & { limit?: number } = {},
options: RequestOptions = {}, options: RequestOptions = {},
@@ -458,6 +487,7 @@ export class SocialhoseClient {
return d.mentions ?? []; return d.mentions ?? [];
} }
/** Fetch a paginated mention page with campaign/date/platform/sentiment/search/order filters. */
getMentions( getMentions(
filters: MentionFilters = {}, filters: MentionFilters = {},
optionsOrRevalidate: RequestOptions | number = {}, optionsOrRevalidate: RequestOptions | number = {},
@@ -466,11 +496,13 @@ export class SocialhoseClient {
return this.get<Paginated<Mention>>('/mentions/', { page: 1, ...filters }, options); return this.get<Paginated<Mention>>('/mentions/', { page: 1, ...filters }, options);
} }
/** Fetch the first page of mailing lists. */
async getMailingLists(options: RequestOptions = {}): Promise<MailingList[]> { async getMailingLists(options: RequestOptions = {}): Promise<MailingList[]> {
const d = await this.get<Paginated<MailingList>>('/mailing-lists/', { page: 1 }, options); const d = await this.get<Paginated<MailingList>>('/mailing-lists/', { page: 1 }, options);
return d.results; return d.results;
} }
/** Invite a recipient to a mailing list and normalize invited/already/error outcomes. */
async inviteMailingListMember( async inviteMailingListMember(
listId: string, listId: string,
invite: { email: string; first_name?: string; last_name?: string; invitation_message?: string }, invite: { email: string; first_name?: string; last_name?: string; invitation_message?: string },