naverseries-sdk 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 re0nyaa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,158 @@
1
+ # naverseries-sdk
2
+
3
+ > 네이버 시리즈(series.naver.com) 비공식 TypeScript SDK
4
+
5
+ [![npm version](https://img.shields.io/npm/v/naverseries-sdk.svg)](https://www.npmjs.com/package/naverseries-sdk)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+
8
+ - **Undici** 커넥션 풀링 기반 고성능 HTTP
9
+ - **Full-Jitter** 지수백오프 자동 재시도
10
+ - **LRU + TTL** 인메모리 캐시
11
+ - 비동기 제너레이터 **스트리밍 검색** (`searchStream`)
12
+ - 인터셉터 지원 (`onRequest / onResponse / onError / onRetry`)
13
+ - 완전한 **TypeScript** 타입 정의 제공
14
+
15
+ ---
16
+
17
+ ## 설치
18
+
19
+ ```bash
20
+ npm install naverseries-sdk
21
+ # or
22
+ pnpm add naverseries-sdk
23
+ ```
24
+
25
+ ---
26
+
27
+ ## 빠른 시작
28
+
29
+ ```typescript
30
+ import NaverSeriesClient from 'naverseries-sdk'
31
+
32
+ const client = new NaverSeriesClient({
33
+ cache: true, // 인메모리 캐시 활성화
34
+ cacheTtlMs: 60000, // 캐시 TTL 60초
35
+ maxRetries: 3, // 최대 재시도 횟수
36
+ timeout: 10000, // 요청 타임아웃 10초
37
+ })
38
+
39
+ // 검색
40
+ const result = await client.search({ keyword: '환생', page: 1 })
41
+ console.log(result.items[0].title) // "66666년 만에 환생한 흑마법사 [독점]"
42
+
43
+ // TOP 100
44
+ const top100 = await client.getTop100()
45
+ console.log(top100[0].rank, top100[0].title)
46
+
47
+ // 작품 상세
48
+ const detail = await client.getNovelDetail('5553059')
49
+ console.log(detail.title, detail.authorName)
50
+
51
+ await client.close()
52
+ ```
53
+
54
+ ---
55
+
56
+ ## API
57
+
58
+ ### `new NaverSeriesClient(options?)`
59
+
60
+ | 옵션 | 타입 | 기본값 | 설명 |
61
+ |---|---|---|---|
62
+ | `cache` | `boolean \| CacheStore` | `false` | 캐시 활성화 또는 커스텀 캐시 |
63
+ | `cacheTtlMs` | `number` | `60000` | 캐시 TTL (ms) |
64
+ | `timeout` | `number` | `10000` | 요청 타임아웃 (ms) |
65
+ | `maxRetries` | `number` | `3` | 최대 재시도 횟수 |
66
+ | `userAgent` | `string` | Chrome UA | 커스텀 User-Agent |
67
+ | `poolOptions` | `ConnectionPoolOptions` | — | Undici 커넥션 풀 설정 |
68
+ | `interceptors` | `Interceptors` | — | 요청/응답 인터셉터 |
69
+ | `logger` | `Logger` | — | 커스텀 로거 |
70
+
71
+ ### 메서드
72
+
73
+ | 메서드 | 반환 | 설명 |
74
+ |---|---|---|
75
+ | `search(options)` | `SearchResult` | 키워드 검색 (page, type 지원) |
76
+ | `searchStream(options)` | `AsyncIterableIterator` | 스트리밍 검색 제너레이터 |
77
+ | `getTop100()` | `RankingNovelItem[]` | 실시간 TOP 100 랭킹 |
78
+ | `getRankingByGenre(code)` | `RankingNovelItem[]` | 장르별 랭킹 |
79
+ | `getNovelDetail(novelId)` | `NovelDetail` | 작품 상세 정보 |
80
+ | `getEpisodes(novelId)` | `EpisodeListResult` | 에피소드 목록 |
81
+ | `getGenres()` | `CategoryItem[]` | 장르 카테고리 목록 |
82
+ | `getNovelByName(keyword)` | `{ searchItem, detail }` | 이름 검색 + 상세 일괄 조회 |
83
+ | `getCacheStats()` | `CacheStats \| undefined` | 캐시 통계 |
84
+ | `clearCache()` | `void` | 캐시 초기화 |
85
+ | `close()` | `Promise<void>` | 커넥션 풀 종료 |
86
+
87
+ ---
88
+
89
+ ## 사용 예제
90
+
91
+ ### 스트리밍 검색
92
+
93
+ ```typescript
94
+ for await (const novel of client.searchStream({ keyword: '로판', maxPages: 3 })) {
95
+ console.log(novel.title, novel.authorName)
96
+ }
97
+ ```
98
+
99
+ ### 이름으로 바로 상세 조회
100
+
101
+ ```typescript
102
+ const { searchItem, detail } = await client.getNovelByName('전지적 독자 시점')
103
+ console.log(detail.title) // 전지적 독자 시점
104
+ ```
105
+
106
+ ### 인터셉터
107
+
108
+ ```typescript
109
+ const client = new NaverSeriesClient({
110
+ interceptors: {
111
+ onRequest: ({ url }) => console.log('→', url),
112
+ onResponse: ({ url, latencyMs }) => console.log('←', url, latencyMs + 'ms'),
113
+ onError: ({ error }) => console.error(error),
114
+ }
115
+ })
116
+ ```
117
+
118
+ ### 에러 핸들링
119
+
120
+ ```typescript
121
+ import {
122
+ NaverSeriesNotFoundError,
123
+ NaverSeriesRateLimitError,
124
+ NaverSeriesTimeoutError,
125
+ } from 'naverseries-sdk/errors'
126
+
127
+ try {
128
+ const detail = await client.getNovelDetail('invalid-id')
129
+ } catch (err) {
130
+ if (err instanceof NaverSeriesNotFoundError) {
131
+ console.log('작품 없음')
132
+ } else if (err instanceof NaverSeriesRateLimitError) {
133
+ console.log('요청 한도 초과, retryAfter:', err.retryAfterMs)
134
+ }
135
+ }
136
+ ```
137
+
138
+ ### 서브패스 임포트
139
+
140
+ ```typescript
141
+ import { NaverSeriesClient } from 'naverseries-sdk/client'
142
+ import type { NovelDetail, RankingNovelItem } from 'naverseries-sdk/types'
143
+ import { NaverSeriesNotFoundError } from 'naverseries-sdk/errors'
144
+ import { MemoryTtlCache } from 'naverseries-sdk/cache'
145
+ ```
146
+
147
+ ---
148
+
149
+ ## 주의사항
150
+
151
+ 이 SDK는 **비공식**이며 네이버 시리즈의 HTML 구조 변경 시 동작하지 않을 수 있습니다.
152
+ 상업적 용도 사용 시 [네이버 이용약관](https://www.naver.com/rules/service.html)을 반드시 확인하세요.
153
+
154
+ ---
155
+
156
+ ## License
157
+
158
+ [MIT](LICENSE)
@@ -0,0 +1,24 @@
1
+ /**
2
+ * LRU + TTL 인메모리 캐시 구현체
3
+ */
4
+ export interface CacheStats {
5
+ hits: number;
6
+ misses: number;
7
+ hitRatio: number;
8
+ evictions: number;
9
+ size: number;
10
+ }
11
+ export declare class MemoryTtlCache {
12
+ private store;
13
+ private stats;
14
+ private defaultTtlMs;
15
+ constructor(options?: {
16
+ defaultTtlMs?: number;
17
+ });
18
+ get<T>(key: string): T | undefined;
19
+ set<T>(key: string, value: T, ttlMs?: number): void;
20
+ delete(key: string): void;
21
+ clear(): void;
22
+ getStats(): CacheStats;
23
+ }
24
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,UAAU;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;CACf;AAOD,qBAAa,cAAc;IACvB,OAAO,CAAC,KAAK,CAAqC;IAClD,OAAO,CAAC,KAAK,CAAuC;IACpD,OAAO,CAAC,YAAY,CAAQ;gBAEhB,OAAO,GAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAO;IAInD,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAgBlC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI;IAOnD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIzB,KAAK,IAAI,IAAI;IAKb,QAAQ,IAAI,UAAU;CAUzB"}
@@ -0,0 +1,47 @@
1
+ export class MemoryTtlCache {
2
+ store = new Map();
3
+ stats = { hits: 0, misses: 0, evictions: 0 };
4
+ defaultTtlMs;
5
+ constructor(options = {}) {
6
+ this.defaultTtlMs = options.defaultTtlMs ?? 60000;
7
+ }
8
+ get(key) {
9
+ const entry = this.store.get(key);
10
+ if (!entry) {
11
+ this.stats.misses++;
12
+ return undefined;
13
+ }
14
+ if (Date.now() > entry.expiresAt) {
15
+ this.store.delete(key);
16
+ this.stats.misses++;
17
+ this.stats.evictions++;
18
+ return undefined;
19
+ }
20
+ this.stats.hits++;
21
+ return entry.value;
22
+ }
23
+ set(key, value, ttlMs) {
24
+ this.store.set(key, {
25
+ value,
26
+ expiresAt: Date.now() + (ttlMs ?? this.defaultTtlMs),
27
+ });
28
+ }
29
+ delete(key) {
30
+ this.store.delete(key);
31
+ }
32
+ clear() {
33
+ this.store.clear();
34
+ this.stats = { hits: 0, misses: 0, evictions: 0 };
35
+ }
36
+ getStats() {
37
+ const total = this.stats.hits + this.stats.misses;
38
+ return {
39
+ hits: this.stats.hits,
40
+ misses: this.stats.misses,
41
+ hitRatio: total === 0 ? 0 : this.stats.hits / total,
42
+ evictions: this.stats.evictions,
43
+ size: this.store.size,
44
+ };
45
+ }
46
+ }
47
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"AAgBA,MAAM,OAAO,cAAc;IACf,KAAK,GAAG,IAAI,GAAG,EAA2B,CAAA;IAC1C,KAAK,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAA;IAC5C,YAAY,CAAQ;IAE5B,YAAY,UAAqC,EAAE;QAC/C,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,KAAK,CAAA;IACrD,CAAC;IAED,GAAG,CAAI,GAAW;QACd,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACjC,IAAI,CAAC,KAAK,EAAE,CAAC;YACT,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAA;YACnB,OAAO,SAAS,CAAA;QACpB,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;YAC/B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACtB,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAA;YACnB,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAA;YACtB,OAAO,SAAS,CAAA;QACpB,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAA;QACjB,OAAO,KAAK,CAAC,KAAU,CAAA;IAC3B,CAAC;IAED,GAAG,CAAI,GAAW,EAAE,KAAQ,EAAE,KAAc;QACxC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE;YAChB,KAAK;YACL,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC;SACvD,CAAC,CAAA;IACN,CAAC;IAED,MAAM,CAAC,GAAW;QACd,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAC1B,CAAC;IAED,KAAK;QACD,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;QAClB,IAAI,CAAC,KAAK,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAA;IACrD,CAAC;IAED,QAAQ;QACJ,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAA;QACjD,OAAO;YACH,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI;YACrB,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM;YACzB,QAAQ,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK;YACnD,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS;YAC/B,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI;SACxB,CAAA;IACL,CAAC;CACJ"}
@@ -0,0 +1,102 @@
1
+ import type { CacheStats } from "./cache.js";
2
+ import type { CategoryItem, EpisodeListResult, NaverSeriesClientOptions, NovelDetail, NovelSearchResultItem, RankingNovelItem, SearchOptions, SearchResult, SearchStreamOptions } from "./types.js";
3
+ export interface RequestOptions {
4
+ timeout?: number;
5
+ maxRetries?: number;
6
+ skipCache?: boolean;
7
+ cacheTtlMs?: number;
8
+ headers?: Record<string, string>;
9
+ signal?: AbortSignal;
10
+ }
11
+ /**
12
+ * 네이버 시리즈(series.naver.com) 비공식 TypeScript 클라이언트
13
+ *
14
+ * - Undici 커넥션 풀링
15
+ * - Full-Jitter 지수백오프 재시도
16
+ * - LRU+TTL 인메모리 캐시
17
+ * - 인터셉터 (onRequest / onResponse / onError / onRetry)
18
+ * - 비동기 제너레이터 스트리밍 검색 (searchStream)
19
+ */
20
+ export declare class NaverSeriesClient {
21
+ private baseUrl;
22
+ private userAgent;
23
+ private timeoutMs;
24
+ private maxRetries;
25
+ private cacheStore?;
26
+ private cacheTtlMs;
27
+ private logger?;
28
+ private interceptors;
29
+ private dispatcher;
30
+ constructor(options?: NaverSeriesClientOptions);
31
+ /**
32
+ * 저수준 HTTP 요청 (캐시 / 재시도 / 인터셉터 포함)
33
+ * HTML 응답의 경우 string으로, JSON이면 파싱된 객체로 반환
34
+ */
35
+ request<T = string>(endpoint: string, method?: "GET" | "POST", params?: Record<string, any>, options?: RequestOptions): Promise<T>;
36
+ /**
37
+ * 소설 / 웹툰 통합 검색
38
+ * @example
39
+ * const result = await client.search({ keyword: '환생', page: 1, type: 'novel' })
40
+ */
41
+ search(options: SearchOptions, requestOptions?: RequestOptions): Promise<SearchResult>;
42
+ /**
43
+ * 검색 결과를 비동기 제너레이터로 스트리밍 순회
44
+ * @example
45
+ * for await (const novel of client.searchStream({ keyword: '환생', maxPages: 3 })) {
46
+ * console.log(novel.title)
47
+ * }
48
+ */
49
+ searchStream(options: SearchStreamOptions, requestOptions?: RequestOptions): AsyncIterableIterator<NovelSearchResultItem>;
50
+ /**
51
+ * 소설 TOP 100 랭킹 조회
52
+ * @example
53
+ * const top100 = await client.getTop100()
54
+ */
55
+ getTop100(requestOptions?: RequestOptions): Promise<RankingNovelItem[]>;
56
+ /**
57
+ * 장르별 랭킹 목록 조회
58
+ * @param genreCode 장르 코드 (예: "romance", "fantasy", "bl")
59
+ * @example
60
+ * const items = await client.getRankingByGenre('romance')
61
+ */
62
+ getRankingByGenre(genreCode: string, requestOptions?: RequestOptions): Promise<RankingNovelItem[]>;
63
+ /** 랭킹 HTML 공통 파서 */
64
+ private _parseRankingList;
65
+ /**
66
+ * 작품 상세 정보 조회
67
+ * @param novelId productNo (예: "5553059")
68
+ * @example
69
+ * const detail = await client.getNovelDetail('5553059')
70
+ */
71
+ getNovelDetail(novelId: string | number, requestOptions?: RequestOptions): Promise<NovelDetail>;
72
+ /**
73
+ * 작품의 에피소드 목록 조회
74
+ * @param novelId productNo
75
+ * @example
76
+ * const eps = await client.getEpisodes('5553059')
77
+ */
78
+ getEpisodes(novelId: string | number, requestOptions?: RequestOptions): Promise<EpisodeListResult>;
79
+ /**
80
+ * 장르 카테고리 목록 조회
81
+ * @example
82
+ * const genres = await client.getGenres()
83
+ */
84
+ getGenres(requestOptions?: RequestOptions): Promise<CategoryItem[]>;
85
+ /**
86
+ * 키워드로 검색 후 첫 번째 결과의 상세 정보까지 반환 (편의 메서드)
87
+ * @example
88
+ * const { searchItem, detail } = await client.getNovelByName('전지적 독자 시점')
89
+ */
90
+ getNovelByName(keyword: string, requestOptions?: RequestOptions): Promise<{
91
+ searchItem: NovelSearchResultItem;
92
+ detail: NovelDetail;
93
+ }>;
94
+ /** 캐시 통계 반환 */
95
+ getCacheStats(): CacheStats | undefined;
96
+ /** 캐시 전체 비우기 */
97
+ clearCache(): void;
98
+ /** 커넥션 풀 정상 종료 */
99
+ close(): Promise<void>;
100
+ }
101
+ export default NaverSeriesClient;
102
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAW5C,OAAO,KAAK,EAER,YAAY,EAIZ,iBAAiB,EAGjB,wBAAwB,EACxB,WAAW,EACX,qBAAqB,EACrB,gBAAgB,EAEhB,aAAa,EACb,YAAY,EACZ,mBAAmB,EACtB,MAAM,YAAY,CAAA;AAwBnB,MAAM,WAAW,cAAc;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,MAAM,CAAC,EAAE,WAAW,CAAA;CACvB;AAED;;;;;;;;GAQG;AACH,qBAAa,iBAAiB;IAC1B,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,UAAU,CAAC,CAAY;IAC/B,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,MAAM,CAAC,CAAQ;IACvB,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,UAAU,CAAO;gBAEb,OAAO,GAAE,wBAA6B;IA+BlD;;;OAGG;IACG,OAAO,CAAC,CAAC,GAAG,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,KAAK,GAAG,MAAc,EAC9B,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,EAChC,OAAO,GAAE,cAAmB,GAC7B,OAAO,CAAC,CAAC,CAAC;IA8Ib;;;;OAIG;IACG,MAAM,CACR,OAAO,EAAE,aAAa,EACtB,cAAc,CAAC,EAAE,cAAc,GAChC,OAAO,CAAC,YAAY,CAAC;IA8CxB;;;;;;OAMG;IACI,YAAY,CACf,OAAO,EAAE,mBAAmB,EAC5B,cAAc,CAAC,EAAE,cAAc,GAChC,qBAAqB,CAAC,qBAAqB,CAAC;IAyB/C;;;;OAIG;IACG,SAAS,CAAC,cAAc,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAW7E;;;;;OAKG;IACG,iBAAiB,CACnB,SAAS,EAAE,MAAM,EACjB,cAAc,CAAC,EAAE,cAAc,GAChC,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAe9B,oBAAoB;IACpB,OAAO,CAAC,iBAAiB;IAuCzB;;;;;OAKG;IACG,cAAc,CAChB,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,cAAc,CAAC,EAAE,cAAc,GAChC,OAAO,CAAC,WAAW,CAAC;IAyDvB;;;;;OAKG;IACG,WAAW,CACb,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,cAAc,CAAC,EAAE,cAAc,GAChC,OAAO,CAAC,iBAAiB,CAAC;IA2C7B;;;;OAIG;IACG,SAAS,CAAC,cAAc,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IA2BzE;;;;OAIG;IACG,cAAc,CAChB,OAAO,EAAE,MAAM,EACf,cAAc,CAAC,EAAE,cAAc,GAChC,OAAO,CAAC;QAAE,UAAU,EAAE,qBAAqB,CAAC;QAAC,MAAM,EAAE,WAAW,CAAA;KAAE,CAAC;IAatE,eAAe;IACf,aAAa,IAAI,UAAU,GAAG,SAAS;IAIvC,gBAAgB;IAChB,UAAU,IAAI,IAAI;IAIlB,kBAAkB;IACZ,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG/B;AAED,eAAe,iBAAiB,CAAA"}
@@ -0,0 +1,450 @@
1
+ import { Agent, fetch as undiciFetch } from "undici";
2
+ import * as cheerio from "cheerio";
3
+ import { MemoryTtlCache } from "./cache.js";
4
+ import { NaverSeriesApiError, NaverSeriesError, NaverSeriesNetworkError, NaverSeriesNotFoundError, NaverSeriesRateLimitError, NaverSeriesTimeoutError, NaverSeriesValidationError, } from "./errors.js";
5
+ import { withRetry } from "./retry.js";
6
+ // ── 내부 유틸 ────────────────────────────────────────────
7
+ /** href에서 productNo 추출 */
8
+ function extractProductNo(href, base) {
9
+ try {
10
+ const u = new URL(href, base);
11
+ return u.searchParams.get("productNo") ?? "";
12
+ }
13
+ catch {
14
+ return "";
15
+ }
16
+ }
17
+ /** 제목에서 화수/완결 suffix 제거 */
18
+ function cleanTitle(raw) {
19
+ return raw
20
+ .split("\n")[0]
21
+ .replace(/\(총\s*\d+화.*?\)/g, "")
22
+ .trim();
23
+ }
24
+ /**
25
+ * 네이버 시리즈(series.naver.com) 비공식 TypeScript 클라이언트
26
+ *
27
+ * - Undici 커넥션 풀링
28
+ * - Full-Jitter 지수백오프 재시도
29
+ * - LRU+TTL 인메모리 캐시
30
+ * - 인터셉터 (onRequest / onResponse / onError / onRetry)
31
+ * - 비동기 제너레이터 스트리밍 검색 (searchStream)
32
+ */
33
+ export class NaverSeriesClient {
34
+ baseUrl;
35
+ userAgent;
36
+ timeoutMs;
37
+ maxRetries;
38
+ cacheStore;
39
+ cacheTtlMs;
40
+ logger;
41
+ interceptors;
42
+ dispatcher;
43
+ constructor(options = {}) {
44
+ this.baseUrl = (options.baseUrl ?? "https://series.naver.com").replace(/\/+$/, "");
45
+ this.userAgent =
46
+ options.userAgent ??
47
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
48
+ this.timeoutMs = options.timeout ?? 10000;
49
+ this.maxRetries = options.maxRetries ?? 3;
50
+ this.cacheTtlMs = options.cacheTtlMs ?? 60000;
51
+ this.logger = options.logger;
52
+ this.interceptors = options.interceptors ?? {};
53
+ if (options.cache === true) {
54
+ this.cacheStore = new MemoryTtlCache({ defaultTtlMs: this.cacheTtlMs });
55
+ }
56
+ else if (options.cache && typeof options.cache === "object") {
57
+ this.cacheStore = options.cache;
58
+ }
59
+ const pool = options.poolOptions ?? {};
60
+ this.dispatcher = new Agent({
61
+ connections: pool.connections ?? 128,
62
+ pipelining: pool.pipelining ?? 10,
63
+ keepAliveTimeout: pool.keepAliveTimeout ?? 30000,
64
+ keepAliveMaxTimeout: pool.keepAliveMaxTimeout ?? 600000,
65
+ connect: { timeout: pool.connectTimeout ?? 10000 },
66
+ });
67
+ }
68
+ // ── 저수준 HTTP ───────────────────────────────────────
69
+ /**
70
+ * 저수준 HTTP 요청 (캐시 / 재시도 / 인터셉터 포함)
71
+ * HTML 응답의 경우 string으로, JSON이면 파싱된 객체로 반환
72
+ */
73
+ async request(endpoint, method = "GET", params = {}, options = {}) {
74
+ let fullUrl = endpoint.startsWith("http")
75
+ ? endpoint
76
+ : `${this.baseUrl}${endpoint.startsWith("/") ? "" : "/"}${endpoint}`;
77
+ const defaultHeaders = {
78
+ "User-Agent": this.userAgent,
79
+ Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
80
+ "Accept-Language": "ko-KR,ko;q=0.9",
81
+ Referer: `${this.baseUrl}/`,
82
+ ...options.headers,
83
+ };
84
+ if (method === "GET" && Object.keys(params).length > 0) {
85
+ const urlObj = new URL(fullUrl);
86
+ for (const [k, v] of Object.entries(params)) {
87
+ if (v !== undefined && v !== null)
88
+ urlObj.searchParams.append(k, String(v));
89
+ }
90
+ fullUrl = urlObj.toString();
91
+ }
92
+ const cacheKey = `${method}:${fullUrl}`;
93
+ if (method === "GET" && !options.skipCache && this.cacheStore) {
94
+ const cached = this.cacheStore.get(cacheKey);
95
+ if (cached !== undefined) {
96
+ this.logger?.debug(`[Cache HIT] ${fullUrl}`);
97
+ return cached;
98
+ }
99
+ }
100
+ const timeoutMs = options.timeout ?? this.timeoutMs;
101
+ const maxRetries = options.maxRetries ?? this.maxRetries;
102
+ const execute = async () => {
103
+ const controller = new AbortController();
104
+ let timerId;
105
+ const startTime = Date.now();
106
+ if (timeoutMs > 0) {
107
+ timerId = setTimeout(() => controller.abort(), timeoutMs);
108
+ }
109
+ const signal = options.signal
110
+ ? typeof AbortSignal.any === "function"
111
+ ? AbortSignal.any([options.signal, controller.signal])
112
+ : controller.signal
113
+ : controller.signal;
114
+ try {
115
+ if (this.interceptors.onRequest) {
116
+ await this.interceptors.onRequest({
117
+ url: fullUrl,
118
+ headers: defaultHeaders,
119
+ params,
120
+ timestamp: startTime,
121
+ });
122
+ }
123
+ this.logger?.debug(`[HTTP ${method}] ${fullUrl}`);
124
+ const res = await undiciFetch(fullUrl, {
125
+ method,
126
+ headers: defaultHeaders,
127
+ body: method === "POST" ? JSON.stringify(params) : undefined,
128
+ signal,
129
+ dispatcher: this.dispatcher,
130
+ });
131
+ const latencyMs = Date.now() - startTime;
132
+ if (timerId)
133
+ clearTimeout(timerId);
134
+ if (res.status === 404) {
135
+ throw new NaverSeriesNotFoundError(`리소스 없음: ${fullUrl}`, undefined, fullUrl);
136
+ }
137
+ if (res.status === 429) {
138
+ const retryAfter = res.headers.get("retry-after");
139
+ throw new NaverSeriesRateLimitError("요청 한도 초과 (429)", retryAfter ? parseInt(retryAfter, 10) * 1000 : undefined, fullUrl);
140
+ }
141
+ const text = await res.text();
142
+ if (!res.ok) {
143
+ throw new NaverSeriesApiError(`HTTP ${res.status}`, res.status, undefined, text, fullUrl);
144
+ }
145
+ // JSON 시도 → 실패 시 raw text 반환
146
+ let responseData = text;
147
+ try {
148
+ responseData = JSON.parse(text);
149
+ }
150
+ catch { /* HTML 응답 */ }
151
+ if (this.interceptors.onResponse) {
152
+ await this.interceptors.onResponse({
153
+ url: fullUrl,
154
+ status: res.status,
155
+ data: responseData,
156
+ latencyMs,
157
+ fromCache: false,
158
+ });
159
+ }
160
+ if (method === "GET" && !options.skipCache && this.cacheStore) {
161
+ this.cacheStore.set(cacheKey, responseData, options.cacheTtlMs ?? this.cacheTtlMs);
162
+ }
163
+ return responseData;
164
+ }
165
+ catch (err) {
166
+ const latencyMs = Date.now() - startTime;
167
+ if (timerId)
168
+ clearTimeout(timerId);
169
+ let finalError;
170
+ if (err.name === "AbortError" || controller.signal.aborted) {
171
+ finalError = new NaverSeriesTimeoutError(`요청 시간 초과 (${timeoutMs}ms)`, timeoutMs, fullUrl);
172
+ }
173
+ else if (err instanceof NaverSeriesError) {
174
+ finalError = err;
175
+ }
176
+ else {
177
+ finalError = new NaverSeriesNetworkError(err.message ?? "네트워크 오류", err, fullUrl);
178
+ }
179
+ if (this.interceptors.onError) {
180
+ await this.interceptors.onError({ url: fullUrl, error: finalError, latencyMs });
181
+ }
182
+ throw finalError;
183
+ }
184
+ };
185
+ return withRetry(execute, {
186
+ maxRetries,
187
+ baseDelayMs: 300,
188
+ maxDelayMs: 3000,
189
+ logger: this.logger,
190
+ onRetry: this.interceptors.onRetry,
191
+ url: fullUrl,
192
+ });
193
+ }
194
+ // ── 검색 ─────────────────────────────────────────────
195
+ /**
196
+ * 소설 / 웹툰 통합 검색
197
+ * @example
198
+ * const result = await client.search({ keyword: '환생', page: 1, type: 'novel' })
199
+ */
200
+ async search(options, requestOptions) {
201
+ if (!options.keyword?.trim()) {
202
+ throw new NaverSeriesValidationError("keyword는 필수 값입니다.", "keyword");
203
+ }
204
+ const type = options.type ?? "novel";
205
+ const html = await this.request("/search/search.series", "GET", { t: type, q: options.keyword.trim(), page: options.page ?? 1 }, requestOptions);
206
+ const $ = cheerio.load(html);
207
+ const items = [];
208
+ $(".lst_list li").each((_, el) => {
209
+ const $el = $(el);
210
+ const href = $el.find("h3 a").attr("href") ?? "";
211
+ const rawTitle = $el.find("h3 a").text().trim();
212
+ // 총 N화/완결 파싱
213
+ const epMatch = rawTitle.match(/총\s*(\d+)화/);
214
+ const isComplete = rawTitle.includes("완결");
215
+ const item = {
216
+ novelId: extractProductNo(href, this.baseUrl),
217
+ title: cleanTitle(rawTitle),
218
+ authorName: $el.find(".author").text().trim(),
219
+ score: $el.find("em.score_num").text().trim() || undefined,
220
+ updatedAt: $el.find(".date").text().trim() || undefined,
221
+ episodeCount: epMatch ? parseInt(epMatch[1], 10) : undefined,
222
+ isComplete,
223
+ thumbnailUrl: $el.find(".pic img").attr("src") ?? undefined,
224
+ freeLabel: $el.find("em.ico_onlyfree2, em.ico_free").text().trim() || undefined,
225
+ link: href ? `${this.baseUrl}${href}` : "",
226
+ };
227
+ items.push(item);
228
+ });
229
+ // 다음 페이지 여부: 페이지네이션이 JS 렌더링이어서 아이템 수로 추론
230
+ const hasNext = items.length >= 20;
231
+ return { items, hasNext };
232
+ }
233
+ /**
234
+ * 검색 결과를 비동기 제너레이터로 스트리밍 순회
235
+ * @example
236
+ * for await (const novel of client.searchStream({ keyword: '환생', maxPages: 3 })) {
237
+ * console.log(novel.title)
238
+ * }
239
+ */
240
+ async *searchStream(options, requestOptions) {
241
+ if (!options.keyword?.trim()) {
242
+ throw new NaverSeriesValidationError("keyword는 필수 값입니다.", "keyword");
243
+ }
244
+ let page = 1;
245
+ const maxPages = options.maxPages ?? Number.MAX_SAFE_INTEGER;
246
+ while (page <= maxPages) {
247
+ const result = await this.search({ keyword: options.keyword, page, type: options.type }, requestOptions);
248
+ if (result.items.length === 0)
249
+ break;
250
+ for (const item of result.items)
251
+ yield item;
252
+ if (!result.hasNext)
253
+ break;
254
+ page++;
255
+ }
256
+ }
257
+ // ── 랭킹 ─────────────────────────────────────────────
258
+ /**
259
+ * 소설 TOP 100 랭킹 조회
260
+ * @example
261
+ * const top100 = await client.getTop100()
262
+ */
263
+ async getTop100(requestOptions) {
264
+ const html = await this.request("/novel/top100List.series", "GET", {}, requestOptions);
265
+ return this._parseRankingList(html);
266
+ }
267
+ /**
268
+ * 장르별 랭킹 목록 조회
269
+ * @param genreCode 장르 코드 (예: "romance", "fantasy", "bl")
270
+ * @example
271
+ * const items = await client.getRankingByGenre('romance')
272
+ */
273
+ async getRankingByGenre(genreCode, requestOptions) {
274
+ if (!genreCode?.trim()) {
275
+ throw new NaverSeriesValidationError("genreCode는 필수 값입니다.", "genreCode");
276
+ }
277
+ const html = await this.request(`/novel/categoryList.series`, "GET", { categoryTypeCode: genreCode }, requestOptions);
278
+ return this._parseRankingList(html);
279
+ }
280
+ /** 랭킹 HTML 공통 파서 */
281
+ _parseRankingList(html) {
282
+ const $ = cheerio.load(html);
283
+ const items = [];
284
+ $(".comic_top_lst li, .lst_type1 li").each((i, el) => {
285
+ const $el = $(el);
286
+ const href = $el.find("a.pic").attr("href") ?? $el.find("h3 a").attr("href") ?? "";
287
+ const rawTitle = ($el.find("h3 a").text() || $el.find(".tit").text()).trim();
288
+ // 순위 변동 파악
289
+ let rankChange;
290
+ if ($el.find(".ico_new, .new").length)
291
+ rankChange = "new";
292
+ else if ($el.find(".ico_up, .up").length)
293
+ rankChange = "up";
294
+ else if ($el.find(".ico_down, .down").length)
295
+ rankChange = "down";
296
+ else
297
+ rankChange = "same";
298
+ // 순위 번호
299
+ const rankText = $el.find(".top_num em, .no1, .no2, .no3").text().trim();
300
+ const rank = rankText ? parseInt(rankText, 10) : i + 1;
301
+ items.push({
302
+ rank,
303
+ novelId: extractProductNo(href, this.baseUrl),
304
+ title: cleanTitle(rawTitle),
305
+ authorName: $el.find(".author").text().trim(),
306
+ score: $el.find("em.score_num").text().trim() || undefined,
307
+ thumbnailUrl: $el.find(".pic img").attr("src") ?? undefined,
308
+ rankChange,
309
+ isNew: rankChange === "new",
310
+ freeLabel: $el.find("em.ico_onlyfree2, em.ico_free").text().trim() || undefined,
311
+ link: href ? `${this.baseUrl}${href}` : "",
312
+ });
313
+ });
314
+ return items;
315
+ }
316
+ // ── 작품 상세 ─────────────────────────────────────────
317
+ /**
318
+ * 작품 상세 정보 조회
319
+ * @param novelId productNo (예: "5553059")
320
+ * @example
321
+ * const detail = await client.getNovelDetail('5553059')
322
+ */
323
+ async getNovelDetail(novelId, requestOptions) {
324
+ if (!novelId) {
325
+ throw new NaverSeriesValidationError("novelId는 필수 값입니다.", "novelId");
326
+ }
327
+ const html = await this.request(`/novel/detail.series`, "GET", { productNo: String(novelId) }, requestOptions);
328
+ const $ = cheerio.load(html);
329
+ // 제목: <title> 태그에서 가장 정확하게 추출
330
+ const pageTitle = $("title").text().trim();
331
+ // "작품명 : 네이버 시리즈" 형식에서 추출
332
+ const title = pageTitle.split(":")[0].trim();
333
+ const authorName = $(".author").first().text().trim();
334
+ const score = $("em.score_num").first().text().trim() || undefined;
335
+ // 설명문 - JS 렌더링 전에도 일부 포함될 수 있음
336
+ const description = $(".dsc_txt, p.txt, .desc").first().text().trim() || undefined;
337
+ // 장르
338
+ const genres = [];
339
+ $(".genre_area a, .category a").each((_, el) => {
340
+ const g = $(el).text().trim();
341
+ if (g)
342
+ genres.push(g);
343
+ });
344
+ // 에피소드 수/완결 여부
345
+ const epContainer = $("[class*='episode'], .ep_count, .lst_view").first();
346
+ const episodeCountText = $("dt:contains('총 회차') + dd, .total_episode").text().trim();
347
+ const epMatch = episodeCountText.match(/\d+/);
348
+ const episodeCount = epMatch ? parseInt(epMatch[0], 10) : undefined;
349
+ const isComplete = html.includes("완결") || html.includes("complete");
350
+ const thumbnailUrl = $(".book_area img, .pic img").first().attr("src") ?? undefined;
351
+ return {
352
+ novelId: String(novelId),
353
+ title,
354
+ authorName,
355
+ score,
356
+ description,
357
+ thumbnailUrl,
358
+ genres: genres.length > 0 ? genres : undefined,
359
+ isComplete,
360
+ episodeCount,
361
+ link: `${this.baseUrl}/novel/detail.series?productNo=${novelId}`,
362
+ };
363
+ }
364
+ // ── 에피소드 목록 ─────────────────────────────────────
365
+ /**
366
+ * 작품의 에피소드 목록 조회
367
+ * @param novelId productNo
368
+ * @example
369
+ * const eps = await client.getEpisodes('5553059')
370
+ */
371
+ async getEpisodes(novelId, requestOptions) {
372
+ if (!novelId) {
373
+ throw new NaverSeriesValidationError("novelId는 필수 값입니다.", "novelId");
374
+ }
375
+ const html = await this.request(`/novel/detail.series`, "GET", { productNo: String(novelId) }, requestOptions);
376
+ const $ = cheerio.load(html);
377
+ const items = [];
378
+ $("[class*='episode'] li, .lst_view li").each((i, el) => {
379
+ const $el = $(el);
380
+ const href = $el.find("a").first().attr("href") ?? "";
381
+ const title = $el.find(".title, a").first().text().trim();
382
+ const isFree = $el.find(".free, .ico_free, em.free").length > 0 ||
383
+ $el.find("em.ico_onlyfree2").length > 0;
384
+ const uploadedAt = $el.find(".date").text().trim() || undefined;
385
+ if (!title)
386
+ return;
387
+ items.push({
388
+ episodeId: href
389
+ ? (new URL(href, this.baseUrl).searchParams.get("no") ?? undefined)
390
+ : undefined,
391
+ title,
392
+ link: href.startsWith("http") ? href : `${this.baseUrl}${href}`,
393
+ isFree,
394
+ uploadedAt,
395
+ episodeNumber: i + 1,
396
+ });
397
+ });
398
+ return { novelId: String(novelId), items };
399
+ }
400
+ // ── 카테고리 / 장르 ───────────────────────────────────
401
+ /**
402
+ * 장르 카테고리 목록 조회
403
+ * @example
404
+ * const genres = await client.getGenres()
405
+ */
406
+ async getGenres(requestOptions) {
407
+ const html = await this.request("/novel/categoryList.series", "GET", { categoryTypeCode: "genre" }, requestOptions);
408
+ const $ = cheerio.load(html);
409
+ const items = [];
410
+ $(".category_area li a, .gnb_sub a").each((_, el) => {
411
+ const name = $(el).text().trim();
412
+ const href = $(el).attr("href") ?? "";
413
+ if (!name || !href)
414
+ return;
415
+ const code = new URL(href, this.baseUrl).searchParams.get("categoryTypeCode") ?? href;
416
+ items.push({ name, code, link: `${this.baseUrl}${href}` });
417
+ });
418
+ return items;
419
+ }
420
+ // ── 이름으로 검색 → 상세 일괄 ─────────────────────────
421
+ /**
422
+ * 키워드로 검색 후 첫 번째 결과의 상세 정보까지 반환 (편의 메서드)
423
+ * @example
424
+ * const { searchItem, detail } = await client.getNovelByName('전지적 독자 시점')
425
+ */
426
+ async getNovelByName(keyword, requestOptions) {
427
+ const result = await this.search({ keyword, page: 1 }, requestOptions);
428
+ if (result.items.length === 0) {
429
+ throw new NaverSeriesNotFoundError(`"${keyword}"에 해당하는 작품을 찾지 못했습니다.`);
430
+ }
431
+ const searchItem = result.items[0];
432
+ const detail = await this.getNovelDetail(searchItem.novelId, requestOptions);
433
+ return { searchItem, detail };
434
+ }
435
+ // ── 캐시 / 리소스 ─────────────────────────────────────
436
+ /** 캐시 통계 반환 */
437
+ getCacheStats() {
438
+ return this.cacheStore?.getStats ? this.cacheStore.getStats() : undefined;
439
+ }
440
+ /** 캐시 전체 비우기 */
441
+ clearCache() {
442
+ this.cacheStore?.clear();
443
+ }
444
+ /** 커넥션 풀 정상 종료 */
445
+ async close() {
446
+ await this.dispatcher.close();
447
+ }
448
+ }
449
+ export default NaverSeriesClient;
450
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ export declare class NaverSeriesError extends Error {
2
+ readonly cause?: (Error | unknown) | undefined;
3
+ readonly url?: string | undefined;
4
+ constructor(message: string, cause?: (Error | unknown) | undefined, url?: string | undefined);
5
+ }
6
+ export declare class NaverSeriesApiError extends NaverSeriesError {
7
+ readonly statusCode?: number | undefined;
8
+ readonly errorCode?: string | number | undefined;
9
+ readonly rawData?: any | undefined;
10
+ constructor(message: string, statusCode?: number | undefined, errorCode?: string | number | undefined, rawData?: any | undefined, url?: string);
11
+ }
12
+ export declare class NaverSeriesNetworkError extends NaverSeriesError {
13
+ constructor(message: string, cause?: Error | unknown, url?: string);
14
+ }
15
+ export declare class NaverSeriesTimeoutError extends NaverSeriesError {
16
+ readonly timeoutMs?: number | undefined;
17
+ constructor(message: string, timeoutMs?: number | undefined, url?: string);
18
+ }
19
+ export declare class NaverSeriesNotFoundError extends NaverSeriesError {
20
+ constructor(message: string, cause?: Error | unknown, url?: string);
21
+ }
22
+ export declare class NaverSeriesRateLimitError extends NaverSeriesError {
23
+ readonly retryAfterMs?: number | undefined;
24
+ constructor(message: string, retryAfterMs?: number | undefined, url?: string);
25
+ }
26
+ export declare class NaverSeriesValidationError extends NaverSeriesError {
27
+ readonly field?: string | undefined;
28
+ constructor(message: string, field?: string | undefined);
29
+ }
30
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,gBAAiB,SAAQ,KAAK;aAGnB,KAAK,CAAC,GAAE,KAAK,GAAG,OAAO;aACvB,GAAG,CAAC,EAAE,MAAM;gBAF5B,OAAO,EAAE,MAAM,EACC,KAAK,CAAC,GAAE,KAAK,GAAG,OAAO,aAAA,EACvB,GAAG,CAAC,EAAE,MAAM,YAAA;CAKnC;AAED,qBAAa,mBAAoB,SAAQ,gBAAgB;aAGjC,UAAU,CAAC,EAAE,MAAM;aACnB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM;aAC3B,OAAO,CAAC,EAAE,GAAG;gBAH7B,OAAO,EAAE,MAAM,EACC,UAAU,CAAC,EAAE,MAAM,YAAA,EACnB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,YAAA,EAC3B,OAAO,CAAC,EAAE,GAAG,YAAA,EAC7B,GAAG,CAAC,EAAE,MAAM;CAKnB;AAED,qBAAa,uBAAwB,SAAQ,gBAAgB;gBAC7C,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM;CAIrE;AAED,qBAAa,uBAAwB,SAAQ,gBAAgB;aACZ,SAAS,CAAC,EAAE,MAAM;gBAAnD,OAAO,EAAE,MAAM,EAAkB,SAAS,CAAC,EAAE,MAAM,YAAA,EAAE,GAAG,CAAC,EAAE,MAAM;CAIhF;AAED,qBAAa,wBAAyB,SAAQ,gBAAgB;gBAC9C,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM;CAIrE;AAED,qBAAa,yBAA0B,SAAQ,gBAAgB;aAGvC,YAAY,CAAC,EAAE,MAAM;gBADrC,OAAO,EAAE,MAAM,EACC,YAAY,CAAC,EAAE,MAAM,YAAA,EACrC,GAAG,CAAC,EAAE,MAAM;CAKnB;AAED,qBAAa,0BAA2B,SAAQ,gBAAgB;aACf,KAAK,CAAC,EAAE,MAAM;gBAA/C,OAAO,EAAE,MAAM,EAAkB,KAAK,CAAC,EAAE,MAAM,YAAA;CAI9D"}
@@ -0,0 +1,59 @@
1
+ export class NaverSeriesError extends Error {
2
+ cause;
3
+ url;
4
+ constructor(message, cause, url) {
5
+ super(message);
6
+ this.cause = cause;
7
+ this.url = url;
8
+ this.name = "NaverSeriesError";
9
+ }
10
+ }
11
+ export class NaverSeriesApiError extends NaverSeriesError {
12
+ statusCode;
13
+ errorCode;
14
+ rawData;
15
+ constructor(message, statusCode, errorCode, rawData, url) {
16
+ super(message, undefined, url);
17
+ this.statusCode = statusCode;
18
+ this.errorCode = errorCode;
19
+ this.rawData = rawData;
20
+ this.name = "NaverSeriesApiError";
21
+ }
22
+ }
23
+ export class NaverSeriesNetworkError extends NaverSeriesError {
24
+ constructor(message, cause, url) {
25
+ super(message, cause, url);
26
+ this.name = "NaverSeriesNetworkError";
27
+ }
28
+ }
29
+ export class NaverSeriesTimeoutError extends NaverSeriesError {
30
+ timeoutMs;
31
+ constructor(message, timeoutMs, url) {
32
+ super(message, undefined, url);
33
+ this.timeoutMs = timeoutMs;
34
+ this.name = "NaverSeriesTimeoutError";
35
+ }
36
+ }
37
+ export class NaverSeriesNotFoundError extends NaverSeriesError {
38
+ constructor(message, cause, url) {
39
+ super(message, cause, url);
40
+ this.name = "NaverSeriesNotFoundError";
41
+ }
42
+ }
43
+ export class NaverSeriesRateLimitError extends NaverSeriesError {
44
+ retryAfterMs;
45
+ constructor(message, retryAfterMs, url) {
46
+ super(message, undefined, url);
47
+ this.retryAfterMs = retryAfterMs;
48
+ this.name = "NaverSeriesRateLimitError";
49
+ }
50
+ }
51
+ export class NaverSeriesValidationError extends NaverSeriesError {
52
+ field;
53
+ constructor(message, field) {
54
+ super(message);
55
+ this.field = field;
56
+ this.name = "NaverSeriesValidationError";
57
+ }
58
+ }
59
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAGnB;IACA;IAHpB,YACI,OAAe,EACC,KAAuB,EACvB,GAAY;QAE5B,KAAK,CAAC,OAAO,CAAC,CAAA;QAHE,UAAK,GAAL,KAAK,CAAkB;QACvB,QAAG,GAAH,GAAG,CAAS;QAG5B,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAA;IAClC,CAAC;CACJ;AAED,MAAM,OAAO,mBAAoB,SAAQ,gBAAgB;IAGjC;IACA;IACA;IAJpB,YACI,OAAe,EACC,UAAmB,EACnB,SAA2B,EAC3B,OAAa,EAC7B,GAAY;QAEZ,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,CAAA;QALd,eAAU,GAAV,UAAU,CAAS;QACnB,cAAS,GAAT,SAAS,CAAkB;QAC3B,YAAO,GAAP,OAAO,CAAM;QAI7B,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAA;IACrC,CAAC;CACJ;AAED,MAAM,OAAO,uBAAwB,SAAQ,gBAAgB;IACzD,YAAY,OAAe,EAAE,KAAuB,EAAE,GAAY;QAC9D,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,CAAA;QAC1B,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAA;IACzC,CAAC;CACJ;AAED,MAAM,OAAO,uBAAwB,SAAQ,gBAAgB;IACZ;IAA7C,YAAY,OAAe,EAAkB,SAAkB,EAAE,GAAY;QACzE,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,CAAA;QADW,cAAS,GAAT,SAAS,CAAS;QAE3D,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAA;IACzC,CAAC;CACJ;AAED,MAAM,OAAO,wBAAyB,SAAQ,gBAAgB;IAC1D,YAAY,OAAe,EAAE,KAAuB,EAAE,GAAY;QAC9D,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,CAAA;QAC1B,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAA;IAC1C,CAAC;CACJ;AAED,MAAM,OAAO,yBAA0B,SAAQ,gBAAgB;IAGvC;IAFpB,YACI,OAAe,EACC,YAAqB,EACrC,GAAY;QAEZ,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,CAAA;QAHd,iBAAY,GAAZ,YAAY,CAAS;QAIrC,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAA;IAC3C,CAAC;CACJ;AAED,MAAM,OAAO,0BAA2B,SAAQ,gBAAgB;IACf;IAA7C,YAAY,OAAe,EAAkB,KAAc;QACvD,KAAK,CAAC,OAAO,CAAC,CAAA;QAD2B,UAAK,GAAL,KAAK,CAAS;QAEvD,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAA;IAC5C,CAAC;CACJ"}
@@ -0,0 +1,8 @@
1
+ export * from "./types.js";
2
+ export * from "./errors.js";
3
+ export * from "./cache.js";
4
+ export * from "./retry.js";
5
+ export * from "./client.js";
6
+ import { NaverSeriesClient } from "./client.js";
7
+ export default NaverSeriesClient;
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAE3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC/C,eAAe,iBAAiB,CAAA"}
@@ -0,0 +1,8 @@
1
+ export * from "./types.js";
2
+ export * from "./errors.js";
3
+ export * from "./cache.js";
4
+ export * from "./retry.js";
5
+ export * from "./client.js";
6
+ import { NaverSeriesClient } from "./client.js";
7
+ export default NaverSeriesClient;
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAE3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC/C,eAAe,iBAAiB,CAAA"}
@@ -0,0 +1,11 @@
1
+ import { Logger } from "./types.js";
2
+ export interface RetryOptions {
3
+ maxRetries: number;
4
+ baseDelayMs?: number;
5
+ maxDelayMs?: number;
6
+ logger?: Logger;
7
+ onRetry?: (error: Error, attempt: number, delayMs: number) => Promise<void> | void;
8
+ url?: string;
9
+ }
10
+ export declare function withRetry<T>(fn: () => Promise<T>, options: RetryOptions): Promise<T>;
11
+ //# sourceMappingURL=retry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/retry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAEnC,MAAM,WAAW,YAAY;IACzB,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAClF,GAAG,CAAC,EAAE,MAAM,CAAA;CACf;AAeD,wBAAsB,SAAS,CAAC,CAAC,EAC7B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,EAAE,YAAY,GACtB,OAAO,CAAC,CAAC,CAAC,CAyBZ"}
@@ -0,0 +1,36 @@
1
+ function isRetryable(err) {
2
+ // 429, 5xx, 네트워크 에러 재시도
3
+ if (err?.statusCode && err.statusCode < 500 && err.statusCode !== 429)
4
+ return false;
5
+ if (err?.name === "NaverSeriesNotFoundError")
6
+ return false;
7
+ if (err?.name === "NaverSeriesValidationError")
8
+ return false;
9
+ return true;
10
+ }
11
+ function fullJitterDelay(base, max, attempt) {
12
+ const cap = Math.min(max, base * 2 ** attempt);
13
+ return Math.random() * cap;
14
+ }
15
+ export async function withRetry(fn, options) {
16
+ const { maxRetries, baseDelayMs = 300, maxDelayMs = 3000, logger, onRetry, url } = options;
17
+ let lastError = new Error("unknown");
18
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
19
+ try {
20
+ return await fn();
21
+ }
22
+ catch (err) {
23
+ lastError = err instanceof Error ? err : new Error(String(err));
24
+ if (attempt >= maxRetries || !isRetryable(err)) {
25
+ throw lastError;
26
+ }
27
+ const delayMs = fullJitterDelay(baseDelayMs, maxDelayMs, attempt);
28
+ logger?.warn(`[Retry ${attempt + 1}/${maxRetries}] ${url ?? ""} - ${lastError.message} (delay: ${delayMs.toFixed(0)}ms)`);
29
+ if (onRetry)
30
+ await onRetry(lastError, attempt + 1, delayMs);
31
+ await new Promise((r) => setTimeout(r, delayMs));
32
+ }
33
+ }
34
+ throw lastError;
35
+ }
36
+ //# sourceMappingURL=retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/retry.ts"],"names":[],"mappings":"AAYA,SAAS,WAAW,CAAC,GAAQ;IACzB,wBAAwB;IACxB,IAAI,GAAG,EAAE,UAAU,IAAI,GAAG,CAAC,UAAU,GAAG,GAAG,IAAI,GAAG,CAAC,UAAU,KAAK,GAAG;QAAE,OAAO,KAAK,CAAA;IACnF,IAAI,GAAG,EAAE,IAAI,KAAK,0BAA0B;QAAE,OAAO,KAAK,CAAA;IAC1D,IAAI,GAAG,EAAE,IAAI,KAAK,4BAA4B;QAAE,OAAO,KAAK,CAAA;IAC5D,OAAO,IAAI,CAAA;AACf,CAAC;AAED,SAAS,eAAe,CAAC,IAAY,EAAE,GAAW,EAAE,OAAe;IAC/D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,GAAG,CAAC,IAAI,OAAO,CAAC,CAAA;IAC9C,OAAO,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAA;AAC9B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAC3B,EAAoB,EACpB,OAAqB;IAErB,MAAM,EAAE,UAAU,EAAE,WAAW,GAAG,GAAG,EAAE,UAAU,GAAG,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,OAAO,CAAA;IAC1F,IAAI,SAAS,GAAU,IAAI,KAAK,CAAC,SAAS,CAAC,CAAA;IAE3C,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;QACrD,IAAI,CAAC;YACD,OAAO,MAAM,EAAE,EAAE,CAAA;QACrB,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAChB,SAAS,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;YAE/D,IAAI,OAAO,IAAI,UAAU,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC7C,MAAM,SAAS,CAAA;YACnB,CAAC;YAED,MAAM,OAAO,GAAG,eAAe,CAAC,WAAW,EAAE,UAAU,EAAE,OAAO,CAAC,CAAA;YACjE,MAAM,EAAE,IAAI,CACR,UAAU,OAAO,GAAG,CAAC,IAAI,UAAU,KAAK,GAAG,IAAI,EAAE,MAAM,SAAS,CAAC,OAAO,YAAY,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAC9G,CAAA;YAED,IAAI,OAAO;gBAAE,MAAM,OAAO,CAAC,SAAS,EAAE,OAAO,GAAG,CAAC,EAAE,OAAO,CAAC,CAAA;YAC3D,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAA;QACpD,CAAC;IACL,CAAC;IAED,MAAM,SAAS,CAAA;AACnB,CAAC"}
@@ -0,0 +1,125 @@
1
+ export interface Logger {
2
+ debug(message: string, ...meta: any[]): void;
3
+ info(message: string, ...meta: any[]): void;
4
+ warn(message: string, ...meta: any[]): void;
5
+ error(message: string, ...meta: any[]): void;
6
+ }
7
+ export interface Interceptors {
8
+ onRequest?: (req: RequestContext) => Promise<void> | void;
9
+ onResponse?: (res: ResponseContext) => Promise<void> | void;
10
+ onError?: (ctx: ErrorContext) => Promise<void> | void;
11
+ onRetry?: (error: Error, attempt: number, delayMs: number) => Promise<void> | void;
12
+ }
13
+ export interface RequestContext {
14
+ url: string;
15
+ headers: Record<string, string>;
16
+ params: Record<string, any>;
17
+ timestamp: number;
18
+ }
19
+ export interface ResponseContext {
20
+ url: string;
21
+ status: number;
22
+ data: any;
23
+ latencyMs: number;
24
+ fromCache: boolean;
25
+ }
26
+ export interface ErrorContext {
27
+ url: string;
28
+ error: Error;
29
+ latencyMs: number;
30
+ }
31
+ export interface ConnectionPoolOptions {
32
+ connections?: number;
33
+ pipelining?: number;
34
+ keepAliveTimeout?: number;
35
+ keepAliveMaxTimeout?: number;
36
+ connectTimeout?: number;
37
+ }
38
+ export interface CacheStore {
39
+ get<T>(key: string): T | undefined;
40
+ set<T>(key: string, value: T, ttlMs?: number): void;
41
+ delete(key: string): void;
42
+ clear(): void;
43
+ getStats?(): import("./cache.js").CacheStats;
44
+ }
45
+ export interface NaverSeriesClientOptions {
46
+ baseUrl?: string;
47
+ userAgent?: string;
48
+ timeout?: number;
49
+ maxRetries?: number;
50
+ cache?: boolean | CacheStore;
51
+ cacheTtlMs?: number;
52
+ logger?: Logger;
53
+ interceptors?: Interceptors;
54
+ poolOptions?: ConnectionPoolOptions;
55
+ }
56
+ export type ContentType = 'novel' | 'webtoon' | 'all';
57
+ export interface SearchOptions {
58
+ keyword: string;
59
+ page?: number;
60
+ type?: ContentType;
61
+ }
62
+ export interface SearchStreamOptions {
63
+ keyword: string;
64
+ maxPages?: number;
65
+ type?: ContentType;
66
+ }
67
+ export interface NovelSearchResultItem {
68
+ novelId: string;
69
+ title: string;
70
+ authorName: string;
71
+ score?: string;
72
+ updatedAt?: string;
73
+ episodeCount?: number;
74
+ isComplete?: boolean;
75
+ thumbnailUrl?: string;
76
+ freeLabel?: string;
77
+ link: string;
78
+ }
79
+ export interface SearchResult {
80
+ items: NovelSearchResultItem[];
81
+ hasNext: boolean;
82
+ }
83
+ export type RankingType = 'top100' | 'daily' | 'weekly' | 'monthly';
84
+ export interface RankingNovelItem {
85
+ rank: number;
86
+ novelId: string;
87
+ title: string;
88
+ authorName: string;
89
+ score?: string;
90
+ thumbnailUrl?: string;
91
+ rankChange?: 'up' | 'down' | 'new' | 'same';
92
+ isNew?: boolean;
93
+ freeLabel?: string;
94
+ link: string;
95
+ }
96
+ export interface NovelDetail {
97
+ novelId: string;
98
+ title: string;
99
+ authorName: string;
100
+ score?: string;
101
+ description?: string;
102
+ thumbnailUrl?: string;
103
+ genres?: string[];
104
+ isComplete?: boolean;
105
+ episodeCount?: number;
106
+ link: string;
107
+ }
108
+ export interface EpisodeItem {
109
+ episodeId?: string;
110
+ title: string;
111
+ link: string;
112
+ isFree: boolean;
113
+ uploadedAt?: string;
114
+ episodeNumber?: number;
115
+ }
116
+ export interface EpisodeListResult {
117
+ novelId: string;
118
+ items: EpisodeItem[];
119
+ }
120
+ export interface CategoryItem {
121
+ name: string;
122
+ code: string;
123
+ link: string;
124
+ }
125
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,MAAM;IACnB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;IAC5C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;IAC3C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;IAC3C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;CAC/C;AAED,MAAM,WAAW,YAAY;IACzB,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACzD,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC3D,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACrD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CACrF;AAED,MAAM,WAAW,cAAc;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC3B,SAAS,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,eAAe;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,GAAG,CAAA;IACT,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,OAAO,CAAA;CACrB;AAED,MAAM,WAAW,YAAY;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,KAAK,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,qBAAqB;IAClC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,UAAU;IACvB,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAA;IAClC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,KAAK,IAAI,IAAI,CAAA;IACb,QAAQ,CAAC,IAAI,OAAO,YAAY,EAAE,UAAU,CAAA;CAC/C;AAED,MAAM,WAAW,wBAAwB;IACrC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,OAAO,GAAG,UAAU,CAAA;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B,WAAW,CAAC,EAAE,qBAAqB,CAAA;CACtC;AAGD,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,SAAS,GAAG,KAAK,CAAA;AAErD,MAAM,WAAW,aAAa;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,WAAW,CAAA;CACrB;AAED,MAAM,WAAW,mBAAmB;IAChC,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,WAAW,CAAA;CACrB;AAED,MAAM,WAAW,qBAAqB;IAClC,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,YAAY;IACzB,KAAK,EAAE,qBAAqB,EAAE,CAAA;IAC9B,OAAO,EAAE,OAAO,CAAA;CACnB;AAGD,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAA;AAEnE,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAA;IAC3C,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;CACf;AAGD,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,IAAI,EAAE,MAAM,CAAA;CACf;AAGD,MAAM,WAAW,WAAW;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,OAAO,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,aAAa,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,MAAM,WAAW,iBAAiB;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,WAAW,EAAE,CAAA;CACvB;AAGD,MAAM,WAAW,YAAY;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACf"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "naverseries-sdk",
3
+ "version": "1.0.0",
4
+ "description": "네이버 시리즈 비공식 TypeScript API 클라이언트",
5
+ "type": "module",
6
+ "main": "./dist/src/index.js",
7
+ "types": "./dist/src/index.d.ts",
8
+ "module": "./dist/src/index.js",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/re0nyaa/naverseries-sdk.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/re0nyaa/naverseries-sdk/issues"
15
+ },
16
+ "homepage": "https://github.com/re0nyaa/naverseries-sdk#readme",
17
+ "keywords": [
18
+ "naver",
19
+ "series",
20
+ "naver-series",
21
+ "novel",
22
+ "webtoon",
23
+ "sdk",
24
+ "scraper",
25
+ "typescript",
26
+ "api"
27
+ ],
28
+ "author": "re0nyaa",
29
+ "license": "MIT",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/src/index.d.ts",
33
+ "import": "./dist/src/index.js",
34
+ "require": "./dist/src/index.js"
35
+ },
36
+ "./client": {
37
+ "types": "./dist/src/client.d.ts",
38
+ "import": "./dist/src/client.js",
39
+ "require": "./dist/src/client.js"
40
+ },
41
+ "./types": {
42
+ "types": "./dist/src/types.d.ts",
43
+ "import": "./dist/src/types.js",
44
+ "require": "./dist/src/types.js"
45
+ },
46
+ "./errors": {
47
+ "types": "./dist/src/errors.d.ts",
48
+ "import": "./dist/src/errors.js",
49
+ "require": "./dist/src/errors.js"
50
+ },
51
+ "./cache": {
52
+ "types": "./dist/src/cache.d.ts",
53
+ "import": "./dist/src/cache.js",
54
+ "require": "./dist/src/cache.js"
55
+ },
56
+ "./retry": {
57
+ "types": "./dist/src/retry.d.ts",
58
+ "import": "./dist/src/retry.js",
59
+ "require": "./dist/src/retry.js"
60
+ }
61
+ },
62
+ "files": [
63
+ "dist",
64
+ "README.md",
65
+ "LICENSE"
66
+ ],
67
+ "scripts": {
68
+ "prepublishOnly": "pnpm build",
69
+ "start": "node --import tsx/esm demo.ts",
70
+ "demo": "node --import tsx/esm demo.ts",
71
+ "build": "tsc -p tsconfig.build.json",
72
+ "typecheck": "tsc --noEmit"
73
+ },
74
+ "engines": {
75
+ "node": ">=18.0.0"
76
+ },
77
+ "dependencies": {
78
+ "cheerio": "^1.0.0",
79
+ "undici": "^6.21.1"
80
+ },
81
+ "devDependencies": {
82
+ "@types/node": "^22.0.0",
83
+ "tsx": "^4.23.15",
84
+ "typescript": "^5.5.0"
85
+ }
86
+ }