diff --git a/src/index.common.ts b/src/index.common.ts index ff154ba..1141b43 100644 --- a/src/index.common.ts +++ b/src/index.common.ts @@ -14,7 +14,7 @@ export { export * from "./ranking-history.js"; export * from "./params.js"; -export type { NarouParams } from "./narou.js"; +export type { ExecuteOptions, NarouParams } from "./narou.js"; export * from "./narou-search-results.js"; export type * from "./narou-ranking-results.js"; export * from "./search-builder.js"; diff --git a/src/index.ts b/src/index.ts index f298417..c4a829f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -77,7 +77,7 @@ export async function rankingHistory( if (Array.isArray(result)) { return result.map(formatRankingHistory); } else { - throw new Error(result); + throw result; } } diff --git a/src/narou-fetch.ts b/src/narou-fetch.ts index 9c162b5..574a319 100644 --- a/src/narou-fetch.ts +++ b/src/narou-fetch.ts @@ -1,6 +1,6 @@ import { unzipp } from "./util/unzipp.js"; import NarouNovel from "./narou.js"; -import type { NarouParams } from "./narou.js"; +import type { ExecuteOptions, NarouParams } from "./narou.js"; type Fetch = typeof fetch; @@ -18,7 +18,8 @@ export default class NarouNovelFetch extends NarouNovel { protected async execute( params: NarouParams, - endpoint: string + endpoint: string, + options?: ExecuteOptions ): Promise { const query = { ...params, out: "json" }; @@ -36,7 +37,7 @@ export default class NarouNovelFetch extends NarouNovel { } }); - const res = await (this.fetch ?? fetch)(url); + const res = await (this.fetch ?? fetch)(url, options?.fetchOptions); if (!query.gzip) { return (await res.json()) as T; diff --git a/src/narou-jsonp.ts b/src/narou-jsonp.ts index 8f4ad01..04666b2 100644 --- a/src/narou-jsonp.ts +++ b/src/narou-jsonp.ts @@ -1,5 +1,5 @@ import NarouNovel from "./narou.js"; -import type { NarouParams } from "./narou.js"; +import type { ExecuteOptions, NarouParams } from "./narou.js"; import { jsonp } from "./util/jsonp.js"; /** @@ -8,7 +8,8 @@ import { jsonp } from "./util/jsonp.js"; export default class NarouNovelJsonp extends NarouNovel { protected async execute( params: NarouParams, - endpoint: string + endpoint: string, + _options?: ExecuteOptions ): Promise { const query = { ...params, out: "jsonp" }; query.gzip = 0; diff --git a/src/narou.ts b/src/narou.ts index 5ff03ac..ac05b66 100644 --- a/src/narou.ts +++ b/src/narou.ts @@ -21,6 +21,10 @@ export type NarouParams = | RankingHistoryParams | UserSearchParams; +export type ExecuteOptions = { + fetchOptions?: RequestInit; +}; + /** * なろう小説APIへのリクエストを実行する * @class NarouNovel @@ -35,7 +39,8 @@ export default abstract class NarouNovel { */ protected abstract execute( params: NarouParams, - endpoint: string + endpoint: string, + options?: ExecuteOptions ): Promise; /** @@ -46,9 +51,13 @@ export default abstract class NarouNovel { */ protected async executeSearch( params: SearchParams, - endpoint = "https://api.syosetu.com/novelapi/api/" + endpoint = "https://api.syosetu.com/novelapi/api/", + options?: ExecuteOptions ): Promise> { - return new NarouSearchResults(await this.execute(params, endpoint), params); + return new NarouSearchResults( + await this.execute(params, endpoint, options), + params + ); } /** @@ -58,11 +67,13 @@ export default abstract class NarouNovel { * @see https://dev.syosetu.com/man/api/ */ async executeNovel( - params: SearchParams + params: SearchParams, + options?: ExecuteOptions ): Promise> { return await this.executeSearch( params, - "https://api.syosetu.com/novelapi/api/" + "https://api.syosetu.com/novelapi/api/", + options ); } @@ -73,11 +84,13 @@ export default abstract class NarouNovel { * @see https://dev.syosetu.com/xman/api/ */ async executeNovel18( - params: SearchParams + params: SearchParams, + options?: ExecuteOptions ): Promise> { return await this.executeSearch( params, - "https://api.syosetu.com/novel18api/api/" + "https://api.syosetu.com/novel18api/api/", + options ); } @@ -87,8 +100,15 @@ export default abstract class NarouNovel { * @returns ランキング結果 * @see https://dev.syosetu.com/man/rankapi/ */ - async executeRanking(params: RankingParams): Promise { - return await this.execute(params, "https://api.syosetu.com/rank/rankget/"); + async executeRanking( + params: RankingParams, + options?: ExecuteOptions + ): Promise { + return await this.execute( + params, + "https://api.syosetu.com/rank/rankget/", + options + ); } /** @@ -98,9 +118,14 @@ export default abstract class NarouNovel { * @see https://dev.syosetu.com/man/rankinapi/ */ async executeRankingHistory( - params: RankingHistoryParams + params: RankingHistoryParams, + options?: ExecuteOptions ): Promise { - return await this.execute(params, "https://api.syosetu.com/rank/rankin/"); + return await this.execute( + params, + "https://api.syosetu.com/rank/rankin/", + options + ); } /** @@ -110,10 +135,15 @@ export default abstract class NarouNovel { * @see https://dev.syosetu.com/man/userapi/ */ async executeUserSearch( - params: UserSearchParams + params: UserSearchParams, + options?: ExecuteOptions ): Promise> { return new NarouSearchResults( - await this.execute(params, "https://api.syosetu.com/userapi/api/"), + await this.execute( + params, + "https://api.syosetu.com/userapi/api/", + options + ), params ); } diff --git a/src/ranking.ts b/src/ranking.ts index 21ec693..5ae64f3 100644 --- a/src/ranking.ts +++ b/src/ranking.ts @@ -11,6 +11,7 @@ import { Fields, } from "./params.js"; import type NarouNovel from "./narou.js"; +import type { ExecuteOptions } from "./narou.js"; import type { SearchResultFields } from "./narou-search-results.js"; import { addDays, formatDate } from "./util/date.js"; @@ -111,18 +112,18 @@ export default class RankingBuilder { * @returns {Promise} ランキング結果の配列 * @see https://dev.syosetu.com/man/rankapi/#output */ - execute(): Promise { + execute(options?: ExecuteOptions): Promise { const date = formatDate(this.date$); this.set({ rtype: `${date}-${this.type$}` }); - return this.api.executeRanking(this.params as RankingParams); + return this.api.executeRanking(this.params as RankingParams, options); } /** * ランキングAPIを実行し、取得したNコードを元になろう小説APIで詳細情報を取得して結合します。 */ - async executeWithFields(): Promise< - RankingResult[] - >; + async executeWithFields( + options?: ExecuteOptions + ): Promise[]>; /** * ランキングAPIを実行し、取得したNコードを元になろう小説APIで詳細情報を取得して結合します。 * @@ -131,7 +132,8 @@ export default class RankingBuilder { * @returns {Promise>[]>} 詳細情報を含むランキング結果の配列 */ async executeWithFields( - fields: TFields | TFields[] + fields: TFields | TFields[], + options?: ExecuteOptions ): Promise>[]>; /** * ランキングAPIを実行し、取得したNコードを元になろう小説APIで詳細情報を取得して結合します。 @@ -141,7 +143,8 @@ export default class RankingBuilder { */ async executeWithFields( fields: never[], - opt: OptionalFields | OptionalFields[] + opt: OptionalFields | OptionalFields[], + options?: ExecuteOptions ): Promise[]>; /** * ランキングAPIを実行し、取得したNコードを元になろう小説APIで詳細情報を取得して結合します。 @@ -169,9 +172,10 @@ export default class RankingBuilder { TOpt extends OptionalFields | undefined = undefined >( fields: TFields | TFields[] = [], - opt?: TOpt + opt?: TOpt, + options?: ExecuteOptions ): Promise>[]> { - const ranking = await this.execute(); + const ranking = await this.execute(options); const fields$ = Array.isArray(fields) ? fields.length == 0 ? [] @@ -186,7 +190,7 @@ export default class RankingBuilder { } builder.ncode(rankingNcodes); builder.limit(ranking.length); - const result = await builder.execute(); + const result = await builder.execute(options); return ranking.map< RankingResult< diff --git a/src/search-builder-r18.ts b/src/search-builder-r18.ts index 02e961c..b2efd42 100644 --- a/src/search-builder-r18.ts +++ b/src/search-builder-r18.ts @@ -5,6 +5,7 @@ import type { SearchResultR18Fields, SearchResultOptionalFields, } from "./narou-search-results.js"; +import type { ExecuteOptions } from "./narou.js"; import type { R18Site, SearchResultFieldNames, @@ -30,8 +31,10 @@ export default class SearchBuilderR18< * @override * @returns {Promise} 検索結果 */ - execute(): Promise> { - return this.api.executeNovel18(this.params); + execute( + options?: ExecuteOptions + ): Promise> { + return this.api.executeNovel18(this.params, options); } /** diff --git a/src/search-builder.ts b/src/search-builder.ts index 30f871d..14f4e76 100644 --- a/src/search-builder.ts +++ b/src/search-builder.ts @@ -1,4 +1,5 @@ import type NarouNovel from "./narou.js"; +import type { ExecuteOptions } from "./narou.js"; import type { NarouSearchResult, SearchResultFields, @@ -474,8 +475,10 @@ export abstract class NovelSearchBuilderBase< * なろう小説APIへの検索リクエストを実行する * @returns {Promise} 検索結果 */ - execute(): Promise> { - return this.api.executeNovel(this.params); + execute(options?: ExecuteOptions): Promise< + NarouSearchResults + > { + return this.api.executeNovel(this.params, options); } } diff --git a/src/user-search.ts b/src/user-search.ts index eb607d9..36ad4f6 100644 --- a/src/user-search.ts +++ b/src/user-search.ts @@ -5,6 +5,7 @@ import type { } from "./narou-search-results.js"; import type { UserFields, UserOrder, UserSearchParams } from "./params.js"; import { SearchBuilderBase } from "./search-builder.js"; +import type { ExecuteOptions } from "./narou.js"; /** * なろうユーザ検索API @@ -104,7 +105,12 @@ export default class UserSearchBuilder< * なろう小説APIへのリクエストを実行する * @returns ランキング */ - execute(): Promise> { - return this.api.executeUserSearch(this.params as UserSearchParams); + execute( + options?: ExecuteOptions + ): Promise> { + return this.api.executeUserSearch( + this.params as UserSearchParams, + options + ); } } diff --git a/test/narou-fetch.test.ts b/test/narou-fetch.test.ts index f5578c4..5f6654f 100644 --- a/test/narou-fetch.test.ts +++ b/test/narou-fetch.test.ts @@ -95,6 +95,26 @@ describe('NarouNovelFetch', () => { expect(result).toEqual(mockData); }); + it('should pass fetch options to fetch implementation', async () => { + const customFetchMock = vi.fn().mockResolvedValue({ + json: vi.fn().mockResolvedValue(mockData) + }); + + const narouFetch = new NarouNovelFetch(customFetchMock); + + // @ts-expect-error - Accessing protected method for testing + await narouFetch.execute( + { gzip: 0 }, + 'https://api.example.com', + { fetchOptions: { method: 'POST', headers: { 'X-Test': '1' } } } + ); + + expect(customFetchMock).toHaveBeenCalledWith( + new URL('https://api.example.com/?out=json'), + { method: 'POST', headers: { 'X-Test': '1' } } + ); + }); + it('should set gzip to 5 when undefined', async () => { // URLパラメータをキャプチャするモック const requestSpy = vi.fn();