@zonuexe/techbook-mcp 0.2.2 → 0.2.4

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.
Files changed (151) hide show
  1. package/CHANGELOG.md +28 -1
  2. package/README.md +39 -20
  3. package/dist/adapters/calil.d.ts +10 -0
  4. package/dist/adapters/calil.d.ts.map +1 -0
  5. package/dist/adapters/calil.js +45 -0
  6. package/dist/adapters/calil.js.map +1 -0
  7. package/dist/adapters/openbd.d.ts +57 -0
  8. package/dist/adapters/openbd.d.ts.map +1 -0
  9. package/dist/adapters/openbd.js +87 -0
  10. package/dist/adapters/openbd.js.map +1 -0
  11. package/dist/adapters/publishers/google-books.d.ts +4 -0
  12. package/dist/adapters/publishers/google-books.d.ts.map +1 -0
  13. package/dist/adapters/publishers/google-books.js +75 -0
  14. package/dist/adapters/publishers/google-books.js.map +1 -0
  15. package/dist/adapters/publishers/isbn-publisher-codes.d.ts +21 -0
  16. package/dist/adapters/publishers/isbn-publisher-codes.d.ts.map +1 -0
  17. package/dist/adapters/publishers/isbn-publisher-codes.js +49 -0
  18. package/dist/adapters/publishers/isbn-publisher-codes.js.map +1 -0
  19. package/dist/adapters/publishers/juse-p.d.ts +3 -0
  20. package/dist/adapters/publishers/juse-p.d.ts.map +1 -0
  21. package/dist/adapters/publishers/juse-p.js +110 -0
  22. package/dist/adapters/publishers/juse-p.js.map +1 -0
  23. package/dist/adapters/publishers/registry.d.ts.map +1 -1
  24. package/dist/adapters/publishers/registry.js +4 -0
  25. package/dist/adapters/publishers/registry.js.map +1 -1
  26. package/dist/adapters/publishers/tatsu-zine.d.ts.map +1 -1
  27. package/dist/adapters/publishers/tatsu-zine.js +6 -18
  28. package/dist/adapters/publishers/tatsu-zine.js.map +1 -1
  29. package/dist/application/get-book-by-isbn.d.ts +13 -0
  30. package/dist/application/get-book-by-isbn.d.ts.map +1 -0
  31. package/dist/application/get-book-by-isbn.js +61 -0
  32. package/dist/application/get-book-by-isbn.js.map +1 -0
  33. package/dist/application/get-book-detail.d.ts.map +1 -1
  34. package/dist/application/get-book-detail.js +16 -1
  35. package/dist/application/get-book-detail.js.map +1 -1
  36. package/dist/application/search-books.d.ts.map +1 -1
  37. package/dist/application/search-books.js +20 -0
  38. package/dist/application/search-books.js.map +1 -1
  39. package/dist/config/credentials.d.ts +8 -0
  40. package/dist/config/credentials.d.ts.map +1 -0
  41. package/dist/config/credentials.js +32 -0
  42. package/dist/config/credentials.js.map +1 -0
  43. package/dist/main.js +15 -1
  44. package/dist/main.js.map +1 -1
  45. package/dist/mcp/server.d.ts.map +1 -1
  46. package/dist/mcp/server.js +10 -0
  47. package/dist/mcp/server.js.map +1 -1
  48. package/dist/mcp/tools.d.ts +13 -0
  49. package/dist/mcp/tools.d.ts.map +1 -1
  50. package/dist/mcp/tools.js +16 -0
  51. package/dist/mcp/tools.js.map +1 -1
  52. package/dist/setup.d.ts +2 -0
  53. package/dist/setup.d.ts.map +1 -0
  54. package/dist/setup.js +43 -0
  55. package/dist/setup.js.map +1 -0
  56. package/flake.lock +61 -0
  57. package/package.json +1 -1
  58. package/.claude/settings.local.json +0 -36
  59. package/.codex/skills/techbook-mcp-release-prep/SKILL.md +0 -105
  60. package/.github/workflows/test.yml +0 -72
  61. package/.oxlintrc.json +0 -12
  62. package/AGENTS.md +0 -100
  63. package/deno.json +0 -3
  64. package/src/adapters/cache/memory-cache.ts +0 -31
  65. package/src/adapters/cache/null-cache.ts +0 -8
  66. package/src/adapters/html/cheerio-parser.ts +0 -50
  67. package/src/adapters/http/fetch-client.ts +0 -47
  68. package/src/adapters/http/mock-client.ts +0 -77
  69. package/src/adapters/publishers/base.ts +0 -279
  70. package/src/adapters/publishers/book-tech.ts +0 -117
  71. package/src/adapters/publishers/born-digital.ts +0 -143
  72. package/src/adapters/publishers/coronasha.ts +0 -139
  73. package/src/adapters/publishers/gihyo.ts +0 -120
  74. package/src/adapters/publishers/impress.ts +0 -103
  75. package/src/adapters/publishers/lambdanote.ts +0 -146
  76. package/src/adapters/publishers/manatee.ts +0 -113
  77. package/src/adapters/publishers/maruzen-publishing.ts +0 -129
  78. package/src/adapters/publishers/optronics.ts +0 -113
  79. package/src/adapters/publishers/oreilly-japan.ts +0 -133
  80. package/src/adapters/publishers/peaks.ts +0 -98
  81. package/src/adapters/publishers/personal-media.ts +0 -168
  82. package/src/adapters/publishers/registry.ts +0 -38
  83. package/src/adapters/publishers/rutles.ts +0 -149
  84. package/src/adapters/publishers/saiensu.ts +0 -136
  85. package/src/adapters/publishers/seshop.ts +0 -121
  86. package/src/adapters/publishers/tatsu-zine.ts +0 -154
  87. package/src/adapters/publishers/techbookfest.ts +0 -179
  88. package/src/application/get-book-detail.ts +0 -24
  89. package/src/application/search-books.ts +0 -44
  90. package/src/domain/book.ts +0 -35
  91. package/src/domain/publisher.ts +0 -18
  92. package/src/main.ts +0 -14
  93. package/src/mcp/server.ts +0 -103
  94. package/src/mcp/tools.ts +0 -54
  95. package/src/ports/cache.ts +0 -5
  96. package/src/ports/html-parser.ts +0 -15
  97. package/src/ports/http.ts +0 -17
  98. package/tests/fixtures/book-tech-detail.html +0 -51
  99. package/tests/fixtures/book-tech-search.html +0 -91
  100. package/tests/fixtures/born-digital-detail.html +0 -62
  101. package/tests/fixtures/born-digital-search.html +0 -51
  102. package/tests/fixtures/coronasha-detail.html +0 -41
  103. package/tests/fixtures/coronasha-search.html +0 -61
  104. package/tests/fixtures/gihyo-detail.html +0 -42
  105. package/tests/fixtures/gihyo-search.json +0 -54
  106. package/tests/fixtures/impress-detail-epub.html +0 -746
  107. package/tests/fixtures/impress-detail-social.html +0 -689
  108. package/tests/fixtures/lambdanote-search.html +0 -66
  109. package/tests/fixtures/manatee-detail.html +0 -53
  110. package/tests/fixtures/manatee-search.html +0 -59
  111. package/tests/fixtures/maruzen-detail.html +0 -51
  112. package/tests/fixtures/maruzen-search.html +0 -60
  113. package/tests/fixtures/optronics-detail.html +0 -30
  114. package/tests/fixtures/optronics-search.html +0 -75
  115. package/tests/fixtures/oreilly-detail.html +0 -52
  116. package/tests/fixtures/oreilly-ebook-list.html +0 -53
  117. package/tests/fixtures/peaks-detail.html +0 -39
  118. package/tests/fixtures/peaks-top.html +0 -50
  119. package/tests/fixtures/personal-media-detail.html +0 -32
  120. package/tests/fixtures/personal-media-search.html +0 -39
  121. package/tests/fixtures/rutles-detail.html +0 -32
  122. package/tests/fixtures/rutles-search.html +0 -62
  123. package/tests/fixtures/saiensu-detail.html +0 -41
  124. package/tests/fixtures/saiensu-search.html +0 -65
  125. package/tests/fixtures/seshop-detail.html +0 -45
  126. package/tests/fixtures/seshop-search.html +0 -58
  127. package/tests/fixtures/tatsu-zine-detail-free.html +0 -22
  128. package/tests/fixtures/tatsu-zine-search.html +0 -40
  129. package/tests/fixtures/techbookfest-search.json +0 -73
  130. package/tests/unit/adapters/base.test.ts +0 -441
  131. package/tests/unit/adapters/publishers/book-tech.test.ts +0 -186
  132. package/tests/unit/adapters/publishers/born-digital.test.ts +0 -194
  133. package/tests/unit/adapters/publishers/coronasha.test.ts +0 -207
  134. package/tests/unit/adapters/publishers/gihyo.test.ts +0 -137
  135. package/tests/unit/adapters/publishers/impress.test.ts +0 -129
  136. package/tests/unit/adapters/publishers/lambdanote.test.ts +0 -85
  137. package/tests/unit/adapters/publishers/manatee.test.ts +0 -165
  138. package/tests/unit/adapters/publishers/maruzen-publishing.test.ts +0 -179
  139. package/tests/unit/adapters/publishers/optronics.test.ts +0 -208
  140. package/tests/unit/adapters/publishers/oreilly-japan.test.ts +0 -194
  141. package/tests/unit/adapters/publishers/peaks.test.ts +0 -177
  142. package/tests/unit/adapters/publishers/personal-media.test.ts +0 -199
  143. package/tests/unit/adapters/publishers/rutles.test.ts +0 -173
  144. package/tests/unit/adapters/publishers/saiensu.test.ts +0 -169
  145. package/tests/unit/adapters/publishers/seshop.test.ts +0 -174
  146. package/tests/unit/adapters/publishers/tatsu-zine.test.ts +0 -172
  147. package/tests/unit/adapters/publishers/techbookfest.test.ts +0 -94
  148. package/tests/unit/adapters/registry.test.ts +0 -37
  149. package/tests/unit/application/get-book-detail.test.ts +0 -102
  150. package/tests/unit/application/search-books.test.ts +0 -137
  151. package/tsconfig.json +0 -17
@@ -1,36 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(npm info:*)",
5
- "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\('version:', d.get\\('version'\\)\\); print\\('exports:', list\\(d.get\\('exports', {}\\).keys\\(\\)\\)[:20]\\)\")",
6
- "WebFetch(domain:gihyo.jp)",
7
- "WebFetch(domain:www.lambdanote.com)",
8
- "Bash(curl:*)",
9
- "Bash(python3 -c ':*)",
10
- "Bash(npx vitest:*)",
11
- "Bash(git mv:*)",
12
- "Bash(git add:*)",
13
- "Bash(git commit -m ':*)",
14
- "Bash(node:*)",
15
- "Bash(npm install:*)",
16
- "Bash(curl -s \"https://wgn-obs.shop-pro.jp/?mode=srh&keyword=HTML\" -A \"Mozilla/5.0\")",
17
- "Read(//tmp/**)",
18
- "Bash(npm test:*)",
19
- "Bash(python3:*)",
20
- "WebFetch(domain:www.personal-media.co.jp)",
21
- "Bash(grep -rn \"export.*Element\" node_modules/cheerio/dist/esm/*.d.ts)",
22
- "Bash(npx tsc:*)",
23
- "Bash(npx oxlint:*)",
24
- "Bash(npm run:*)",
25
- "Bash(curl -s \"https://book.impress.co.jp/books/1124101031\")",
26
- "Bash(curl -s \"https://book.impress.co.jp/books/1125101113\")",
27
- "Bash(cp /tmp/impress-detail-social.html tests/fixtures/impress-detail-social.html)",
28
- "Bash(cp /tmp/impress-detail-nodrm.html tests/fixtures/impress-detail-epub.html)",
29
- "Bash(wc -c tests/fixtures/impress-detail-*.html)",
30
- "Bash(git -C /Users/megurine/repo/js/techbook-mcp status)",
31
- "Bash(git -C /Users/megurine/repo/js/techbook-mcp diff)",
32
- "Bash(git:*)",
33
- "Bash(npm pkg:*)"
34
- ]
35
- }
36
- }
@@ -1,105 +0,0 @@
1
- ---
2
- name: techbook-mcp-release-prep
3
- description: Prepare a techbook-mcp release by bumping the version, updating the changelog, and running verification. Use when the user asks to prepare the next version, cut a release, or make sure versioned files are consistent before tagging.
4
- ---
5
-
6
- # techbook-mcp Release Prep
7
-
8
- Follow this workflow when preparing a new `techbook-mcp` release.
9
-
10
- ## Decide the Next Version
11
-
12
- Choose the next semantic version before touching any files.
13
-
14
- - `patch` (0.x.Y) — bug fixes, no new functionality
15
- - `minor` (0.X.0) — new adapters, new features, backward-compatible changes
16
- - `major` (X.0.0) — breaking changes to the MCP tool interface or `BookRecord` schema
17
-
18
- If the user specifies a version, use it. Otherwise infer from the unreleased commits since the last tag.
19
-
20
- ## Update Versioned Files
21
-
22
- Update these files together in one pass:
23
-
24
- - `CHANGELOG.md`
25
- - `package.json` — `"version"` field
26
- - `package-lock.json` — `"version"` field in the root object (same value as `package.json`)
27
-
28
- ### CHANGELOG.md Rules
29
-
30
- - If `CHANGELOG.md` does not exist yet, create it with the standard Keep a Changelog header.
31
- - Add a new `## [x.y.z] - YYYY-MM-DD` section directly below `## [Unreleased]`.
32
- - Use Keep a Changelog section headings as needed: `Added`, `Changed`, `Deprecated`, `Removed`, `Fixed`, `Security`.
33
- - Group the same kinds of changes under the same heading.
34
- - Write entries user-facing: what changed, not how. Skip internal refactors unless they affect users.
35
- - List new publisher adapters under `Added`.
36
- - Preserve the release date in every version heading.
37
- - Keep the `[Unreleased]` section empty after moving its contents to the new release section.
38
- - Update the `[Unreleased]` compare link and add the new release compare link at the bottom of the file.
39
- - Keep version headings and bottom-of-file links consistent so releases and compare ranges remain linkable.
40
-
41
- ### CHANGELOG.md Template (first release)
42
-
43
- ```markdown
44
- # Changelog
45
-
46
- All notable changes to this project will be documented in this file.
47
-
48
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
49
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
50
-
51
- ## [Unreleased]
52
-
53
- ## [x.y.z] - YYYY-MM-DD
54
-
55
- ### Added
56
-
57
- - ...
58
-
59
- [Unreleased]: https://github.com/zonuexe/techbook-mcp/compare/vx.y.z...HEAD
60
- [x.y.z]: https://github.com/zonuexe/techbook-mcp/releases/tag/vx.y.z
61
- ```
62
-
63
- ## Verify Before Committing
64
-
65
- Run all checks in order:
66
-
67
- ```bash
68
- npm test
69
- ```
70
-
71
- ```bash
72
- npm run lint
73
- ```
74
-
75
- ```bash
76
- npm run build
77
- ```
78
-
79
- All three must pass. Do not commit if any check fails.
80
-
81
- ## Commit the Release
82
-
83
- Prefer a single release-prep commit containing:
84
-
85
- - version bump in `package.json` and `package-lock.json`
86
- - `CHANGELOG.md` update
87
-
88
- Use this commit message format:
89
-
90
- ```text
91
- Bump up version to x.y.z
92
- ```
93
-
94
- Do not include other unrelated changes in the release commit.
95
-
96
- ## Quick Checklist
97
-
98
- - Working tree starts clean or you understand every pending change.
99
- - `CHANGELOG.md` has a new `## [x.y.z] - YYYY-MM-DD` section with user-facing entries.
100
- - `package.json` and `package-lock.json` both show the new version.
101
- - Bottom-of-file links in `CHANGELOG.md` are consistent.
102
- - `npm test` passed.
103
- - `npm run lint` passed.
104
- - `npm run build` passed.
105
- - Commit message follows `chore: release x.y.z`.
@@ -1,72 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- push:
5
- branches: [main, master]
6
- pull_request:
7
- branches: [main, master]
8
-
9
- jobs:
10
- test:
11
- runs-on: ubuntu-latest
12
-
13
- strategy:
14
- matrix:
15
- node-version: [22.x]
16
-
17
- steps:
18
- - uses: actions/checkout@v4
19
-
20
- - name: Use Node.js ${{ matrix.node-version }}
21
- uses: actions/setup-node@v4
22
- with:
23
- node-version: ${{ matrix.node-version }}
24
- cache: npm
25
-
26
- - name: Install dependencies
27
- run: npm ci
28
-
29
- - name: Lint
30
- run: npm run lint
31
-
32
- - name: Type check
33
- run: npx tsc --noEmit
34
-
35
- - name: Run tests
36
- run: npm test
37
-
38
- - name: Build
39
- run: npm run build
40
-
41
- bun:
42
- runs-on: ubuntu-latest
43
- steps:
44
- - uses: actions/checkout@v4
45
-
46
- - uses: oven-sh/setup-bun@v2
47
-
48
- - name: Install dependencies
49
- run: bun install
50
-
51
- - name: Run tests
52
- run: bun test
53
-
54
- deno:
55
- runs-on: ubuntu-latest
56
- steps:
57
- - uses: actions/checkout@v4
58
-
59
- - uses: denoland/setup-deno@v2
60
- with:
61
- deno-version: v2.x
62
-
63
- - name: Install Node deps (for node_modules)
64
- uses: actions/setup-node@v4
65
- with:
66
- node-version: 22.x
67
- cache: npm
68
-
69
- - run: npm ci
70
-
71
- - name: Run tests
72
- run: deno test --allow-read --no-check --sloppy-imports tests/
package/.oxlintrc.json DELETED
@@ -1,12 +0,0 @@
1
- {
2
- "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
3
- "plugins": ["typescript", "unicorn"],
4
- "rules": {
5
- "no-unused-vars": "error",
6
- "no-console": "warn",
7
- "unicorn/no-array-for-each": "error",
8
- "unicorn/prefer-string-slice": "error",
9
- "typescript/consistent-type-imports": "error"
10
- },
11
- "ignorePatterns": ["dist/**", "node_modules/**"]
12
- }
package/AGENTS.md DELETED
@@ -1,100 +0,0 @@
1
- # techbook-mcp — AI エージェント向けガイド
2
-
3
- 日本語技術書の書誌情報を出版社公式サイト・APIから取得するMCPサーバー。
4
- 詳細な設計は [docs/design-doc.md](docs/design-doc.md) を参照。
5
-
6
- ## コミットメッセージ規約
7
-
8
- Conventional Commits は使わない。コミットメッセージは端的な日本語または英語の命令形で書く。
9
-
10
- ```
11
- robots.txt チェックを追加し結果を6時間キャッシュする
12
- Add robots.txt check with 6-hour cache
13
- ```
14
-
15
- ## 開発コマンド
16
-
17
- ```bash
18
- npm install
19
- npm test # ユニットテスト実行 (node:test)
20
- npm run build # TypeScript コンパイル → dist/
21
- ```
22
-
23
- ## コーディング規約
24
-
25
- - **新しいアダプターを追加するときは必ずテストも書く**(`tests/unit/adapters/publishers/{id}.test.ts`)
26
- - テストは `MockHttpClient` + `NullCacheStore` + `CheerioHtmlParser` の組み合わせで書く
27
- - フィクスチャHTMLは `tests/fixtures/` に配置し、実サイトの構造を忠実に再現する
28
- - `fetchText()` はキャッシュ・ヘッダーを内包するため、アダプター内では直接 `deps.http.get()` を呼ばない
29
- - Referer ヘッダーが必要なサイトは `fetchText(url, deps, { Referer: "..." })` の第3引数を使う
30
- - 著者名から役割語(著・訳・編・監修・監訳など)を除去すること
31
- - 価格は税込み整数(円)で `BookRecord.price` に格納する
32
- - `publisher` フィールドには実際の出版社名を入れる(ストアプラットフォーム名ではない)
33
-
34
- ## 新しい出版社アダプターを追加するとき
35
-
36
- `docs/design-doc.md` の「新しいアダプターの追加手順」を参照。要点は以下:
37
-
38
- 1. `src/adapters/publishers/{id}.ts` — `PublisherAdapter` インターフェースを実装
39
- 2. `tests/fixtures/{id}-search.html` + `{id}-detail.html` — 実サイトHTMLのスナップショット
40
- 3. `tests/unit/adapters/publishers/{id}.test.ts` — `MockHttpClient` でユニットテスト
41
- 4. `src/adapters/publishers/base.ts` — 必要に応じて `EBOOK_STORE_PATTERNS` に追加
42
- 5. `src/adapters/publishers/registry.ts` — `DEFAULT_PUBLISHERS` に登録
43
-
44
- ## DRM 分類の判断基準
45
-
46
- 新しいストアを `EBOOK_STORE_PATTERNS` に追加する際の判断順:
47
- 1. **free** — 公式が明言、または購入して透かし等がないことを確認済み
48
- 2. **social** — 購入者情報(メールアドレス等)が埋め込まれるが技術的制限なし
49
- 3. **password_pdf** — PDFにパスワードがかかる(標準ビューアで開ける)
50
- 4. **drm** — 専用ビューアーが必要、または上記いずれでもない場合
51
-
52
- ## アーキテクチャ上の制約
53
-
54
- - ポート (`HttpClient`, `HtmlParser`, `CacheStore`) はインターフェースのみ。実装を直接 import しない
55
- - `DrmType` に新しい値を追加するときは `src/domain/book.ts` と `src/mcp/server.ts` の両方を更新する
56
-
57
- ## テスト方針
58
-
59
- テストフレームワークは `node:test` + `node:assert/strict` を使う(vitest は使わない)。
60
-
61
- ```typescript
62
- import { describe, it } from "node:test";
63
- import assert from "node:assert/strict";
64
-
65
- // 標準的なテストセットアップ
66
- function makeDeps(http: MockHttpClient) {
67
- return { http, parser: new CheerioHtmlParser(), cache: new NullCacheStore() };
68
- }
69
-
70
- // GET のモック(URL前方一致)
71
- const http = new MockHttpClient()
72
- .addResponse("https://example.com/search", { status: 200, body: html });
73
-
74
- // POST のモック (GraphQL等)
75
- const http = new MockHttpClient()
76
- .addPostResponse("https://api.example.com/graphql", { status: 200, body: json });
77
-
78
- // vitest → node:assert の主な対応
79
- // expect(x).toBe(y) → assert.strictEqual(x, y)
80
- // expect(x).toEqual(y) → assert.deepStrictEqual(x, y)
81
- // expect(x).toMatchObject(y) → assert.partialDeepStrictEqual(x, y)
82
- // expect(x).toHaveLength(n) → assert.strictEqual(x.length, n)
83
- // expect(x).toContain(s) → assert.ok(x.includes(s))
84
- // expect(x).toMatch(/r/) → assert.match(x, /r/)
85
- // await expect(p).rejects.toThrow("msg") → await assert.rejects(p, /msg/)
86
-
87
- // モック関数: node:test の mock.fn() は Bun 非対応のため、テストファイル内に
88
- // ローカルで mockFn() ヘルパーを定義して使う(Node.js・Bun・Deno 共通で動作)
89
- // vi.fn().mockResolvedValue(v) → mockFn(async () => v)
90
- // fn.toHaveBeenCalledOnce() → assert.strictEqual(fn.mock.callCount(), 1)
91
- ```
92
-
93
- ## よくある落とし穴
94
-
95
- - **EUC-JP サイト**: `shop.rutles.net` はクエリを EUC-JP エンコードしないとヒットしない → `iconv-lite` を使用
96
- - **XSRF-TOKEN**: `techbookfest.org` GraphQL はダブルサブミットCookieパターン必須
97
- - **Referer 必須**: `maruzen-publishing.co.jp` の検索は Referer なしで 403
98
- - **機関向けストア除外**: `kw.maruzen.co.jp`(Knowledge Worker)は個人向けではないため除外
99
- - **ローカルフィルタ型**: `oreilly-japan` と `peaks` は検索APIがなくトップページ/一覧をローカルフィルタ
100
- - **著者のみ検索不可**: ローカルフィルタ型アダプターは `!query.title` のとき `[]` を返す(HTTP呼ばない)
package/deno.json DELETED
@@ -1,3 +0,0 @@
1
- {
2
- "nodeModulesDir": "auto"
3
- }
@@ -1,31 +0,0 @@
1
- import type { CacheStore } from "../../ports/cache.js";
2
-
3
- interface CacheEntry {
4
- value: string;
5
- expiresAt?: number;
6
- }
7
-
8
- export class MemoryCacheStore implements CacheStore {
9
- private readonly store = new Map<string, CacheEntry>();
10
-
11
- async get(key: string): Promise<string | null> {
12
- const entry = this.store.get(key);
13
- if (!entry) return null;
14
- if (entry.expiresAt !== undefined && Date.now() > entry.expiresAt) {
15
- this.store.delete(key);
16
- return null;
17
- }
18
- return entry.value;
19
- }
20
-
21
- async set(key: string, value: string, ttlSeconds?: number): Promise<void> {
22
- this.store.set(key, {
23
- value,
24
- expiresAt: ttlSeconds !== undefined ? Date.now() + ttlSeconds * 1000 : undefined,
25
- });
26
- }
27
-
28
- async delete(key: string): Promise<void> {
29
- this.store.delete(key);
30
- }
31
- }
@@ -1,8 +0,0 @@
1
- import type { CacheStore } from "../../ports/cache.js";
2
-
3
- /** テスト・デバッグ用。キャッシュを一切行わない。 */
4
- export class NullCacheStore implements CacheStore {
5
- async get(_key: string): Promise<null> { return null; }
6
- async set(_key: string, _value: string, _ttlSeconds?: number): Promise<void> {}
7
- async delete(_key: string): Promise<void> {}
8
- }
@@ -1,50 +0,0 @@
1
- import * as cheerio from "cheerio";
2
- import type { Element } from "domhandler";
3
- import type { HtmlParser, HtmlDocument, HtmlElement } from "../../ports/html-parser.js";
4
-
5
- class CheerioElement implements HtmlElement {
6
- constructor(
7
- private readonly $: cheerio.CheerioAPI,
8
- private readonly el: Element,
9
- ) {}
10
-
11
- text(): string {
12
- return this.$(this.el).text().trim();
13
- }
14
-
15
- html(): string | null {
16
- return this.$(this.el).html();
17
- }
18
-
19
- attr(name: string): string | undefined {
20
- return this.$(this.el).attr(name);
21
- }
22
-
23
- find(selector: string): HtmlElement[] {
24
- return this.$(this.el)
25
- .find(selector)
26
- .toArray()
27
- .map(el => new CheerioElement(this.$, el as Element));
28
- }
29
- }
30
-
31
- class CheerioDocument implements HtmlDocument {
32
- constructor(private readonly $: cheerio.CheerioAPI) {}
33
-
34
- select(selector: string): HtmlElement[] {
35
- return this.$(selector)
36
- .toArray()
37
- .map(el => new CheerioElement(this.$, el as Element));
38
- }
39
-
40
- selectOne(selector: string): HtmlElement | null {
41
- return this.select(selector)[0] ?? null;
42
- }
43
- }
44
-
45
- export class CheerioHtmlParser implements HtmlParser {
46
- parse(html: string): HtmlDocument {
47
- const $ = cheerio.load(html);
48
- return new CheerioDocument($);
49
- }
50
- }
@@ -1,47 +0,0 @@
1
- import type { HttpClient, RequestOptions, HttpResponse } from "../../ports/http.js";
2
-
3
- class FetchHttpResponse implements HttpResponse {
4
- constructor(private readonly response: Response) {}
5
-
6
- get status(): number {
7
- return this.response.status;
8
- }
9
-
10
- get url(): string {
11
- return this.response.url;
12
- }
13
-
14
- text(): Promise<string> {
15
- return this.response.text();
16
- }
17
-
18
- header(name: string): string | null {
19
- return this.response.headers.get(name);
20
- }
21
- }
22
-
23
- export class FetchHttpClient implements HttpClient {
24
- async get(url: string, options?: RequestOptions): Promise<HttpResponse> {
25
- const init: RequestInit = {
26
- headers: options?.headers,
27
- };
28
- if (options?.timeout !== undefined) {
29
- init.signal = AbortSignal.timeout(options.timeout);
30
- }
31
- const response = await fetch(url, init);
32
- return new FetchHttpResponse(response);
33
- }
34
-
35
- async post(url: string, body: string, options?: RequestOptions): Promise<HttpResponse> {
36
- const init: RequestInit = {
37
- method: "POST",
38
- body,
39
- headers: { "Content-Type": "application/json", ...options?.headers },
40
- };
41
- if (options?.timeout !== undefined) {
42
- init.signal = AbortSignal.timeout(options.timeout);
43
- }
44
- const response = await fetch(url, init);
45
- return new FetchHttpResponse(response);
46
- }
47
- }
@@ -1,77 +0,0 @@
1
- import type { HttpClient, RequestOptions, HttpResponse } from "../../ports/http.js";
2
-
3
- export interface MockResponseData {
4
- status: number;
5
- body: string;
6
- headers?: Record<string, string>;
7
- }
8
-
9
- class MockHttpResponse implements HttpResponse {
10
- constructor(
11
- private readonly data: MockResponseData,
12
- private readonly requestUrl: string,
13
- ) {}
14
-
15
- get status(): number { return this.data.status; }
16
- get url(): string { return this.requestUrl; }
17
- async text(): Promise<string> { return this.data.body; }
18
- header(name: string): string | null {
19
- return this.data.headers?.[name.toLowerCase()] ?? null;
20
- }
21
- }
22
-
23
- export class MockHttpClient implements HttpClient {
24
- private readonly handlers = new Map<string, MockResponseData>();
25
- private readonly postHandlers = new Map<string, MockResponseData>();
26
- private readonly _calls: string[] = [];
27
-
28
- /** GET: URL の前方一致でレスポンスを登録する */
29
- addResponse(urlPrefix: string, data: MockResponseData): this {
30
- this.handlers.set(urlPrefix, data);
31
- return this;
32
- }
33
-
34
- /** POST: URL の前方一致でレスポンスを登録する */
35
- addPostResponse(urlPrefix: string, data: MockResponseData): this {
36
- this.postHandlers.set(urlPrefix, data);
37
- return this;
38
- }
39
-
40
- get calls(): readonly string[] {
41
- return this._calls;
42
- }
43
-
44
- async get(url: string, _options?: RequestOptions): Promise<HttpResponse> {
45
- this._calls.push(url);
46
-
47
- // 完全一致を優先
48
- if (this.handlers.has(url)) {
49
- return new MockHttpResponse(this.handlers.get(url)!, url);
50
- }
51
-
52
- // 前方一致
53
- for (const [prefix, data] of this.handlers) {
54
- if (url.startsWith(prefix)) {
55
- return new MockHttpResponse(data, url);
56
- }
57
- }
58
-
59
- throw new Error(`MockHttpClient: no handler for GET: ${url}`);
60
- }
61
-
62
- async post(url: string, _body: string, _options?: RequestOptions): Promise<HttpResponse> {
63
- this._calls.push(url);
64
-
65
- if (this.postHandlers.has(url)) {
66
- return new MockHttpResponse(this.postHandlers.get(url)!, url);
67
- }
68
-
69
- for (const [prefix, data] of this.postHandlers) {
70
- if (url.startsWith(prefix)) {
71
- return new MockHttpResponse(data, url);
72
- }
73
- }
74
-
75
- throw new Error(`MockHttpClient: no handler for POST: ${url}`);
76
- }
77
- }