UNPKG

biblesdk

Version:

Typescript client for Bible SDK API

315 lines (310 loc) 10.5 kB
'use strict'; var lruCache = require('lru-cache'); // This file is auto-generated. Do not edit directly. var BASE_URL = "https://biblesdk.com/api"; var httpCache = new lruCache.LRUCache({ // max number of items in cache max: 500, // how long items can live in the cache in ms ttl: 1e3 * 60 * 5, // return stale items before removing from cache? allowStale: true }); async function fancyFetch(input, init, timeoutMs = 3e4, maxRetries = 5, baseDelay = 200, maxDelay = 3e4) { let delay = baseDelay; const controller = new AbortController(); for (let attempt = 0; attempt <= maxRetries; attempt++) { const attemptTimer = setTimeout(() => controller.abort(), timeoutMs); try { const res = await fetch(input, { ...init, signal: controller.signal }); if (!res.ok && res.status >= 500 && attempt < maxRetries) { throw new Error(`HTTP ${res.status}`); } clearTimeout(attemptTimer); return res; } catch (err) { clearTimeout(attemptTimer); if (attempt === maxRetries) throw err; delay = Math.min(maxDelay, Math.random() * (delay * 3 - baseDelay) + baseDelay); await new Promise((res) => setTimeout(res, delay)); } } throw new Error(`fetch failed after ${maxRetries} attempts`); } async function httpGet(url, headers) { const cacheKey = `${url}-${JSON.stringify(headers)}`; if (httpCache.has(cacheKey)) { return httpCache.get(cacheKey); } const response = await fancyFetch(`${BASE_URL}${url}`, { method: "GET", headers: { ...headers, "Content-Type": "application/json" } }); const data = response.json(); httpCache.set(cacheKey, data); return data; } // src/client.ts function parsePaginationLink(link) { const match = link.match( /^\/api\/books\/([^/]+)\/chapters\/([^/]+)\/verses(?:\?([^#]+))?/ ); if (match != null) { const [_, bookParam, chapterParam, query] = match; if (bookParam != null && chapterParam != null && query != null) { const params = new URLSearchParams(query); const takeParam = params.get("take"); const cursorParam = params.get("cursor"); if (takeParam != null && cursorParam != null) { const take = parseInt(takeParam); const cursor = parseInt(cursorParam); const chapter = parseInt(chapterParam); if (bookParam.length === 3 && chapter > 0 && take > 0 && cursor >= 0) { return { book: bookParam, chapter, take, cursor }; } } } } return null; } function parsePhrases(data, book, chapter) { const phrases = []; for (const p of data ?? []) { if (p.text == null || p.usfm == null || p.position == null) { throw new Error(`Failed to parse phrases for book ${book}, chapter ${chapter}, verse ${p.verse}, missing required phrase data`); } phrases.push({ book, text: p.text, usfm: p.usfm, position: p.position, verse: p.verse ?? null, verse_position: p.verse_position ?? null, chapter }); } return phrases; } function parsePhrasesWithConcordanceInfo(data, book, chapter) { const phrases = parsePhrases(data, book, chapter); const phrasesWithConcordanceInfo = []; for (const p of phrases) { const phraseIndex = data.findIndex((d) => d.position === p.position); if (phraseIndex === -1 || data[phraseIndex] == null) { throw new Error(`Failed to parse phrases with concordance info for book ${book}, chapter ${chapter}, verse ${p.verse}, missing required phrase data`); } phrasesWithConcordanceInfo.push({ ...p, strongs_number: data[phraseIndex].strongs_number ?? null, strongs_type: data[phraseIndex].strongs_type ?? null, transliteration: data[phraseIndex].transliteration ?? null, definition: data[phraseIndex].definition ?? null, hebrew_word: data[phraseIndex].hebrew_word ?? null, greek_word: data[phraseIndex].greek_word ?? null }); } return phrasesWithConcordanceInfo; } async function parseVerseRange(book, chapter, verseRange) { const [startVerse, endVerse] = verseRange; const chapterMetadata = await getChapterMetadata(book, chapter); if (endVerse > chapterMetadata.verses) { throw new Error(`Invalid verse range: end verse ${endVerse} out of range for book ${book}, chapter ${chapter}, which has (${chapterMetadata.verses}) verses`); } if (startVerse < 1 || startVerse > chapterMetadata.verses) { throw new Error(`Invalid verse range: start verse ${startVerse} out of range for book ${book}, chapter ${chapter}, which has (${chapterMetadata.verses}) verses`); } if (startVerse > endVerse) { throw new Error(`Invalid verse range: ${startVerse} must be less than or equal to requested end verse ${endVerse} for book ${book}, chapter ${chapter}`); } return { startVerse, endVerse }; } function joinPhrases(phrases) { const output = []; for (let i = 0; i < phrases.length; i++) { const rawPhrase = phrases[i]; if (rawPhrase != null) { output.push(rawPhrase.text.replace(/ {2,}/g, " ")); } } return output.join(""); } async function listBooks() { const data = await httpGet("/books"); if (data.books == null || data.books.length === 0) { throw new Error("Failed to list books, no books found"); } const books = []; for (const b of data.books ?? []) { if (b.code == null || b.name == null || b.chapters == null || b.position == null) { throw new Error("Failed to list books, missing required book data"); } books.push({ code: b.code, name: b.name, chapters: b.chapters, position: b.position }); } return books; } async function listChapters(book) { const data = await httpGet(`/books/${book}/chapters`); if (data.chapters == null || data.chapters.length === 0) { throw new Error(`Failed to list chapters for book ${book}, no chapters found`); } const chapters = []; for (const c of data.chapters ?? []) { if (c.chapter == null || c.position == null || c.verses == null) { throw new Error(`Failed to list chapters for book ${book}, missing required chapter data`); } chapters.push({ book, chapter: c.chapter, position: c.position, verses: c.verses }); } return chapters; } async function getBookMetadata(book) { const data = await httpGet(`/books/${book}`); if (data.code == null || data.name == null || data.chapters == null || data.position == null) { throw new Error(`Failed to get book metadata for book ${book}, missing required book data`); } return { code: data.code, name: data.name, chapters: data.chapters, position: data.position }; } async function getChapterMetadata(book, chapter) { const data = await httpGet(`/books/${book}/chapters/${chapter}`); if (data.chapter == null || data.position == null || data.verses == null) { throw new Error(`Failed to get chapter metadata for book ${book}, chapter ${chapter}, missing required chapter data`); } return { book, chapter: data.chapter, position: data.position, verses: data.verses }; } async function read(book, chapter, cursor = 0, take = 50, withConcordanceInfo = false) { let url = `/books/${book}/chapters/${chapter}/verses?cursor=${cursor}&take=${take}`; if (withConcordanceInfo === true) { url += `&concordance=true`; } const data = await httpGet(url); if (data.phrases == null || data.phrases.length === 0) { throw new Error(`Failed to read chapter ${chapter} for book ${book}, no phrases found`); } if (data.links == null) { throw new Error(`Error while reading book ${book}, chapter ${chapter}, no next or prev links available`); } let phrases = []; if (withConcordanceInfo === true) { phrases = parsePhrasesWithConcordanceInfo(data.phrases, book, chapter); } else { phrases = parsePhrases(data.phrases, book, chapter); } let n = null; let p = null; if (data.links.next != null) { n = parsePaginationLink(data.links.next); } if (data.links.prev != null) { p = parsePaginationLink(data.links.prev); } const output = { phrases, next: null, prev: null }; if (n != null) { output.next = () => read(n.book, n.chapter, n.cursor, n.take); } if (p != null) { output.prev = () => read(p.book, p.chapter, p.cursor, p.take); } return output; } async function getPhrases(book, chapter, verseRange, withConcordanceInfo = false) { const { startVerse, endVerse } = await parseVerseRange(book, chapter, verseRange); const url = `/books/${book}/chapters/${chapter}/verses/${startVerse}-${endVerse}`; if (withConcordanceInfo === true) { const data = await httpGet(`${url}?concordance=true`); return parsePhrasesWithConcordanceInfo(data, book, chapter); } else { const data = await httpGet(url); return parsePhrases(data, book, chapter); } } async function getVerses(book, chapter, verseRange) { const phrases = await getPhrases(book, chapter, verseRange); const verses = []; let currentVerse = { number: 1, phrases: [] }; for (const p of phrases) { if (p.verse == null) { continue; } if (p.verse > currentVerse.number) { verses.push({ book, chapter, verse: currentVerse.number, text: joinPhrases(currentVerse.phrases) }); currentVerse.number = p.verse; currentVerse.phrases = []; } currentVerse.phrases.push(p); } return verses; } async function getSearchResults(query) { const data = await httpGet(`/search?query=${query}`); if (data.matches == null || data.matches.length === 0) { throw new Error(`Failed to retrieve search results for query "${query}"`); } const matches = []; for (const m of data.matches) { if (m.book == null || m.chapter == null || m.verse == null || m.score == null) { throw new Error(`Unexpected missing data in search results for query ${query}`); } matches.push({ book: m.book, chapter: m.chapter, verse: m.verse, score: m.score }); } return matches; } exports.getBookMetadata = getBookMetadata; exports.getChapterMetadata = getChapterMetadata; exports.getPhrases = getPhrases; exports.getSearchResults = getSearchResults; exports.getVerses = getVerses; exports.joinPhrases = joinPhrases; exports.listBooks = listBooks; exports.listChapters = listChapters; exports.read = read; //# sourceMappingURL=out.js.map //# sourceMappingURL=index.js.map