Skip to content

Commit

Permalink
Merge pull request #12 from quran/dev
Browse files Browse the repository at this point in the history
v1.7.0
  • Loading branch information
ahmedriad1 authored Apr 9, 2023
2 parents a5b5f84 + 0b8c657 commit 201dc39
Show file tree
Hide file tree
Showing 17 changed files with 294 additions and 278 deletions.
3 changes: 2 additions & 1 deletion docs/src/pages/_meta.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{
"index": "Introduction",
"techniques": "Techniques",
"chapters": "Chapters",
"verses": "Verses",
"search": "Search",
"juzs": "Juzs",
"audio": "Audio",
"resources": "Resources",
"utils": "Utilities"
}
}
39 changes: 39 additions & 0 deletions docs/src/pages/techniques.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
## Custom fetcher

By default, all functions that interact with the [Quran.com API](https://quran.api-docs.io/v4) use the global `fetch` function.

You can override this by passing a custom fetcher (as `fetchFn`) to the options object of any method.

import { Callout } from 'nextra-theme-docs';

<Callout>
Note that the `fetchFn` accepts a string url and must return a promise with
the JSON response.
</Callout>

### Examples

#### Axios

```ts
import axios from 'axios';

const chapters = await quran.v4.chapters.findAll({
fetchFn: (url) => axios.get(url).then((res) => res.data),
});
```

---

#### Node-fetch

```ts
import fetch from 'node-fetch';

const chapters = await quran.v4.chapters.findAll({
fetchFn: async (url) => {
const response = await fetch(url);
return response.json();
},
});
```
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
"analyze": "size-limit --why"
},
"dependencies": {
"cross-fetch": "^3.1.5",
"humps": "^2.0.1"
},
"devDependencies": {
"cross-fetch": "^3.1.5",
"@size-limit/preset-small-lib": "^7.0.8",
"@types/humps": "^2.0.1",
"@typescript-eslint/eslint-plugin": "^5.42.0",
Expand Down Expand Up @@ -62,4 +62,4 @@
"engines": {
"node": ">=12"
}
}
}
27 changes: 18 additions & 9 deletions src/sdk/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
import {
ChapterId,
HizbNumber,
JuzNumber,
PageNumber,
RubNumber,
VerseKey,
} from '../types';

// this maps chapterNumber to verseCount
export const versesMapping = {
'1': 7,
Expand Down Expand Up @@ -126,7 +135,7 @@ export const versesMapping = {
isValidChapterId('-1') // false
isValidChapterId('200') // false
*/
const isValidChapterId = (id: string | number) => {
const isValidChapterId = (id: string | number): id is ChapterId => {
const parsedId = typeof id === 'number' ? id : Number(id);
if (!parsedId || parsedId <= 0 || parsedId > 114) return false;
return true;
Expand All @@ -142,7 +151,7 @@ const isValidChapterId = (id: string | number) => {
isValidJuz('-1') // false
isValidJuz('200') // false
*/
const isValidJuz = (juz: string | number) => {
const isValidJuz = (juz: string | number): juz is JuzNumber => {
const parsedJuz = typeof juz === 'number' ? juz : Number(juz);
if (!parsedJuz || parsedJuz <= 0 || parsedJuz > 30) return false;
return true;
Expand All @@ -158,7 +167,7 @@ const isValidJuz = (juz: string | number) => {
isValidRub('-1') // false
isValidRub('300') // false
*/
const isValidRub = (rub: string | number) => {
const isValidRub = (rub: string | number): rub is RubNumber => {
const parsedRub = typeof rub === 'number' ? rub : Number(rub);
if (!parsedRub || parsedRub <= 0 || parsedRub > 240) return false;
return true;
Expand All @@ -174,7 +183,7 @@ const isValidRub = (rub: string | number) => {
isValidHizb('-1') // false
isValidHizb('200') // false
*/
const isValidHizb = (hizb: string | number) => {
const isValidHizb = (hizb: string | number): hizb is HizbNumber => {
const parsedHizb = typeof hizb === 'number' ? hizb : Number(hizb);
if (!parsedHizb || parsedHizb <= 0 || parsedHizb > 60) return false;
return true;
Expand All @@ -190,7 +199,7 @@ const isValidHizb = (hizb: string | number) => {
isValidQuranPage('-1') // false
isValidQuranPage('1000') // false
*/
const isValidQuranPage = (page: string | number) => {
const isValidQuranPage = (page: string | number): page is PageNumber => {
const parsedPage = typeof page === 'number' ? page : Number(page);
if (!parsedPage || parsedPage <= 0 || parsedPage > 604) return false;
return true;
Expand All @@ -206,19 +215,19 @@ const isValidQuranPage = (page: string | number) => {
isValidVerseKey('1:-') // false
isValidVerseKey('1_1') // false
*/
const isValidVerseKey = (key: string) => {
const isValidVerseKey = (key: string): key is VerseKey => {
const [chapterId, verseId] = key.trim().split(':');
if (!chapterId || !verseId || !isValidChapterId(chapterId)) return false;

const parsedVerse = Number(verseId);
const verseCount = (versesMapping as any)[chapterId];
const verseCount = (versesMapping as Record<string, number>)[chapterId];
if (!parsedVerse || parsedVerse <= 0 || parsedVerse > verseCount)
return false;

return true;
};

const Utils = {
const utils = {
isValidChapterId,
isValidJuz,
isValidRub,
Expand All @@ -227,4 +236,4 @@ const Utils = {
isValidVerseKey,
};

export default Utils;
export default utils;
65 changes: 58 additions & 7 deletions src/sdk/v4/_fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fetch from 'cross-fetch';
import { camelizeKeys, decamelizeKeys } from 'humps';
import stringify from '../../utils/qs-stringify';
import { camelizeKeys, decamelize, decamelizeKeys } from 'humps';
import { FetchFn } from '../../types';
import { BaseApiOptions } from '../../types/BaseApiOptions';
import { removeBeginningSlash } from '../../utils/misc';

export const API_BASE_URL = 'https://api.quran.com/api/v4/';
Expand All @@ -9,20 +9,71 @@ export const makeUrl = (url: string, params?: Record<string, unknown>) => {
const baseUrl = `${API_BASE_URL}${removeBeginningSlash(url)}`;
if (!params) return baseUrl;

const decamelizedKeys = decamelizeKeys(params);
const paramsString = stringify(decamelizedKeys);
const paramsWithDecamelizedKeys = decamelizeKeys(params) as Record<
string,
string
>;
const paramsString = new URLSearchParams(
Object.entries(paramsWithDecamelizedKeys).filter(
([, value]) => value !== undefined
)
).toString();
if (!paramsString) return baseUrl;

return `${baseUrl}?${paramsString}`;
};

export const fetcher = async <T extends object>(
url: string,
params: Record<string, unknown> = {}
params: Record<string, unknown> = {},
fetchFn?: FetchFn
) => {
if (fetchFn) {
const json = await fetchFn(makeUrl(url, params));
return camelizeKeys(json) as T;
}

if (typeof fetch === 'undefined') {
throw new Error('fetch is not defined');
}

// if there is no fetchFn, we use the global fetch
const res = await fetch(makeUrl(url, params));
if (res.status >= 400) throw new Error(`${res.status} ${res.statusText}`);

if (!res.ok || res.status >= 400) {
throw new Error(`${res.status} ${res.statusText}`);
}

const json = await res.json();

return camelizeKeys(json) as T;
};

type MergeApiOptionsObject = Pick<BaseApiOptions, 'fetchFn'> & {
fields?: Record<string, boolean>;
} & Record<string, unknown>;

export const mergeApiOptions = (
options: MergeApiOptionsObject = {},
defaultOptions: Record<string, unknown> = {}
) => {
// we can set it to undefined because `makeUrl` will filter it out
if (options.fetchFn) options.fetchFn = undefined;

const final: Record<string, unknown> = {
...defaultOptions,
...options,
};

if (final.fields) {
const fields: string[] = [];
Object.entries(final.fields).forEach(([key, value]) => {
if (value) fields.push(decamelize(key));
});

// convert `fields` to a string sperated by commas
final.fields = fields.join(',');
}

return final;
};
Loading

1 comment on commit 201dc39

@vercel
Copy link

@vercel vercel bot commented on 201dc39 Apr 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

api – ./docs

quranjs.vercel.app
api-quranjs.vercel.app
api-git-master-quranjs.vercel.app
quranjs.com
www.quranjs.com

Please sign in to comment.