From 2a80a43eed0d92264e84dcaaf253c05988328f9a Mon Sep 17 00:00:00 2001 From: Basit Minhas Date: Mon, 4 May 2026 09:12:47 +0500 Subject: [PATCH 1/2] Add v4 content parity SDK methods --- .changeset/v4-content-parity.md | 5 + packages/api/mocks/handlers.ts | 145 ++++++++++++++++-- packages/api/src/runtime/create-client.ts | 17 +- .../api/src/runtime/create-public-client.ts | 3 +- packages/api/src/sdk/chapters.ts | 38 ++++- packages/api/src/sdk/client.ts | 3 + packages/api/src/sdk/hadith-references.ts | 90 +++++++++++ packages/api/src/types/api/ChapterInfo.ts | 8 + packages/api/src/types/api/HadithReference.ts | 57 +++++++ packages/api/src/types/api/index.ts | 1 + packages/api/test/chapters.test.ts | 20 +++ packages/api/test/contracts.test.ts | 41 +++-- packages/api/test/hadith-references.test.ts | 58 +++++++ 13 files changed, 454 insertions(+), 32 deletions(-) create mode 100644 .changeset/v4-content-parity.md create mode 100644 packages/api/src/sdk/hadith-references.ts create mode 100644 packages/api/src/types/api/HadithReference.ts create mode 100644 packages/api/test/hadith-references.test.ts diff --git a/.changeset/v4-content-parity.md b/.changeset/v4-content-parity.md new file mode 100644 index 0000000..3fee64b --- /dev/null +++ b/.changeset/v4-content-parity.md @@ -0,0 +1,5 @@ +--- +"@quranjs/api": minor +--- + +Add typed v4 hadith reference SDK methods and expose the full chapter info response with resource selection support. diff --git a/packages/api/mocks/handlers.ts b/packages/api/mocks/handlers.ts index 2e98e76..a660fbf 100644 --- a/packages/api/mocks/handlers.ts +++ b/packages/api/mocks/handlers.ts @@ -662,18 +662,56 @@ export const handlers = [ http.get( "https://apis.quran.foundation/content/api/v4/chapters/:chapter_id/info", - () => { + ({ request }) => { + const url = new URL(request.url); + const includeResources = + url.searchParams.get("include_resources") === "true"; + const resourceId = url.searchParams.get("resource_id"); + const chapterInfo = + resourceId === "missing-resource" + ? null + : { + id: 1, + chapter_id: 1, + language_name: "english", + resource_id: resourceId ? Number(resourceId) || 58 : 58, + short_text: + "This Surah is named Al-Fatihah because of its subject-matter. Fatihah is that which opens a subject or a book or any other thing. In other words, Al-Fatihah is a sort of preface.", + source: + "Sayyid Abul Ala Maududi - Tafhim al-Qur'an - The Meaning of the Quran", + text: "

Name

\r\n

This Surah is named Al-Fatihah because of its subject-matter. Fatihah is that which opens a subject or a book or any other thing. In other words, Al-Fatihah is a sort of preface.

\r\n

Period of Revelation

...", + }; + return HttpResponse.json({ - chapterInfo: { - id: 1, - chapter_id: 1, - language_name: "english", - short_text: - "This Surah is named Al-Fatihah because of its subject-matter. Fatihah is that which opens a subject or a book or any other thing. In other words, Al-Fatihah is a sort of preface.", - source: - "Sayyid Abul Ala Maududi - Tafhim al-Qur'an - The Meaning of the Quran", - text: "

Name

\r\n

This Surah is named Al-Fatihah because of its subject-matter. Fatihah is that which opens a subject or a book or any other thing. In other words, Al-Fatihah is a sort of preface.

\r\n

Period of Revelation

...", - }, + chapter_info: chapterInfo, + ...(includeResources + ? { + resources: [ + { + id: 58, + name: "Chapter Info", + author_name: "Sayyid Abul Ala Maududi", + slug: "", + language_name: "english", + translated_name: { + name: "Chapter Info", + language_name: "english", + }, + }, + { + id: 167, + name: "Tafsir al-Tahrir wa'l-Tanwir", + author_name: "Ibn Ashur", + slug: "en-tafsir-ibn-ashur", + language_name: "english", + translated_name: { + name: "Tafsir al-Tahrir wa'l-Tanwir", + language_name: "english", + }, + }, + ], + } + : {}), }); }, ), @@ -747,6 +785,91 @@ export const handlers = [ }, ), + http.get( + "https://apis.quran.foundation/content/api/v4/hadith_references/by_ayah/:ayah_key", + ({ params }) => { + return HttpResponse.json({ + verse_key: params.ayah_key, + verse_number: 12, + chapter_number: 12, + language: "en", + direction: "ltr", + hadith_references: [ + { + id: 10, + collection: "bukhari", + hadith_number: "1", + our_hadith_number: 1, + arabic_urn: 111, + english_urn: 211, + surah_number: 12, + ayah_start_number: 11, + ayah_end_number: 12, + }, + { + id: 11, + collection: "muslim", + hadith_number: "3", + our_hadith_number: 1, + arabic_urn: 113, + english_urn: 213, + surah_number: 12, + ayah_start_number: 12, + ayah_end_number: 14, + }, + ], + }); + }, + ), + + http.get( + "https://apis.quran.foundation/content/api/v4/hadith_references/by_ayah/:ayah_key/hadiths", + () => { + return HttpResponse.json({ + hadiths: [ + { + urn: 201, + collection: "bukhari", + bookNumber: "1", + chapterId: "1", + hadithNumber: "1", + name: "Sahih al-Bukhari", + hadith: [ + { + lang: "en", + chapterNumber: "1", + chapterTitle: "Revelation", + body: "Narrated Umar bin Al-Khattab:...", + urn: 31, + grades: [ + { + graded_by: "Ahmad Muhammad Shakir", + grade: "Sahih", + }, + ], + }, + ], + }, + ], + page: 1, + limit: 4, + has_more: true, + language: "en", + direction: "ltr", + }); + }, + ), + + http.get( + "https://apis.quran.foundation/content/api/v4/hadith_references/count_within_range", + () => { + return HttpResponse.json({ + "12:12": 2, + "12:13": 2, + }); + }, + ), + http.get( "https://apis.quran.foundation/content/api/v4/resources/verse_media", () => { diff --git a/packages/api/src/runtime/create-client.ts b/packages/api/src/runtime/create-client.ts index 53e558f..21d583c 100644 --- a/packages/api/src/runtime/create-client.ts +++ b/packages/api/src/runtime/create-client.ts @@ -1,3 +1,4 @@ +import type { AuthService } from "@/generated/public-contracts"; import type { ApiParams, ChapterId, @@ -14,13 +15,13 @@ import type { VerseKey, } from "@/types"; import { operationCatalog } from "@/generated/contracts"; -import type { AuthService } from "@/generated/public-contracts"; import { toUserSession } from "@/lib/http-utils"; import { createGeneratedGroups, createRawClient } from "@/lib/runtime-utils"; import { replacePathParams } from "@/lib/url"; import { QuranAudio } from "@/sdk/audio"; import { QuranChapters } from "@/sdk/chapters"; import { QuranFetcher } from "@/sdk/fetcher"; +import { QuranHadithReferences } from "@/sdk/hadith-references"; import { QuranJuzs } from "@/sdk/juzs"; import { QuranResources } from "@/sdk/resources"; import { QuranSearch } from "@/sdk/search"; @@ -348,6 +349,7 @@ const createContentFacade = ( verses: QuranVerses, juzs: QuranJuzs, audio: QuranAudio, + hadithReferences: QuranHadithReferences, resources: QuranResources, raw: Record, ) => { @@ -374,8 +376,18 @@ const createContentFacade = ( get: (id: ChapterId, query?: ApiParams) => chapters.findById(id, query), getInfo: (id: ChapterId, query?: ApiParams) => chapters.findInfoById(id, query), + getInfoResponse: (id: ChapterId, query?: ApiParams) => + chapters.findInfoResponseById(id, query), list: (query?: ApiParams) => chapters.findAll(query), }, + hadithReferences: { + byAyah: (key: VerseKey, query?: ApiParams) => + hadithReferences.findByAyah(key, query), + countWithinRange: (from: VerseKey, to: VerseKey, query?: ApiParams) => + hadithReferences.countWithinRange(from, to, query), + hadithsByAyah: (key: VerseKey, query?: ApiParams) => + hadithReferences.findHadithsByAyah(key, query), + }, juzs: { list: () => juzs.findAll(), }, @@ -439,6 +451,7 @@ export const createRuntimeClient = ( const verses = new QuranVerses(fetcher); const juzs = new QuranJuzs(fetcher); const audio = new QuranAudio(fetcher); + const hadithReferences = new QuranHadithReferences(fetcher); const resources = new QuranResources(fetcher); const searchClient = new QuranSearch(fetcher); @@ -465,6 +478,7 @@ export const createRuntimeClient = ( verses, juzs, audio, + hadithReferences, resources, raw.content.v4, ); @@ -501,6 +515,7 @@ export const createRuntimeClient = ( v4: contentV4, }, getUserSession: () => fetcher.getUserSession(), + hadithReferences, juzs, oauth2: { ...oauth2V1, diff --git a/packages/api/src/runtime/create-public-client.ts b/packages/api/src/runtime/create-public-client.ts index e281eee..3bebedd 100644 --- a/packages/api/src/runtime/create-public-client.ts +++ b/packages/api/src/runtime/create-public-client.ts @@ -1,3 +1,4 @@ +import type { AuthService } from "@/generated/public-contracts"; import type { ApiParams, HTTPMethod, @@ -5,7 +6,6 @@ import type { PublicClientConfig, TokenResponse, } from "@/types"; -import type { AuthService } from "@/generated/public-contracts"; import { publicOperationCatalog } from "@/generated/public-contracts"; import { toUserSession } from "@/lib/http-utils"; import { createGeneratedGroups, createRawClient } from "@/lib/runtime-utils"; @@ -357,6 +357,7 @@ export const createPublicRuntimeClient = (config: PublicClientConfig) => { clearCachedTokens: () => fetcher.clearCachedTokens(), content: serverOnlyGuard, getUserSession: () => fetcher.getUserSession(), + hadithReferences: serverOnlyGuard, juzs: serverOnlyGuard, oauth2: { ...oauth2V1, diff --git a/packages/api/src/sdk/chapters.ts b/packages/api/src/sdk/chapters.ts index 4179fb0..5632cfd 100644 --- a/packages/api/src/sdk/chapters.ts +++ b/packages/api/src/sdk/chapters.ts @@ -3,11 +3,16 @@ import type { Chapter, ChapterId, ChapterInfo, + ChapterInfoResponse, QuranFetchClient, } from "@/types"; import { isValidChapterId } from "@/utils"; type GetChapterOptions = BaseApiParams; +type GetChapterInfoOptions = BaseApiParams & { + resourceId?: string | number; + includeResources?: boolean; +}; /** * Chapters API methods @@ -55,21 +60,38 @@ export class QuranChapters { * Get chapter info by id. * @description https://api-docs.quran.com/docs/quran.com_versioned/4.0.0/get-chapter-info * @param {ChapterId} id chapter id, minimum 1, maximum 114 - * @param {GetChapterOptions} options + * @param {GetChapterInfoOptions} options * @example * client.chapters.findInfoById('1') * client.chapters.findInfoById('114') */ async findInfoById( id: ChapterId, - options?: GetChapterOptions, - ): Promise { - if (!isValidChapterId(id)) throw new Error("Invalid chapter id"); - - const { chapterInfo } = await this.fetcher.fetch<{ - chapterInfo: ChapterInfo; - }>(`/content/api/v4/chapters/${id}/info`, options); + options?: GetChapterInfoOptions, + ): Promise { + const { chapterInfo } = await this.findInfoResponseById(id, options); return chapterInfo; } + + /** + * Get the full chapter info response, including available resources when requested. + * @description https://api-docs.quran.com/docs/quran.com_versioned/4.0.0/get-chapter-info + * @param {ChapterId} id chapter id, minimum 1, maximum 114 + * @param {GetChapterInfoOptions} options + * @example + * client.chapters.findInfoResponseById('1', { includeResources: true }) + * client.chapters.findInfoResponseById('1', { resourceId: 'en-tafsir-ibn-ashur' }) + */ + async findInfoResponseById( + id: ChapterId, + options?: GetChapterInfoOptions, + ): Promise { + if (!isValidChapterId(id)) throw new Error("Invalid chapter id"); + + return this.fetcher.fetch( + `/content/api/v4/chapters/${id}/info`, + options, + ); + } } diff --git a/packages/api/src/sdk/client.ts b/packages/api/src/sdk/client.ts index b7d96bc..5524f66 100644 --- a/packages/api/src/sdk/client.ts +++ b/packages/api/src/sdk/client.ts @@ -14,6 +14,7 @@ import humps from "humps"; import { QuranAudio } from "./audio"; import { QuranChapters } from "./chapters"; +import { QuranHadithReferences } from "./hadith-references"; import { QuranJuzs } from "./juzs"; import { QuranResources } from "./resources"; import { QuranSearch } from "./search"; @@ -132,6 +133,7 @@ export class QuranClient { public readonly verses: QuranVerses; public readonly juzs: QuranJuzs; public readonly audio: QuranAudio; + public readonly hadithReferences: QuranHadithReferences; public readonly resources: QuranResources; public readonly search: QuranSearch; @@ -153,6 +155,7 @@ export class QuranClient { this.verses = new QuranVerses(this.fetcher); this.juzs = new QuranJuzs(this.fetcher); this.audio = new QuranAudio(this.fetcher); + this.hadithReferences = new QuranHadithReferences(this.fetcher); this.resources = new QuranResources(this.fetcher); this.search = new QuranSearch(this.fetcher); } diff --git a/packages/api/src/sdk/hadith-references.ts b/packages/api/src/sdk/hadith-references.ts new file mode 100644 index 0000000..0fd5467 --- /dev/null +++ b/packages/api/src/sdk/hadith-references.ts @@ -0,0 +1,90 @@ +import type { + BaseApiParams, + HadithCountWithinRangeResponse, + HadithReferencesByAyahResponse, + HadithsByAyahResponse, + QuranFetchClient, + VerseKey, +} from "@/types"; +import { isValidVerseKey } from "@/utils"; + +type GetHadithReferenceOptions = BaseApiParams; + +type GetHadithsByAyahOptions = BaseApiParams & { + page?: number; + limit?: number; +}; + +type CountHadithsWithinRangeOptions = BaseApiParams; + +/** + * Hadith reference API methods. + */ +export class QuranHadithReferences { + constructor(private fetcher: QuranFetchClient) {} + + /** + * Get ordered hadith reference records linked to a specific ayah. + * @param {VerseKey} key verse key in format "chapter:verse" (e.g., "12:12") + * @param {GetHadithReferenceOptions} options + * @example + * client.hadithReferences.findByAyah("12:12") + */ + async findByAyah( + key: VerseKey, + options?: GetHadithReferenceOptions, + ): Promise { + if (!isValidVerseKey(key)) throw new Error("Invalid verse key"); + + return this.fetcher.fetch( + `/content/api/v4/hadith_references/by_ayah/${key}`, + options, + ); + } + + /** + * Get paginated hadith payloads linked to a specific ayah. + * @param {VerseKey} key verse key in format "chapter:verse" (e.g., "12:12") + * @param {GetHadithsByAyahOptions} options + * @example + * client.hadithReferences.findHadithsByAyah("12:12", { limit: 4 }) + */ + async findHadithsByAyah( + key: VerseKey, + options?: GetHadithsByAyahOptions, + ): Promise { + if (!isValidVerseKey(key)) throw new Error("Invalid verse key"); + + return this.fetcher.fetch( + `/content/api/v4/hadith_references/by_ayah/${key}/hadiths`, + options, + ); + } + + /** + * Get a verse-key to hadith-count map within an inclusive ayah range. + * @param {VerseKey} from start verse key in format "chapter:verse" + * @param {VerseKey} to end verse key in format "chapter:verse" + * @param {CountHadithsWithinRangeOptions} options + * @example + * client.hadithReferences.countWithinRange("12:12", "12:13") + */ + async countWithinRange( + from: VerseKey, + to: VerseKey, + options?: CountHadithsWithinRangeOptions, + ): Promise { + if (!isValidVerseKey(from) || !isValidVerseKey(to)) { + throw new Error("Invalid verse key"); + } + + return this.fetcher.fetch( + "/content/api/v4/hadith_references/count_within_range", + { + ...options, + from, + to, + }, + ); + } +} diff --git a/packages/api/src/types/api/ChapterInfo.ts b/packages/api/src/types/api/ChapterInfo.ts index 88ae9b0..30db388 100644 --- a/packages/api/src/types/api/ChapterInfo.ts +++ b/packages/api/src/types/api/ChapterInfo.ts @@ -1,3 +1,5 @@ +import type { ChapterInfoResource } from "./Resources"; + export interface ChapterInfo { id: number; chapterId: number; @@ -5,4 +7,10 @@ export interface ChapterInfo { shortText: string; source: string; languageName?: string; + resourceId?: number; +} + +export interface ChapterInfoResponse { + chapterInfo: ChapterInfo | null; + resources?: ChapterInfoResource[]; } diff --git a/packages/api/src/types/api/HadithReference.ts b/packages/api/src/types/api/HadithReference.ts new file mode 100644 index 0000000..b1aa1e5 --- /dev/null +++ b/packages/api/src/types/api/HadithReference.ts @@ -0,0 +1,57 @@ +import type { VerseKey } from "../common/verse-key"; + +export interface HadithReference { + id: number; + collection: string; + hadithNumber: string; + ourHadithNumber: number; + arabicUrn: number; + englishUrn: number; + surahNumber: number; + ayahStartNumber: number; + ayahEndNumber: number; +} + +export interface HadithReferencesByAyahResponse { + verseKey: VerseKey; + verseNumber: number; + chapterNumber: number; + language: string; + direction: string; + hadithReferences: HadithReference[]; +} + +export interface HadithGrade { + gradedBy?: string; + grade?: string; +} + +export interface HadithContent { + lang: string; + chapterNumber: string; + chapterTitle: string; + body: string; + urn: number; + grades: HadithGrade[]; +} + +export interface Hadith { + urn: number; + collection: string; + bookNumber: string; + chapterId: string; + hadithNumber: string; + name: string; + hadith: HadithContent[]; +} + +export interface HadithsByAyahResponse { + hadiths: Hadith[]; + page: number; + limit: number; + hasMore: boolean; + language: string; + direction: string; +} + +export type HadithCountWithinRangeResponse = Record; diff --git a/packages/api/src/types/api/index.ts b/packages/api/src/types/api/index.ts index ced9d6d..d9283a0 100644 --- a/packages/api/src/types/api/index.ts +++ b/packages/api/src/types/api/index.ts @@ -4,6 +4,7 @@ export * from './AudioResponse'; export * from './Chapter'; export * from './ChapterInfo'; export * from './Footnote'; +export * from './HadithReference'; export * from './Reciter'; export * from './Segment'; export * from './Tafsir'; diff --git a/packages/api/test/chapters.test.ts b/packages/api/test/chapters.test.ts index ac76fc6..9e05cf8 100644 --- a/packages/api/test/chapters.test.ts +++ b/packages/api/test/chapters.test.ts @@ -29,6 +29,26 @@ describe("Chapters API", () => { it("should return chapter info for valid chapter ID", async () => { const response = await testClient.chapters.findInfoById("1"); expect(response).toBeDefined(); + expect(response?.resourceId).toBe(58); + }); + + it("should return the full chapter info response with resources", async () => { + const response = await testClient.chapters.findInfoResponseById("1", { + includeResources: true, + resourceId: 58, + }); + + expect(response.chapterInfo?.resourceId).toBe(58); + expect(response.resources).toBeInstanceOf(Array); + expect(response.resources?.[0]?.id).toBe(58); + }); + + it("should support null chapter info for missing resources", async () => { + const response = await testClient.chapters.findInfoResponseById("1", { + resourceId: "missing-resource", + }); + + expect(response.chapterInfo).toBeNull(); }); it("should throw error for invalid chapter ID", async () => { diff --git a/packages/api/test/contracts.test.ts b/packages/api/test/contracts.test.ts index 84efba6..ec23c67 100644 --- a/packages/api/test/contracts.test.ts +++ b/packages/api/test/contracts.test.ts @@ -1,9 +1,26 @@ import { describe, expect, it } from "vitest"; -import { getOperationByName, operationCatalog } from "../src/generated/contracts"; +import { + getOperationByName, + operationCatalog, +} from "../src/generated/contracts"; const representativeOperations = [ ["content", "v4", "getChapter", "get", "/chapters/{id}"], + [ + "content", + "v4", + "hadithReferencesByAyah", + "get", + "/hadith_references/by_ayah/{ayah_key}", + ], + [ + "content", + "v4", + "listSurahTafsirs", + "get", + "/tafsirs/{resource_id}/by_chapter/{chapter_number}", + ], ["search", "v1", "searchControllerSearch", "get", "/v1/search"], ["auth", "v1", "getV1NotesByNoteId", "get", "/v1/notes/{noteId}"], ["quranReflect", "v1", "postsControllerFeed", "get", "/v1/posts/feed"], @@ -14,20 +31,22 @@ describe("operation contracts", () => { it.each(representativeOperations)( "loads %s %s %s from the synced OpenAPI contracts", (service, version, operationName, method, path) => { - expect(getOperationByName(service, version, operationName)).toMatchObject({ - method, - operationName, - path, - service, - version, - }); + expect(getOperationByName(service, version, operationName)).toMatchObject( + { + method, + operationName, + path, + service, + version, + }, + ); }, ); it("normalizes colon params into curly-brace params", () => { - const noteOperation = Object.values(operationCatalog.auth.v1.operations).find( - (operation) => operation.path.includes("/v1/notes/{noteId}"), - ); + const noteOperation = Object.values( + operationCatalog.auth.v1.operations, + ).find((operation) => operation.path.includes("/v1/notes/{noteId}")); expect(noteOperation).toBeDefined(); }); diff --git a/packages/api/test/hadith-references.test.ts b/packages/api/test/hadith-references.test.ts new file mode 100644 index 0000000..ce91110 --- /dev/null +++ b/packages/api/test/hadith-references.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it } from "vitest"; + +import { testClient } from "./test-client"; + +describe("Hadith References API", () => { + describe("findByAyah()", () => { + it("should return hadith references for a valid ayah key", async () => { + const response = await testClient.hadithReferences.findByAyah("12:12"); + + expect(response.verseKey).toBe("12:12"); + expect(response.hadithReferences).toHaveLength(2); + expect(response.hadithReferences[0]).toMatchObject({ + collection: "bukhari", + hadithNumber: "1", + }); + }); + + it("should throw for invalid ayah keys", async () => { + await expect( + // @ts-expect-error - invalid verse key + testClient.hadithReferences.findByAyah("0:1"), + ).rejects.toThrowError("Invalid verse key"); + }); + }); + + describe("findHadithsByAyah()", () => { + it("should return paginated hadith payloads for a valid ayah key", async () => { + const response = await testClient.hadithReferences.findHadithsByAyah( + "12:12", + { limit: 4 }, + ); + + expect(response.hadiths[0]?.collection).toBe("bukhari"); + expect(response.hadiths[0]?.hadith[0]?.grades[0]?.gradedBy).toBe( + "Ahmad Muhammad Shakir", + ); + expect(response.hasMore).toBe(true); + }); + }); + + describe("countWithinRange()", () => { + it("should return hadith counts for a verse range", async () => { + await expect( + testClient.hadithReferences.countWithinRange("12:12", "12:13"), + ).resolves.toEqual({ + "12:12": 2, + "12:13": 2, + }); + }); + + it("should expose the same methods through content.v4", async () => { + const response = + await testClient.content.v4.hadithReferences.byAyah("12:12"); + + expect(response.hadithReferences[1]?.collection).toBe("muslim"); + }); + }); +}); From db193c641261b254616c0c566e40f42e23089f79 Mon Sep 17 00:00:00 2001 From: Basit Minhas Date: Mon, 4 May 2026 09:30:54 +0500 Subject: [PATCH 2/2] Cover chapter info resource slugs --- packages/api/mocks/handlers.ts | 8 +++++++- packages/api/test/chapters.test.ts | 8 ++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/api/mocks/handlers.ts b/packages/api/mocks/handlers.ts index a660fbf..f29b051 100644 --- a/packages/api/mocks/handlers.ts +++ b/packages/api/mocks/handlers.ts @@ -667,6 +667,12 @@ export const handlers = [ const includeResources = url.searchParams.get("include_resources") === "true"; const resourceId = url.searchParams.get("resource_id"); + const chapterInfoResourceId = + resourceId === "en-tafsir-ibn-ashur" + ? 167 + : resourceId + ? Number(resourceId) || 58 + : 58; const chapterInfo = resourceId === "missing-resource" ? null @@ -674,7 +680,7 @@ export const handlers = [ id: 1, chapter_id: 1, language_name: "english", - resource_id: resourceId ? Number(resourceId) || 58 : 58, + resource_id: chapterInfoResourceId, short_text: "This Surah is named Al-Fatihah because of its subject-matter. Fatihah is that which opens a subject or a book or any other thing. In other words, Al-Fatihah is a sort of preface.", source: diff --git a/packages/api/test/chapters.test.ts b/packages/api/test/chapters.test.ts index 9e05cf8..d381ef6 100644 --- a/packages/api/test/chapters.test.ts +++ b/packages/api/test/chapters.test.ts @@ -43,6 +43,14 @@ describe("Chapters API", () => { expect(response.resources?.[0]?.id).toBe(58); }); + it("should support resource slugs when selecting chapter info", async () => { + const response = await testClient.chapters.findInfoResponseById("1", { + resourceId: "en-tafsir-ibn-ashur", + }); + + expect(response.chapterInfo?.resourceId).toBe(167); + }); + it("should support null chapter info for missing resources", async () => { const response = await testClient.chapters.findInfoResponseById("1", { resourceId: "missing-resource",