@shobdo/js 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +131 -0
- package/dist/client.d.ts +10 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# @shobdo/js
|
|
2
|
+
|
|
3
|
+
The official JavaScript and TypeScript client for the [Shobdo API](https://shobdo.me).
|
|
4
|
+
|
|
5
|
+
Search across 30+ multilingual dictionaries, retrieve rich definitions, and stream pronunciation audio.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @shobdo/js
|
|
11
|
+
# or
|
|
12
|
+
yarn add @shobdo/js
|
|
13
|
+
# or
|
|
14
|
+
pnpm add @shobdo/js
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Initialization
|
|
18
|
+
|
|
19
|
+
Import the `ShobdoClient` and initialize it with your API key. You can generate an API key from the [Shobdo Console](https://shobdo.me/console).
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { ShobdoClient } from '@shobdo/js';
|
|
23
|
+
|
|
24
|
+
const client = new ShobdoClient({
|
|
25
|
+
apiKey: process.env.SHOBDO_API_KEY,
|
|
26
|
+
|
|
27
|
+
// Optional: Configure retry behavior for rate limits and server errors
|
|
28
|
+
maxRetries: 3, // default: 2
|
|
29
|
+
|
|
30
|
+
// Optional: Set a request timeout in milliseconds
|
|
31
|
+
timeout: 5000, // default: 10000
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
*Note: In Node.js 18+ and modern browsers, the native `fetch` API is used automatically. If you are using an older environment without a global `fetch`, you must provide a polyfill via the `fetch` configuration option.*
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
### 1. Search Dictionaries
|
|
40
|
+
|
|
41
|
+
Search across all dictionaries, or filter by specific languages and sources.
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
const response = await client.search("hello", {
|
|
45
|
+
lang: "en", // Filter by ISO 639-1 language code
|
|
46
|
+
limit: 10, // Pagination limit (max 100)
|
|
47
|
+
offset: 0, // Pagination offset
|
|
48
|
+
matchType: "prefix", // "exact" | "prefix" | "contains"
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
console.log(`Found ${response.meta.total} results`);
|
|
52
|
+
|
|
53
|
+
response.data.forEach(result => {
|
|
54
|
+
console.log(`${result.word} - ${result.preview}`);
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 2. Retrieve Entry by Word
|
|
59
|
+
|
|
60
|
+
Look up a full dictionary entry directly by the word string. The SDK automatically handles URL-encoding for non-Latin scripts (e.g., Arabic, Bengali, Japanese).
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// Look up a Bengali word in the English-Bengali dictionary
|
|
64
|
+
const bengalEntry = await client.getEntryByWord("shobdo_bi_en_us_bn_bd_665k", "ধন্যবাদ");
|
|
65
|
+
|
|
66
|
+
console.log(bengalEntry.data.htmlContent);
|
|
67
|
+
console.log(bengalEntry.data.phonetic);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 3. Retrieve Entry by ID
|
|
71
|
+
|
|
72
|
+
If you have an entry ID from a previous search result, you can fetch the full entry details:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
const entry = await client.getEntryById("dictionary_id_here", "entry_id_here");
|
|
76
|
+
|
|
77
|
+
if (entry.data.hasAudio) {
|
|
78
|
+
console.log("Audio is available for this entry.");
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 4. Audio URLs
|
|
83
|
+
|
|
84
|
+
Pronunciation audio is served via a CDN redirect. The SDK provides a helper to construct the correct audio URL for use in `<audio>` tags or custom players.
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
const audioUrl = client.getAudioUrl("dictionary_id_here", "entry_id_here");
|
|
88
|
+
|
|
89
|
+
// In the browser:
|
|
90
|
+
const audio = new Audio(audioUrl);
|
|
91
|
+
audio.play();
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Error Handling
|
|
95
|
+
|
|
96
|
+
The SDK exposes custom error classes to help you gracefully handle API failures.
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { RateLimitError, AuthenticationError, NotFoundError } from '@shobdo/js';
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
await client.search("hello");
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if (error instanceof RateLimitError) {
|
|
105
|
+
console.error("Monthly request quota exceeded. Please upgrade your plan.");
|
|
106
|
+
} else if (error instanceof AuthenticationError) {
|
|
107
|
+
console.error("Invalid or missing API key.");
|
|
108
|
+
} else if (error instanceof NotFoundError) {
|
|
109
|
+
console.error("The requested dictionary or entry does not exist.");
|
|
110
|
+
} else {
|
|
111
|
+
console.error("An unexpected error occurred:", error.message);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## TypeScript
|
|
117
|
+
|
|
118
|
+
The SDK is written in TypeScript and exports all API response interfaces.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import type {
|
|
122
|
+
SearchResult,
|
|
123
|
+
FullEntry,
|
|
124
|
+
SearchOptions,
|
|
125
|
+
ApiResponse
|
|
126
|
+
} from '@shobdo/js';
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
Apache-2.0
|
package/dist/client.d.ts
CHANGED
|
@@ -6,12 +6,22 @@ export interface ClientConfig {
|
|
|
6
6
|
baseUrl?: string;
|
|
7
7
|
/** Optional custom fetch implementation */
|
|
8
8
|
fetch?: typeof fetch;
|
|
9
|
+
/** Maximum number of retries for 429 and 5xx errors (default: 2) */
|
|
10
|
+
maxRetries?: number;
|
|
11
|
+
/** Request timeout in milliseconds (default: 10000) */
|
|
12
|
+
timeout?: number;
|
|
9
13
|
}
|
|
10
14
|
export declare class ShobdoClient {
|
|
11
15
|
private apiKey;
|
|
12
16
|
private baseUrl;
|
|
13
17
|
private fetchImpl;
|
|
18
|
+
private maxRetries;
|
|
19
|
+
private timeout;
|
|
14
20
|
constructor(config: ClientConfig);
|
|
21
|
+
/**
|
|
22
|
+
* Helper to sleep for a given number of milliseconds
|
|
23
|
+
*/
|
|
24
|
+
private sleep;
|
|
15
25
|
/**
|
|
16
26
|
* Search across all available dictionaries
|
|
17
27
|
* @param query The search term
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var d=Object.defineProperty;var w=Object.getOwnPropertyDescriptor;var b=Object.getOwnPropertyNames;var x=Object.prototype.hasOwnProperty;var A=(s,e)=>{for(var t in e)d(s,t,{get:e[t],enumerable:!0})},I=(s,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of b(e))!x.call(s,i)&&i!==t&&d(s,i,{get:()=>e[i],enumerable:!(r=w(e,i))||r.enumerable});return s};var C=s=>I(d({},"__esModule",{value:!0}),s);var E={};A(E,{AuthenticationError:()=>c,NotFoundError:()=>l,RateLimitError:()=>u,ShobdoClient:()=>f,ShobdoError:()=>n});module.exports=C(E);var n=class extends Error{status;details;constructor(e,t,r){super(e),this.name="ShobdoError",this.status=t,this.details=r}},c=class extends n{constructor(e="Authentication failed. Check your API key."){super(e,401),this.name="AuthenticationError"}},u=class extends n{constructor(e="Rate limit exceeded. Please upgrade your plan."){super(e,429),this.name="RateLimitError"}},l=class extends n{constructor(e="Resource not found."){super(e,404),this.name="NotFoundError"}};var f=class{apiKey;baseUrl;fetchImpl;maxRetries;timeout;constructor(e){if(!e.apiKey)throw new Error("Shobdo API key is required");if(this.apiKey=e.apiKey,this.baseUrl=e.baseUrl||"https://shobdo.me/api/v1",this.maxRetries=e.maxRetries??2,this.timeout=e.timeout??1e4,e.fetch)this.fetchImpl=e.fetch;else if(typeof fetch<"u")this.fetchImpl=fetch;else throw new Error("No global fetch API found. Please provide a custom fetch implementation in ClientConfig.")}sleep(e){return new Promise(t=>setTimeout(t,e))}async search(e,t){let r=new URLSearchParams({q:e});return t&&(t.limit!==void 0&&r.append("limit",t.limit.toString()),t.offset!==void 0&&r.append("offset",t.offset.toString()),t.lang&&r.append("lang",t.lang),t.source&&r.append("source",t.source),t.matchType&&r.append("matchType",t.matchType)),this.request(`/search?${r.toString()}`)}async getEntryById(e,t){return this.request(`/entry/${encodeURIComponent(e)}/${encodeURIComponent(t)}`)}async getEntryByWord(e,t){return this.request(`/entry/${encodeURIComponent(e)}/word/${encodeURIComponent(t)}`)}getAudioUrl(e,t){return`${this.baseUrl}/audio/${encodeURIComponent(e)}/${encodeURIComponent(t)}`}async request(e,t){let r=`${this.baseUrl}${e}`,i;for(let p=0;p<=this.maxRetries;p++)try{let a=new AbortController,g=setTimeout(()=>a.abort(),this.timeout),o=await this.fetchImpl(r,{...t,signal:a.signal,headers:{...t?.headers,Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json","User-Agent":"shobdo-js/1.1.0"}});clearTimeout(g);let h=await o.json().catch(()=>({ok:!1,data:null,error:"Invalid JSON response from server"}));if(!o.ok||!h.ok){let m=h.error||`HTTP Error ${o.status}`;if((o.status===429||o.status>=500)&&p<this.maxRetries){let y=o.headers.get("Retry-After"),R=y?parseInt(y)*1e3:Math.pow(2,p)*1e3;await this.sleep(R);continue}switch(o.status){case 401:case 403:throw new c(m);case 404:throw new l(m);case 429:throw new u(m);default:throw new n(m,o.status,h)}}return h}catch(a){if(i=a,a.name==="AbortError"||p>=this.maxRetries)throw new n(`Request failed: ${a.message}`,0,a);await this.sleep(Math.pow(2,p)*1e3)}throw i}};0&&(module.exports={AuthenticationError,NotFoundError,RateLimitError,ShobdoClient,ShobdoError});
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/client.ts"],"sourcesContent":["export { ShobdoClient } from \"./client\";\nexport type { ClientConfig } from \"./client\";\nexport * from \"./types\";\nexport * from \"./errors\";\n","export class ShobdoError extends Error {\n public status: number;\n public details?: any;\n\n constructor(message: string, status: number, details?: any) {\n super(message);\n this.name = \"ShobdoError\";\n this.status = status;\n this.details = details;\n }\n}\n\nexport class AuthenticationError extends ShobdoError {\n constructor(message: string = \"Authentication failed. Check your API key.\") {\n super(message, 401);\n this.name = \"AuthenticationError\";\n }\n}\n\nexport class RateLimitError extends ShobdoError {\n constructor(message: string = \"Rate limit exceeded. Please upgrade your plan.\") {\n super(message, 429);\n this.name = \"RateLimitError\";\n }\n}\n\nexport class NotFoundError extends ShobdoError {\n constructor(message: string = \"Resource not found.\") {\n super(message, 404);\n this.name = \"NotFoundError\";\n }\n}\n","import { ApiResponse, FullEntry, SearchOptions, SearchResult } from \"./types\";\nimport { AuthenticationError, NotFoundError, RateLimitError, ShobdoError } from \"./errors\";\n\nexport interface ClientConfig {\n /** The Shobdo API key */\n apiKey: string;\n /** Optional base URL (defaults to https://shobdo.me/api/v1) */\n baseUrl?: string;\n /** Optional custom fetch implementation */\n fetch?: typeof fetch;\n}\n\nexport class ShobdoClient {\n private apiKey: string;\n private baseUrl: string;\n private fetchImpl: typeof fetch;\n\n constructor(config: ClientConfig) {\n if (!config.apiKey) {\n throw new Error(\"Shobdo API key is required\");\n }\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl || \"https://shobdo.me/api/v1\";\n \n // Use native fetch by default, or fallback to the provided fetch\n if (config.fetch) {\n this.fetchImpl = config.fetch;\n } else if (typeof fetch !== \"undefined\") {\n this.fetchImpl = fetch;\n } else {\n throw new Error(\"No global fetch API found. Please provide a custom fetch implementation in ClientConfig.\");\n }\n }\n\n /**\n * Search across all available dictionaries\n * @param query The search term\n * @param options Search filters and pagination\n */\n async search(query: string, options?: SearchOptions): Promise<ApiResponse<SearchResult[]>> {\n const params = new URLSearchParams({ q: query });\n \n if (options) {\n if (options.limit !== undefined) params.append(\"limit\", options.limit.toString());\n if (options.offset !== undefined) params.append(\"offset\", options.offset.toString());\n if (options.lang) params.append(\"lang\", options.lang);\n if (options.source) params.append(\"source\", options.source);\n if (options.matchType) params.append(\"matchType\", options.matchType);\n }\n\n return this.request<SearchResult[]>(`/search?${params.toString()}`);\n }\n\n /**\n * Retrieve a full dictionary entry by its unique ID\n * @param source The dictionary ID\n * @param id The entry ID\n */\n async getEntryById(source: string, id: string): Promise<ApiResponse<FullEntry>> {\n return this.request<FullEntry>(`/entry/${encodeURIComponent(source)}/${encodeURIComponent(id)}`);\n }\n\n /**\n * Look up a dictionary entry by word string\n * @param source The dictionary ID\n * @param word The word to look up\n */\n async getEntryByWord(source: string, word: string): Promise<ApiResponse<FullEntry>> {\n return this.request<FullEntry>(`/entry/${encodeURIComponent(source)}/word/${encodeURIComponent(word)}`);\n }\n\n /**\n * Get the direct URL to the pronunciation audio for an entry\n * @param source The dictionary ID\n * @param lexid The entry ID\n * @returns The full URL to the audio endpoint (which redirects to the CDN)\n */\n getAudioUrl(source: string, lexid: string): string {\n return `${this.baseUrl}/audio/${encodeURIComponent(source)}/${encodeURIComponent(lexid)}`;\n }\n\n private async request<T>(path: string, init?: RequestInit): Promise<ApiResponse<T>> {\n const url = `${this.baseUrl}${path}`;\n \n const response = await this.fetchImpl(url, {\n ...init,\n headers: {\n ...init?.headers,\n \"Authorization\": `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n });\n\n const data: ApiResponse<T> = await response.json().catch(() => ({\n ok: false,\n data: null as any,\n error: \"Invalid JSON response from server\"\n }));\n\n if (!response.ok || !data.ok) {\n const errorMessage = data.error || `HTTP Error ${response.status}`;\n \n switch (response.status) {\n case 401:\n case 403:\n throw new AuthenticationError(errorMessage);\n case 404:\n throw new NotFoundError(errorMessage);\n case 429:\n throw new RateLimitError(errorMessage);\n default:\n throw new ShobdoError(errorMessage, response.status, data);\n }\n }\n\n return data;\n }\n}\n"],"mappings":"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","names":["index_exports","__export","AuthenticationError","NotFoundError","RateLimitError","ShobdoClient","ShobdoError","__toCommonJS","ShobdoError","message","status","details","AuthenticationError","RateLimitError","NotFoundError","ShobdoClient","config","query","options","params","source","id","word","lexid","path","init","url","response","data","errorMessage","AuthenticationError","NotFoundError","RateLimitError","ShobdoError"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/client.ts"],"sourcesContent":["export { ShobdoClient } from \"./client\";\nexport type { ClientConfig } from \"./client\";\nexport * from \"./types\";\nexport * from \"./errors\";\n","export class ShobdoError extends Error {\n public status: number;\n public details?: any;\n\n constructor(message: string, status: number, details?: any) {\n super(message);\n this.name = \"ShobdoError\";\n this.status = status;\n this.details = details;\n }\n}\n\nexport class AuthenticationError extends ShobdoError {\n constructor(message: string = \"Authentication failed. Check your API key.\") {\n super(message, 401);\n this.name = \"AuthenticationError\";\n }\n}\n\nexport class RateLimitError extends ShobdoError {\n constructor(message: string = \"Rate limit exceeded. Please upgrade your plan.\") {\n super(message, 429);\n this.name = \"RateLimitError\";\n }\n}\n\nexport class NotFoundError extends ShobdoError {\n constructor(message: string = \"Resource not found.\") {\n super(message, 404);\n this.name = \"NotFoundError\";\n }\n}\n","import { ApiResponse, FullEntry, SearchOptions, SearchResult } from \"./types\";\nimport { AuthenticationError, NotFoundError, RateLimitError, ShobdoError } from \"./errors\";\n\nexport interface ClientConfig {\n /** The Shobdo API key */\n apiKey: string;\n /** Optional base URL (defaults to https://shobdo.me/api/v1) */\n baseUrl?: string;\n /** Optional custom fetch implementation */\n fetch?: typeof fetch;\n /** Maximum number of retries for 429 and 5xx errors (default: 2) */\n maxRetries?: number;\n /** Request timeout in milliseconds (default: 10000) */\n timeout?: number;\n}\n\nexport class ShobdoClient {\n private apiKey: string;\n private baseUrl: string;\n private fetchImpl: typeof fetch;\n private maxRetries: number;\n private timeout: number;\n\n constructor(config: ClientConfig) {\n if (!config.apiKey) {\n throw new Error(\"Shobdo API key is required\");\n }\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl || \"https://shobdo.me/api/v1\";\n this.maxRetries = config.maxRetries ?? 2;\n this.timeout = config.timeout ?? 10000;\n \n // Use native fetch by default, or fallback to the provided fetch\n if (config.fetch) {\n this.fetchImpl = config.fetch;\n } else if (typeof fetch !== \"undefined\") {\n this.fetchImpl = fetch;\n } else {\n throw new Error(\"No global fetch API found. Please provide a custom fetch implementation in ClientConfig.\");\n }\n }\n\n /**\n * Helper to sleep for a given number of milliseconds\n */\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Search across all available dictionaries\n * @param query The search term\n * @param options Search filters and pagination\n */\n async search(query: string, options?: SearchOptions): Promise<ApiResponse<SearchResult[]>> {\n const params = new URLSearchParams({ q: query });\n \n if (options) {\n if (options.limit !== undefined) params.append(\"limit\", options.limit.toString());\n if (options.offset !== undefined) params.append(\"offset\", options.offset.toString());\n if (options.lang) params.append(\"lang\", options.lang);\n if (options.source) params.append(\"source\", options.source);\n if (options.matchType) params.append(\"matchType\", options.matchType);\n }\n\n return this.request<SearchResult[]>(`/search?${params.toString()}`);\n }\n\n /**\n * Retrieve a full dictionary entry by its unique ID\n * @param source The dictionary ID\n * @param id The entry ID\n */\n async getEntryById(source: string, id: string): Promise<ApiResponse<FullEntry>> {\n return this.request<FullEntry>(`/entry/${encodeURIComponent(source)}/${encodeURIComponent(id)}`);\n }\n\n /**\n * Look up a dictionary entry by word string\n * @param source The dictionary ID\n * @param word The word to look up\n */\n async getEntryByWord(source: string, word: string): Promise<ApiResponse<FullEntry>> {\n return this.request<FullEntry>(`/entry/${encodeURIComponent(source)}/word/${encodeURIComponent(word)}`);\n }\n\n /**\n * Get the direct URL to the pronunciation audio for an entry\n * @param source The dictionary ID\n * @param lexid The entry ID\n * @returns The full URL to the audio endpoint (which redirects to the CDN)\n */\n getAudioUrl(source: string, lexid: string): string {\n return `${this.baseUrl}/audio/${encodeURIComponent(source)}/${encodeURIComponent(lexid)}`;\n }\n\n private async request<T>(path: string, init?: RequestInit): Promise<ApiResponse<T>> {\n const url = `${this.baseUrl}${path}`;\n let lastError: any;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n const response = await this.fetchImpl(url, {\n ...init,\n signal: controller.signal as any,\n headers: {\n ...init?.headers,\n \"Authorization\": `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n \"User-Agent\": \"shobdo-js/1.1.0\",\n },\n });\n\n clearTimeout(timeoutId);\n\n const data: ApiResponse<T> = await response.json().catch(() => ({\n ok: false,\n data: null as any,\n error: \"Invalid JSON response from server\"\n }));\n\n if (!response.ok || !data.ok) {\n const errorMessage = data.error || `HTTP Error ${response.status}`;\n \n if (response.status === 429 || response.status >= 500) {\n // Retry on rate limits or server errors\n if (attempt < this.maxRetries) {\n const retryAfter = response.headers.get(\"Retry-After\");\n const delay = retryAfter ? parseInt(retryAfter) * 1000 : Math.pow(2, attempt) * 1000;\n await this.sleep(delay);\n continue;\n }\n }\n\n switch (response.status) {\n case 401:\n case 403:\n throw new AuthenticationError(errorMessage);\n case 404:\n throw new NotFoundError(errorMessage);\n case 429:\n throw new RateLimitError(errorMessage);\n default:\n throw new ShobdoError(errorMessage, response.status, data);\n }\n }\n\n return data;\n } catch (error: any) {\n lastError = error;\n // Don't retry if it's an AbortError (timeout) or if we've run out of retries\n if (error.name === \"AbortError\" || attempt >= this.maxRetries) {\n throw new ShobdoError(`Request failed: ${error.message}`, 0, error);\n }\n await this.sleep(Math.pow(2, attempt) * 1000);\n }\n }\n\n throw lastError;\n }\n}\n"],"mappings":"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","names":["index_exports","__export","AuthenticationError","NotFoundError","RateLimitError","ShobdoClient","ShobdoError","__toCommonJS","ShobdoError","message","status","details","AuthenticationError","RateLimitError","NotFoundError","ShobdoClient","config","ms","resolve","query","options","params","source","id","word","lexid","path","init","url","lastError","attempt","controller","timeoutId","response","data","errorMessage","retryAfter","delay","AuthenticationError","NotFoundError","RateLimitError","ShobdoError","error"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var
|
|
1
|
+
var n=class extends Error{status;details;constructor(e,t,r){super(e),this.name="ShobdoError",this.status=t,this.details=r}},u=class extends n{constructor(e="Authentication failed. Check your API key."){super(e,401),this.name="AuthenticationError"}},l=class extends n{constructor(e="Rate limit exceeded. Please upgrade your plan."){super(e,429),this.name="RateLimitError"}},h=class extends n{constructor(e="Resource not found."){super(e,404),this.name="NotFoundError"}};var m=class{apiKey;baseUrl;fetchImpl;maxRetries;timeout;constructor(e){if(!e.apiKey)throw new Error("Shobdo API key is required");if(this.apiKey=e.apiKey,this.baseUrl=e.baseUrl||"https://shobdo.me/api/v1",this.maxRetries=e.maxRetries??2,this.timeout=e.timeout??1e4,e.fetch)this.fetchImpl=e.fetch;else if(typeof fetch<"u")this.fetchImpl=fetch;else throw new Error("No global fetch API found. Please provide a custom fetch implementation in ClientConfig.")}sleep(e){return new Promise(t=>setTimeout(t,e))}async search(e,t){let r=new URLSearchParams({q:e});return t&&(t.limit!==void 0&&r.append("limit",t.limit.toString()),t.offset!==void 0&&r.append("offset",t.offset.toString()),t.lang&&r.append("lang",t.lang),t.source&&r.append("source",t.source),t.matchType&&r.append("matchType",t.matchType)),this.request(`/search?${r.toString()}`)}async getEntryById(e,t){return this.request(`/entry/${encodeURIComponent(e)}/${encodeURIComponent(t)}`)}async getEntryByWord(e,t){return this.request(`/entry/${encodeURIComponent(e)}/word/${encodeURIComponent(t)}`)}getAudioUrl(e,t){return`${this.baseUrl}/audio/${encodeURIComponent(e)}/${encodeURIComponent(t)}`}async request(e,t){let r=`${this.baseUrl}${e}`,f;for(let o=0;o<=this.maxRetries;o++)try{let i=new AbortController,y=setTimeout(()=>i.abort(),this.timeout),s=await this.fetchImpl(r,{...t,signal:i.signal,headers:{...t?.headers,Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json","User-Agent":"shobdo-js/1.1.0"}});clearTimeout(y);let p=await s.json().catch(()=>({ok:!1,data:null,error:"Invalid JSON response from server"}));if(!s.ok||!p.ok){let c=p.error||`HTTP Error ${s.status}`;if((s.status===429||s.status>=500)&&o<this.maxRetries){let d=s.headers.get("Retry-After"),g=d?parseInt(d)*1e3:Math.pow(2,o)*1e3;await this.sleep(g);continue}switch(s.status){case 401:case 403:throw new u(c);case 404:throw new h(c);case 429:throw new l(c);default:throw new n(c,s.status,p)}}return p}catch(i){if(f=i,i.name==="AbortError"||o>=this.maxRetries)throw new n(`Request failed: ${i.message}`,0,i);await this.sleep(Math.pow(2,o)*1e3)}throw f}};export{u as AuthenticationError,h as NotFoundError,l as RateLimitError,m as ShobdoClient,n as ShobdoError};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/client.ts"],"sourcesContent":["export class ShobdoError extends Error {\n public status: number;\n public details?: any;\n\n constructor(message: string, status: number, details?: any) {\n super(message);\n this.name = \"ShobdoError\";\n this.status = status;\n this.details = details;\n }\n}\n\nexport class AuthenticationError extends ShobdoError {\n constructor(message: string = \"Authentication failed. Check your API key.\") {\n super(message, 401);\n this.name = \"AuthenticationError\";\n }\n}\n\nexport class RateLimitError extends ShobdoError {\n constructor(message: string = \"Rate limit exceeded. Please upgrade your plan.\") {\n super(message, 429);\n this.name = \"RateLimitError\";\n }\n}\n\nexport class NotFoundError extends ShobdoError {\n constructor(message: string = \"Resource not found.\") {\n super(message, 404);\n this.name = \"NotFoundError\";\n }\n}\n","import { ApiResponse, FullEntry, SearchOptions, SearchResult } from \"./types\";\nimport { AuthenticationError, NotFoundError, RateLimitError, ShobdoError } from \"./errors\";\n\nexport interface ClientConfig {\n /** The Shobdo API key */\n apiKey: string;\n /** Optional base URL (defaults to https://shobdo.me/api/v1) */\n baseUrl?: string;\n /** Optional custom fetch implementation */\n fetch?: typeof fetch;\n}\n\nexport class ShobdoClient {\n private apiKey: string;\n private baseUrl: string;\n private fetchImpl: typeof fetch;\n\n constructor(config: ClientConfig) {\n if (!config.apiKey) {\n throw new Error(\"Shobdo API key is required\");\n }\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl || \"https://shobdo.me/api/v1\";\n \n // Use native fetch by default, or fallback to the provided fetch\n if (config.fetch) {\n this.fetchImpl = config.fetch;\n } else if (typeof fetch !== \"undefined\") {\n this.fetchImpl = fetch;\n } else {\n throw new Error(\"No global fetch API found. Please provide a custom fetch implementation in ClientConfig.\");\n }\n }\n\n /**\n * Search across all available dictionaries\n * @param query The search term\n * @param options Search filters and pagination\n */\n async search(query: string, options?: SearchOptions): Promise<ApiResponse<SearchResult[]>> {\n const params = new URLSearchParams({ q: query });\n \n if (options) {\n if (options.limit !== undefined) params.append(\"limit\", options.limit.toString());\n if (options.offset !== undefined) params.append(\"offset\", options.offset.toString());\n if (options.lang) params.append(\"lang\", options.lang);\n if (options.source) params.append(\"source\", options.source);\n if (options.matchType) params.append(\"matchType\", options.matchType);\n }\n\n return this.request<SearchResult[]>(`/search?${params.toString()}`);\n }\n\n /**\n * Retrieve a full dictionary entry by its unique ID\n * @param source The dictionary ID\n * @param id The entry ID\n */\n async getEntryById(source: string, id: string): Promise<ApiResponse<FullEntry>> {\n return this.request<FullEntry>(`/entry/${encodeURIComponent(source)}/${encodeURIComponent(id)}`);\n }\n\n /**\n * Look up a dictionary entry by word string\n * @param source The dictionary ID\n * @param word The word to look up\n */\n async getEntryByWord(source: string, word: string): Promise<ApiResponse<FullEntry>> {\n return this.request<FullEntry>(`/entry/${encodeURIComponent(source)}/word/${encodeURIComponent(word)}`);\n }\n\n /**\n * Get the direct URL to the pronunciation audio for an entry\n * @param source The dictionary ID\n * @param lexid The entry ID\n * @returns The full URL to the audio endpoint (which redirects to the CDN)\n */\n getAudioUrl(source: string, lexid: string): string {\n return `${this.baseUrl}/audio/${encodeURIComponent(source)}/${encodeURIComponent(lexid)}`;\n }\n\n private async request<T>(path: string, init?: RequestInit): Promise<ApiResponse<T>> {\n const url = `${this.baseUrl}${path}`;\n \n const response = await this.fetchImpl(url, {\n ...init,\n headers: {\n ...init?.headers,\n \"Authorization\": `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n });\n\n const data: ApiResponse<T> = await response.json().catch(() => ({\n ok: false,\n data: null as any,\n error: \"Invalid JSON response from server\"\n }));\n\n if (!response.ok || !data.ok) {\n const errorMessage = data.error || `HTTP Error ${response.status}`;\n \n switch (response.status) {\n case 401:\n case 403:\n throw new AuthenticationError(errorMessage);\n case 404:\n throw new NotFoundError(errorMessage);\n case 429:\n throw new RateLimitError(errorMessage);\n default:\n throw new ShobdoError(errorMessage, response.status, data);\n }\n }\n\n return data;\n }\n}\n"],"mappings":"AAAO,IAAMA,EAAN,cAA0B,KAAM,CAC9B,OACA,QAEP,YAAYC,EAAiBC,EAAgBC,EAAe,CAC1D,MAAMF,CAAO,EACb,KAAK,KAAO,cACZ,KAAK,OAASC,EACd,KAAK,QAAUC,CACjB,CACF,EAEaC,EAAN,cAAkCJ,CAAY,CACnD,YAAYC,EAAkB,6CAA8C,CAC1E,MAAMA,EAAS,GAAG,EAClB,KAAK,KAAO,qBACd,CACF,EAEaI,EAAN,cAA6BL,CAAY,CAC9C,YAAYC,EAAkB,iDAAkD,CAC9E,MAAMA,EAAS,GAAG,EAClB,KAAK,KAAO,gBACd,CACF,EAEaK,EAAN,cAA4BN,CAAY,CAC7C,YAAYC,EAAkB,sBAAuB,CACnD,MAAMA,EAAS,GAAG,EAClB,KAAK,KAAO,eACd,CACF,ECnBO,IAAMM,EAAN,KAAmB,CAChB,OACA,QACA,UAER,YAAYC,EAAsB,CAChC,GAAI,CAACA,EAAO,OACV,MAAM,IAAI,MAAM,4BAA4B,EAM9C,GAJA,KAAK,OAASA,EAAO,OACrB,KAAK,QAAUA,EAAO,SAAW,2BAG7BA,EAAO,MACT,KAAK,UAAYA,EAAO,cACf,OAAO,MAAU,IAC1B,KAAK,UAAY,UAEjB,OAAM,IAAI,MAAM,0FAA0F,CAE9G,CAOA,MAAM,OAAOC,EAAeC,EAA+D,CACzF,IAAMC,EAAS,IAAI,gBAAgB,CAAE,EAAGF,CAAM,CAAC,EAE/C,OAAIC,IACEA,EAAQ,QAAU,QAAWC,EAAO,OAAO,QAASD,EAAQ,MAAM,SAAS,CAAC,EAC5EA,EAAQ,SAAW,QAAWC,EAAO,OAAO,SAAUD,EAAQ,OAAO,SAAS,CAAC,EAC/EA,EAAQ,MAAMC,EAAO,OAAO,OAAQD,EAAQ,IAAI,EAChDA,EAAQ,QAAQC,EAAO,OAAO,SAAUD,EAAQ,MAAM,EACtDA,EAAQ,WAAWC,EAAO,OAAO,YAAaD,EAAQ,SAAS,GAG9D,KAAK,QAAwB,WAAWC,EAAO,SAAS,CAAC,EAAE,CACpE,CAOA,MAAM,aAAaC,EAAgBC,EAA6C,CAC9E,OAAO,KAAK,QAAmB,UAAU,mBAAmBD,CAAM,CAAC,IAAI,mBAAmBC,CAAE,CAAC,EAAE,CACjG,CAOA,MAAM,eAAeD,EAAgBE,EAA+C,CAClF,OAAO,KAAK,QAAmB,UAAU,mBAAmBF,CAAM,CAAC,SAAS,mBAAmBE,CAAI,CAAC,EAAE,CACxG,CAQA,YAAYF,EAAgBG,EAAuB,CACjD,MAAO,GAAG,KAAK,OAAO,UAAU,mBAAmBH,CAAM,CAAC,IAAI,mBAAmBG,CAAK,CAAC,EACzF,CAEA,MAAc,QAAWC,EAAcC,EAA6C,CAClF,IAAMC,EAAM,GAAG,KAAK,OAAO,GAAGF,CAAI,GAE5BG,EAAW,MAAM,KAAK,UAAUD,EAAK,CACzC,GAAGD,EACH,QAAS,CACP,GAAGA,GAAM,QACT,cAAiB,UAAU,KAAK,MAAM,GACtC,eAAgB,kBAClB,CACF,CAAC,EAEKG,EAAuB,MAAMD,EAAS,KAAK,EAAE,MAAM,KAAO,CAC9D,GAAI,GACJ,KAAM,KACN,MAAO,mCACT,EAAE,EAEF,GAAI,CAACA,EAAS,IAAM,CAACC,EAAK,GAAI,CAC5B,IAAMC,EAAeD,EAAK,OAAS,cAAcD,EAAS,MAAM,GAEhE,OAAQA,EAAS,OAAQ,CACvB,IAAK,KACL,IAAK,KACH,MAAM,IAAIG,EAAoBD,CAAY,EAC5C,IAAK,KACH,MAAM,IAAIE,EAAcF,CAAY,EACtC,IAAK,KACH,MAAM,IAAIG,EAAeH,CAAY,EACvC,QACE,MAAM,IAAII,EAAYJ,EAAcF,EAAS,OAAQC,CAAI,CAC7D,CACF,CAEA,OAAOA,CACT,CACF","names":["ShobdoError","message","status","details","AuthenticationError","RateLimitError","NotFoundError","ShobdoClient","config","query","options","params","source","id","word","lexid","path","init","url","response","data","errorMessage","AuthenticationError","NotFoundError","RateLimitError","ShobdoError"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/client.ts"],"sourcesContent":["export class ShobdoError extends Error {\n public status: number;\n public details?: any;\n\n constructor(message: string, status: number, details?: any) {\n super(message);\n this.name = \"ShobdoError\";\n this.status = status;\n this.details = details;\n }\n}\n\nexport class AuthenticationError extends ShobdoError {\n constructor(message: string = \"Authentication failed. Check your API key.\") {\n super(message, 401);\n this.name = \"AuthenticationError\";\n }\n}\n\nexport class RateLimitError extends ShobdoError {\n constructor(message: string = \"Rate limit exceeded. Please upgrade your plan.\") {\n super(message, 429);\n this.name = \"RateLimitError\";\n }\n}\n\nexport class NotFoundError extends ShobdoError {\n constructor(message: string = \"Resource not found.\") {\n super(message, 404);\n this.name = \"NotFoundError\";\n }\n}\n","import { ApiResponse, FullEntry, SearchOptions, SearchResult } from \"./types\";\nimport { AuthenticationError, NotFoundError, RateLimitError, ShobdoError } from \"./errors\";\n\nexport interface ClientConfig {\n /** The Shobdo API key */\n apiKey: string;\n /** Optional base URL (defaults to https://shobdo.me/api/v1) */\n baseUrl?: string;\n /** Optional custom fetch implementation */\n fetch?: typeof fetch;\n /** Maximum number of retries for 429 and 5xx errors (default: 2) */\n maxRetries?: number;\n /** Request timeout in milliseconds (default: 10000) */\n timeout?: number;\n}\n\nexport class ShobdoClient {\n private apiKey: string;\n private baseUrl: string;\n private fetchImpl: typeof fetch;\n private maxRetries: number;\n private timeout: number;\n\n constructor(config: ClientConfig) {\n if (!config.apiKey) {\n throw new Error(\"Shobdo API key is required\");\n }\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl || \"https://shobdo.me/api/v1\";\n this.maxRetries = config.maxRetries ?? 2;\n this.timeout = config.timeout ?? 10000;\n \n // Use native fetch by default, or fallback to the provided fetch\n if (config.fetch) {\n this.fetchImpl = config.fetch;\n } else if (typeof fetch !== \"undefined\") {\n this.fetchImpl = fetch;\n } else {\n throw new Error(\"No global fetch API found. Please provide a custom fetch implementation in ClientConfig.\");\n }\n }\n\n /**\n * Helper to sleep for a given number of milliseconds\n */\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Search across all available dictionaries\n * @param query The search term\n * @param options Search filters and pagination\n */\n async search(query: string, options?: SearchOptions): Promise<ApiResponse<SearchResult[]>> {\n const params = new URLSearchParams({ q: query });\n \n if (options) {\n if (options.limit !== undefined) params.append(\"limit\", options.limit.toString());\n if (options.offset !== undefined) params.append(\"offset\", options.offset.toString());\n if (options.lang) params.append(\"lang\", options.lang);\n if (options.source) params.append(\"source\", options.source);\n if (options.matchType) params.append(\"matchType\", options.matchType);\n }\n\n return this.request<SearchResult[]>(`/search?${params.toString()}`);\n }\n\n /**\n * Retrieve a full dictionary entry by its unique ID\n * @param source The dictionary ID\n * @param id The entry ID\n */\n async getEntryById(source: string, id: string): Promise<ApiResponse<FullEntry>> {\n return this.request<FullEntry>(`/entry/${encodeURIComponent(source)}/${encodeURIComponent(id)}`);\n }\n\n /**\n * Look up a dictionary entry by word string\n * @param source The dictionary ID\n * @param word The word to look up\n */\n async getEntryByWord(source: string, word: string): Promise<ApiResponse<FullEntry>> {\n return this.request<FullEntry>(`/entry/${encodeURIComponent(source)}/word/${encodeURIComponent(word)}`);\n }\n\n /**\n * Get the direct URL to the pronunciation audio for an entry\n * @param source The dictionary ID\n * @param lexid The entry ID\n * @returns The full URL to the audio endpoint (which redirects to the CDN)\n */\n getAudioUrl(source: string, lexid: string): string {\n return `${this.baseUrl}/audio/${encodeURIComponent(source)}/${encodeURIComponent(lexid)}`;\n }\n\n private async request<T>(path: string, init?: RequestInit): Promise<ApiResponse<T>> {\n const url = `${this.baseUrl}${path}`;\n let lastError: any;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n const response = await this.fetchImpl(url, {\n ...init,\n signal: controller.signal as any,\n headers: {\n ...init?.headers,\n \"Authorization\": `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n \"User-Agent\": \"shobdo-js/1.1.0\",\n },\n });\n\n clearTimeout(timeoutId);\n\n const data: ApiResponse<T> = await response.json().catch(() => ({\n ok: false,\n data: null as any,\n error: \"Invalid JSON response from server\"\n }));\n\n if (!response.ok || !data.ok) {\n const errorMessage = data.error || `HTTP Error ${response.status}`;\n \n if (response.status === 429 || response.status >= 500) {\n // Retry on rate limits or server errors\n if (attempt < this.maxRetries) {\n const retryAfter = response.headers.get(\"Retry-After\");\n const delay = retryAfter ? parseInt(retryAfter) * 1000 : Math.pow(2, attempt) * 1000;\n await this.sleep(delay);\n continue;\n }\n }\n\n switch (response.status) {\n case 401:\n case 403:\n throw new AuthenticationError(errorMessage);\n case 404:\n throw new NotFoundError(errorMessage);\n case 429:\n throw new RateLimitError(errorMessage);\n default:\n throw new ShobdoError(errorMessage, response.status, data);\n }\n }\n\n return data;\n } catch (error: any) {\n lastError = error;\n // Don't retry if it's an AbortError (timeout) or if we've run out of retries\n if (error.name === \"AbortError\" || attempt >= this.maxRetries) {\n throw new ShobdoError(`Request failed: ${error.message}`, 0, error);\n }\n await this.sleep(Math.pow(2, attempt) * 1000);\n }\n }\n\n throw lastError;\n }\n}\n"],"mappings":"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","names":["ShobdoError","message","status","details","AuthenticationError","RateLimitError","NotFoundError","ShobdoClient","config","ms","resolve","query","options","params","source","id","word","lexid","path","init","url","lastError","attempt","controller","timeoutId","response","data","errorMessage","retryAfter","delay","AuthenticationError","NotFoundError","RateLimitError","ShobdoError","error"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shobdo/js",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Official JavaScript/TypeScript SDK for the Shobdo API",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"sdk"
|
|
33
33
|
],
|
|
34
34
|
"author": "Shobdo",
|
|
35
|
-
"license": "
|
|
35
|
+
"license": "Apache-2.0",
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^26.1.1",
|
|
38
38
|
"tsup": "^8.5.1",
|