@shobdo/js 1.0.1 → 1.0.2
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/dist/client.d.ts +6 -3
- 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 +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ApiResponse, FullEntry, SearchOptions, SearchResult } from "./types";
|
|
2
2
|
export interface ClientConfig {
|
|
3
|
-
/** The Shobdo API key */
|
|
4
|
-
apiKey
|
|
3
|
+
/** The Shobdo API key (optional for internal web requests) */
|
|
4
|
+
apiKey?: string;
|
|
5
5
|
/** Optional base URL (defaults to https://shobdo.me/api/v1) */
|
|
6
6
|
baseUrl?: string;
|
|
7
7
|
/** Optional custom fetch implementation */
|
|
@@ -10,13 +10,16 @@ export interface ClientConfig {
|
|
|
10
10
|
maxRetries?: number;
|
|
11
11
|
/** Request timeout in milliseconds (default: 10000) */
|
|
12
12
|
timeout?: number;
|
|
13
|
+
/** Custom headers to append to all requests */
|
|
14
|
+
headers?: Record<string, string>;
|
|
13
15
|
}
|
|
14
16
|
export declare class ShobdoClient {
|
|
15
|
-
private apiKey
|
|
17
|
+
private apiKey?;
|
|
16
18
|
private baseUrl;
|
|
17
19
|
private fetchImpl;
|
|
18
20
|
private maxRetries;
|
|
19
21
|
private timeout;
|
|
22
|
+
private defaultHeaders;
|
|
20
23
|
constructor(config: ClientConfig);
|
|
21
24
|
/**
|
|
22
25
|
* Helper to sleep for a given number of milliseconds
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var f=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var b=Object.getOwnPropertyNames;var A=Object.prototype.hasOwnProperty;var C=(s,e)=>{for(var t in e)f(s,t,{get:e[t],enumerable:!0})},I=(s,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of b(e))!A.call(s,n)&&n!==t&&f(s,n,{get:()=>e[n],enumerable:!(r=x(e,n))||r.enumerable});return s};var E=s=>I(f({},"__esModule",{value:!0}),s);var U={};C(U,{AuthenticationError:()=>u,NotFoundError:()=>h,RateLimitError:()=>c,ShobdoClient:()=>d,ShobdoError:()=>i});module.exports=E(U);var i=class extends Error{status;details;constructor(e,t,r){super(e),this.name="ShobdoError",this.status=t,this.details=r}},u=class extends i{constructor(e="Authentication failed. Check your API key."){super(e,401),this.name="AuthenticationError"}},c=class extends i{constructor(e="Rate limit exceeded. Please upgrade your plan."){super(e,429),this.name="RateLimitError"}},h=class extends i{constructor(e="Resource not found."){super(e,404),this.name="NotFoundError"}};var d=class{apiKey;baseUrl;fetchImpl;maxRetries;timeout;defaultHeaders;constructor(e){if(this.apiKey=e.apiKey,this.baseUrl=e.baseUrl||"https://shobdo.me/api/v1",this.maxRetries=e.maxRetries??2,this.timeout=e.timeout??1e4,this.defaultHeaders=e.headers||{},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}`,n;for(let p=0;p<=this.maxRetries;p++)try{let o=new AbortController,R=setTimeout(()=>o.abort(),this.timeout),y={...this.defaultHeaders,...t?.headers,"Content-Type":"application/json","User-Agent":"shobdo-js/1.1.0"};this.apiKey&&(y.Authorization=`Bearer ${this.apiKey}`);let a=await this.fetchImpl(r,{...t,signal:o.signal,headers:y});clearTimeout(R);let l=await a.json().catch(()=>({ok:!1,data:null,error:"Invalid JSON response from server"}));if(!a.ok||!l.ok){let m=l.error||`HTTP Error ${a.status}`;if((a.status===429||a.status>=500)&&p<this.maxRetries){let g=a.headers.get("Retry-After"),w=g?parseInt(g)*1e3:Math.pow(2,p)*1e3;await this.sleep(w);continue}switch(a.status){case 401:case 403:throw new u(m);case 404:throw new h(m);case 429:throw new c(m);default:throw new i(m,a.status,l)}}return l}catch(o){if(n=o,o.name==="AbortError"||p>=this.maxRetries)throw new i(`Request failed: ${o.message}`,0,o);await this.sleep(Math.pow(2,p)*1e3)}throw n}};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 /** 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"]}
|
|
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 (optional for internal web requests) */\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 /** Custom headers to append to all requests */\n headers?: Record<string, string>;\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 private defaultHeaders: Record<string, string>;\n\n constructor(config: ClientConfig) {\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 this.defaultHeaders = config.headers || {};\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 headers: Record<string, string> = {\n ...this.defaultHeaders,\n ...(init?.headers as Record<string, string>),\n \"Content-Type\": \"application/json\",\n \"User-Agent\": \"shobdo-js/1.1.0\",\n };\n\n if (this.apiKey) {\n headers[\"Authorization\"] = `Bearer ${this.apiKey}`;\n }\n\n const response = await this.fetchImpl(url, {\n ...init,\n signal: controller.signal as any,\n headers,\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","headers","response","data","errorMessage","retryAfter","delay","AuthenticationError","NotFoundError","RateLimitError","ShobdoError","error"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var
|
|
1
|
+
var i=class extends Error{status;details;constructor(e,t,r){super(e),this.name="ShobdoError",this.status=t,this.details=r}},c=class extends i{constructor(e="Authentication failed. Check your API key."){super(e,401),this.name="AuthenticationError"}},h=class extends i{constructor(e="Rate limit exceeded. Please upgrade your plan."){super(e,429),this.name="RateLimitError"}},l=class extends i{constructor(e="Resource not found."){super(e,404),this.name="NotFoundError"}};var m=class{apiKey;baseUrl;fetchImpl;maxRetries;timeout;defaultHeaders;constructor(e){if(this.apiKey=e.apiKey,this.baseUrl=e.baseUrl||"https://shobdo.me/api/v1",this.maxRetries=e.maxRetries??2,this.timeout=e.timeout??1e4,this.defaultHeaders=e.headers||{},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}`,d;for(let a=0;a<=this.maxRetries;a++)try{let n=new AbortController,g=setTimeout(()=>n.abort(),this.timeout),f={...this.defaultHeaders,...t?.headers,"Content-Type":"application/json","User-Agent":"shobdo-js/1.1.0"};this.apiKey&&(f.Authorization=`Bearer ${this.apiKey}`);let s=await this.fetchImpl(r,{...t,signal:n.signal,headers:f});clearTimeout(g);let p=await s.json().catch(()=>({ok:!1,data:null,error:"Invalid JSON response from server"}));if(!s.ok||!p.ok){let u=p.error||`HTTP Error ${s.status}`;if((s.status===429||s.status>=500)&&a<this.maxRetries){let y=s.headers.get("Retry-After"),R=y?parseInt(y)*1e3:Math.pow(2,a)*1e3;await this.sleep(R);continue}switch(s.status){case 401:case 403:throw new c(u);case 404:throw new l(u);case 429:throw new h(u);default:throw new i(u,s.status,p)}}return p}catch(n){if(d=n,n.name==="AbortError"||a>=this.maxRetries)throw new i(`Request failed: ${n.message}`,0,n);await this.sleep(Math.pow(2,a)*1e3)}throw d}};export{c as AuthenticationError,l as NotFoundError,h as RateLimitError,m as ShobdoClient,i 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 /** 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"]}
|
|
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 (optional for internal web requests) */\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 /** Custom headers to append to all requests */\n headers?: Record<string, string>;\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 private defaultHeaders: Record<string, string>;\n\n constructor(config: ClientConfig) {\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 this.defaultHeaders = config.headers || {};\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 headers: Record<string, string> = {\n ...this.defaultHeaders,\n ...(init?.headers as Record<string, string>),\n \"Content-Type\": \"application/json\",\n \"User-Agent\": \"shobdo-js/1.1.0\",\n };\n\n if (this.apiKey) {\n headers[\"Authorization\"] = `Bearer ${this.apiKey}`;\n }\n\n const response = await this.fetchImpl(url, {\n ...init,\n signal: controller.signal as any,\n headers,\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","headers","response","data","errorMessage","retryAfter","delay","AuthenticationError","NotFoundError","RateLimitError","ShobdoError","error"]}
|