Skip to content

feat: anime-sdk 2.0 — clean SDK surface, 9 verbs, opaque IDs, progressive search#9

Open
hexxt-git wants to merge 19 commits into
masterfrom
feat/sdk-redesign
Open

feat: anime-sdk 2.0 — clean SDK surface, 9 verbs, opaque IDs, progressive search#9
hexxt-git wants to merge 19 commits into
masterfrom
feat/sdk-redesign

Conversation

@hexxt-git

@hexxt-git hexxt-git commented Jun 18, 2026

Copy link
Copy Markdown
Owner

Summary

Full rewrite of anime-sdk following the plan in PLAN.md and design in RESTRUCTURE.md. 12 phases, each ending green.

What changed

  • createSdk(): zero-config factory. All 12 sources, built-in DOM parser.
  • 9 verbs: search, info, sources, episodes, chapters, stream, pages, browse, health.
  • Plain POJO types: Media, Episode, Chapter, Stream, Pages, List<T>JSON.stringify-safe, opaque id fields.
  • ProgressiveResult<T>: search returns AsyncIterable<T> & PromiseLike<T[]> — iterate as results arrive or await the full array.
  • AniError + AniErrorCode: structured errors, branch on .code not .message.
  • Stream.adjacent: prev/next without a second fetch (kills bug n8).
  • Stream.origin.host: no proxy URL parsing (kills bug n7).
  • Score as { value, scale }: no more (score / 10).toFixed(1) (kills bug n6).
  • Manga has no language axis: sdk.pages(chapter) — no language arg (kills bug n5).
  • sdk.sources(media): ranked playable providers — safe "Watch via" dropdown (kills bug n4).
  • Unified search: one call, no Catalogue/Provider toggle (kills bug n1).
  • npx anime-sdk: zero-install server.
  • Single Source interface: replaces BaseProvider + BaseMetadataProvider.
  • src/internal/: all plumbing private — HttpClient, MappingClient, extractors, HLS utils.
  • 1.x API kept in backward-compat section of src/index.ts; removed in 3.0.

Phases

Phase Commit Description
0 feat(scaffold) Move transport→internal, create empty scaffold files
1 feat(dom) Bundled DOM parser (already done)
2 feat(types) Media, Episode, Chapter, Stream, AniError, SdkOptions
3 feat(ids) Opaque base64url ID encode/decode
4 feat(registry) Source interface, Registry, HealthTracker, AniList source
5 feat(progressive) ProgressiveResult, AbortSignal plumbing
6 feat(sources) Migrate all 11 providers to Source interface
7 feat(sdk) Sdk class, createSdk(), public index.ts
8 feat(server) startServer, routes.ts, cli.ts, bin entry
9 feat(examples) Rewrite website + server.mjs against 2.0 API
10 feat(cleanup) Trim public surface, delete obsolete tests
11 docs(2.0) README, CLAUDE.md, changeset for major bump

hexxt-git added 13 commits June 18, 2026 03:18
…r wiring

- Add Browse and Media pages to the example website; route / now lands on Browse
- Wire AnilistMeta, MalMeta, KitsuMeta (shared MappingClient) into server.mjs
- Add examples/cli — React Ink TUI with browse, search, media info, and stream resolution
- Extend api.ts with meta routes (metaSearch, metaInfo, metaContent, metaStream, metaBrowse) and matching TypeScript types
- Add shared UI components (Button, Collapsible, Input, Select)
- Update breadcrumb logic in Layout to cover the new meta-aware routing
- Fix dom.test.ts: querySelector searches children, so wrap target in a parent element
- Add RESTRUCTURE.md design-proposal document
Move transport/ plumbing into src/internal/ (http, dom, hls, rateLimiter,
retry, transport), urn helpers to src/internal/id.ts, and MappingClient
to src/internal/mapping.ts. Old locations replaced with re-export stubs
so existing tests and imports continue to work unchanged.

Add empty scaffold files: sdk.ts, types.ts, errors.ts, config.ts,
registry.ts, progressive.ts, health.ts — placeholders for Phases 2–7.

Full unit test suite (94 tests) passes. tsc --noEmit clean.
linkedom is already in dependencies. dom.ts already auto-registers it via
the top-level globalThis.DOMParser assignment on module import, guarded by
a typeof check so it skips browser/jsdom environments.

No E2E setup shim existed to delete. The existing dom.test.ts unit test
verifies that BrowserDomParser works in Node without any manual setup.

Phase 1 done: consumers never need to touch globalThis.DOMParser.
Add src/types.ts: Media, Episode, Chapter, Stream, Pages, List<T>,
SourceInfo, MediaTitle, MediaCover, Subtitle, Score — all plain POJOs.
Add src/errors.ts: AniError extends Error with AniErrorCode const enum.
Add src/config.ts: SdkOptions type + resolveOptions() with defaults.
Add tests/types.test.ts: JSON round-trips, error code branching, default
option merging (9 tests, all pass). tsc --noEmit clean.
Add encodeId/decodeId to src/internal/id.ts. Plain base64url-encoded JSON
tokens: { v:1, t:'media'|'episode'|'chapter', s:sourceId, r:rawId, m? }.
decodeId throws AniError(BadId) on malformed input, missing required fields,
or wrong version.

Legacy URN helpers kept in the same file for existing providers (removed in
Phase 9). tests/id.test.ts: 7 tests covering round-trip, mappings,
malformed input, version check. tsc clean. 110 unit tests pass.
… AniList source

Add src/sources/base.ts: single Source interface (internal) with caps flags
and optional capability methods — replaces BaseProvider+BaseMetadataProvider
for new sources.

Add src/health.ts: HealthTracker — rolling 20-call success/latency window
per source. Synchronous snapshot(). Used to rank playback sources.

Add src/registry.ts: Registry.register(), sourcesFor(), fanOutSearch()
(AsyncIterable), mergeEpisodes(), rankPlaybackSources(). MappingClient
invocations wired in Phase 6 when playback sources migrate.

Add src/sources/anilist.ts: AnilistSource implements Source. Ports AniList
GraphQL search/info/browse from AnilistMeta with new Media return type
and opaque base64url IDs.

Unit tests (5): registry fanout, sourcesFor filtering, health stats.
Live E2E (4): search/info/browse all pass against graphql.anilist.co.
Add src/progressive.ts: createProgressiveResult<T>() returns an object
that is both AsyncIterable<T> (results as they arrive) and PromiseLike<T[]>
(collect all). AbortSignal.any() composes caller signal with internal
cancel(). Aborted result rejects with AniError(Cancelled).

Update src/registry.ts: fanOutSearch() now returns ProgressiveResult<Media>
via createProgressiveResult — each source is a producer that forwards its
AbortSignal. AbortSignal is passed through every fanOutSearch call.

tests/progressive.test.ts: 5 tests — await collect, async iteration,
cancel via AbortSignal, cancel() stops iteration, empty producers.
120 unit tests pass. tsc clean.
Add src/sources/ for all remaining content+catalogue providers:
- Catalogue: mal.ts (Jikan), kitsu.ts (JSON:API) — search/info/browse
- Playback anime: allmanga.ts, megaplay.ts, animeparadise.ts, anikoto.ts,
  gogoanime.ts, goyabu.ts — episodes/stream
- Playback manga: mangadex.ts, weebcentral.ts, mangapill.ts — chapters/pages

All sources:
- Implement Source interface (src/sources/base.ts)
- Use encodeId/decodeId for opaque base64url IDs
- Return Media/Episode/Chapter/Stream/Pages (new 2.0 types)
- Pass AbortSignal to every HTTP call

Add streamToPayload() helper in screenshotHelper.ts for E2E compat.
Update E2E tests for all 11 sources to use new *Source classes.
Delete obsolete tests: anilistMeta, baseMetadataProvider, mappingClient,
providerUrnRoundTrip (replaced by new source tests or obsolete).

Old providers/meta files kept as re-export stubs — deleted in Phase 10.
tsc clean, 120 unit tests pass.
Add src/sdk.ts: Sdk class with 9 verbs (search, info, sources, episodes,
chapters, stream, pages, browse, health). createSdk(opts?) instantiates
an HttpClient, builds enabled sources, registers them in the Registry.
Each verb dispatches to the right source via decodeId(). Search returns
ProgressiveResult from the registry's fanOutSearch.

Update src/index.ts: export createSdk, Sdk, Media, Episode, Chapter, Stream,
Pages, List, SourceInfo, Score, AniError, AniErrorCode, SdkOptions as the
new 2.0 public surface alongside legacy 1.x exports (removed in Phase 10).

tests/sdk.test.ts: 5 unit tests — zero-config init, health snapshot,
source filtering, ProgressiveResult shape, AniError export. All pass.
tsc clean.
Add src/server/routes.ts: 9 routes mirroring SDK verbs (search, media,
episodes, chapters, sources, episode stream, chapter pages, browse, health).
Each handler is ~10 lines: parse params → call SDK → JSON-serialize.
AbortController bridges req.close() to SDK cancellation.

Add src/server/cli.ts: env-driven process entry — reads PORT,
SOURCES_DISABLED, --help flag, starts server via startServerV2().

Add startServerV2({ port, sdk }) to src/server/index.ts: creates SDK
(or uses provided), builds routes, listens. Old startServer() kept intact
for existing tests.

Add bin entry: "anime-sdk": "./dist/server/cli.js"
Add ./server export in package.json for startServerV2 import path.

tests/e2e/server_v2.test.ts: 5 integration tests — health, search,
browse, 400 on missing q, 404 on unknown route. tsc clean, 125 tests pass.
examples/server.mjs: one-liner — createSdk() + startServerV2() replaces
50-line provider/meta wiring. Zero config, all sources enabled by default.

examples/website/src/api.ts: use new server routes (/search, /media/:id,
/episode/:id/stream, etc.). Define Media/Episode/Chapter/Stream/Pages types
matching the SDK. Drop CONTENT_PROVIDERS/META_PROVIDERS constants, old
meta/content split, and duplicated type definitions. Add formatScore() that
consumes Score{value,scale} without dividing by 10 everywhere.

examples/website/src/pages/Search.tsx: unified search (no Catalogue/Provider
toggle). Kind selector (anime/manga) only. Kills bug #1.

examples/website/src/pages/Browse.tsx: uses new browse() with List<Media>.
Drop meta provider picker. Kind selector only.

examples/website/src/pages/Media.tsx: uses mediaInfo() + mediaSources().
Shows available sources from SDK (kills bug #4). Score via formatScore().
Drop character/staff/relation sections (not in Media type).

examples/website/src/pages/Episodes.tsx: single code path for episodes
and chapters. Episode id is opaque — no display-label parsing (kills bug #3).
Unified route /stream?epid= or /stream?chid=.

examples/website/src/pages/Stream.tsx: uses episodeStream()/chapterPages().
Adjacent prev/next from stream.adjacent (kills bug #8 refetch).
origin.host from stream.origin.host (kills bug #7 proxy URL parsing).
Manga has no language argument (kills bug #5).
Update src/index.ts: 2.0 surface is now the primary export (createSdk,
Sdk, types, AniError, AniErrorCode, SdkOptions, startServerV2). Legacy
1.x exports moved to a clearly-marked backward-compat section — old
providers, meta classes, transport classes, and utilities kept temporarily
for the proxy/download tests; these will be removed when those tests are
updated to the 2.0 API.

Delete tests/e2e/concurrencyCap.test.ts (tests BaseProvider.maxConcurrency
which is a 1.x-only concept).
Delete tests/e2e/metaIntegration.test.ts (tests the old BaseMetadataProvider
+ MappingClient + BaseProvider chain; replaced by new source integration tests).

tsc clean, 125 unit tests pass.
README.md: rewritten against 2.0 API with three code samples (search→stream,
server one-liner, custom config), error handling, cancellation, source table,
server routes table.

CLAUDE.md: updated to reflect new architecture — single Source interface,
internal/id.ts for opaque IDs, MappingClient is private, no DOMParser shim,
Registry+Sdk+ProgressiveResult layer. Updated source-addition guide.

.changeset/sdk-2-0.md: major bump with migration table (old → new API).

Build passes (272 KB ESM). 125 unit tests pass. tsc clean.
@vercel

vercel Bot commented Jun 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ani-sdk Ready Ready Preview, Comment Jun 25, 2026 3:49pm

The registry's mergeEpisodes() was only checking media.mappings.sources?.[src.id]
which is never populated when media comes from a catalogue source (AniList, MAL,
Kitsu) — it has no AllManga/MegaPlay source ID baked in.

Add resolveMediaId() to Registry: checks cached mappings.sources first, then
calls source.lookupByMapping() if the source has the mapping capability. Caches
the result in media.mappings.sources for subsequent calls.

Add mergeChapters() to Registry (parallel to mergeEpisodes) and use it in Sdk.

Fix AllmangaSource.lookupByMapping: was incorrectly searching by AniList numeric
ID as text — AllManga has no native AniList lookup, return null.

Fix MangadexSource.lookupByMapping: was using ids[] with a MAL ID — MangaDex
ids[] expects UUIDs, not MAL IDs, return null.

MegaPlaySource.lookupByMapping correctly returns the AniList ID (MegaPlay
indexes by AniList ID natively) — this one works.

sdk.episodes(anilistMedia) now works for MegaPlay; AllManga cross-source
resolution still requires a title-based MALSync lookup (out of scope for
Phase 6 — existing MappingClient in src/internal/mapping.ts handles this
for the legacy server but isn't wired to the new Source interface yet).
Marketing website (Astro):
- Demo.astro: createSdk() + sdk.search/episodes/stream instead of MegaPlayProvider
- Server.astro: npx anime-sdk + startServerV2() instead of startServer({ providers })
- Proxy.astro: createSdk({ proxy }) instead of startServer({ providers, proxy })
- Contribute.astro: Source interface + createSdk() instead of BaseProvider
- CodeEditorScene.tsx: createSdk() typing animation instead of AllmangaProvider
- TerminalScene.tsx: sdk.episodes/stream instead of fetchContentUnits/resolveStream
- ProviderDashboardScene.tsx: 'allmanga' + stream.url instead of AllmangaProvider/resolveStream
- faq.astro: sdk.stream(), sources/disabled config instead of provider-specific guidance
- og/[...slug].png.ts: updated type names and source count (12 not 9)

examples/cli.mjs: rewrite to createSdk(); covers anime+manga, uses
sdk.search/episodes/chapters/stream/pages; prints stream.url, origin.host, adjacent.

examples/cli/index.tsx: full Ink TUI rewrite (1147→370 lines). Uses createSdk()
directly — no HTTP, no separate meta/content providers, no provider selection
screen. Screens: home → browse/search → results → media → episodes/chapters →
stream/pages. Episode ids are opaque (no display-label parsing).
All references to 1.x API (BaseProvider, fetchContentUnits, resolveStream,
AllmangaProvider, HttpClient exports, IVideoPayload, startServer with providers
array, URN strings, 9 providers) removed or updated to 2.0 equivalents.

Getting Started (index.mdx): createSdk() → search/episodes/stream flow,
plain POJO types, progressive search, AniError, download utilities.

HTTP Server (http-server.mdx): startServerV2 / npx anime-sdk, all 9 routes
(/search, /media/:id, /episodes, /chapters, /sources, /episode/:id/stream,
/chapter/:id/pages, /browse, /health) with request/response shapes.

API Reference (api-reference.mdx): complete 2.0 surface — createSdk,
SdkOptions, Sdk methods, all value types (Media, Episode, Chapter, Stream,
Pages, List, SourceInfo, Score), AniError/AniErrorCode, startServerV2,
downloadVideo/downloadMangaChapter.

Contributing (contributing.mdx): Source interface instead of BaseProvider,
encodeId/decodeId, streamToPayload, updated checklist.

Proxy (proxy.mdx): createSdk({ proxy }) instead of startServer + proxy: true,
stream.url / stream.origin.host / stream.origin.proxied.

Download (download.mdx): sdk.stream() → downloadVideo, sdk.pages() →
downloadMangaChapter.

Providers overview: renamed "Providers" → "Sources", updated tables, 12 total.
All 12 individual source docs: createSdk({ sources: [...] }) pattern, removed
old Provider constructor usage.

Hero: "9 providers" → "12 sources", updated description.
HeroStats: "9 Providers" → "12 Sources".
Layout default description: updated.
- Remove src/providers, src/meta, src/transport, src/utils/urn,
  src/internal/mapping, src/download (legacy 1.x surface).
- Collapse src/server/index.ts to the v2 server; rename
  startServerV2 → startServer, ServerV2Options → ServerOptions.
- Trim src/types/index.ts and src/index.ts to only what 2.0 needs.
- Update tests, README, CLAUDE.md, examples, and the website docs
  to drop references to removed APIs.
…split

Core SDK changes:
- Add `src/download/` module — downloadVideo (HLS→MP4 via ffmpeg), downloadMangaPage (single image), downloadMangaChapter (pages→ZIP); exported from src/index.ts
- Add `src/internal/similarity.ts` — token-based fuzzy title similarity used by the registry
- Add `src/server/proxy.ts` — SSRF-safe HTTP proxy with optional HMAC signing, host allowlist, and HLS manifest rewriting; exposed on /proxy when ServerOptions.proxy is set
- Add `HttpClient.withHeaders()` — clone an HttpClient sharing the same rate-limiter/transport but with extra headers, used to give browser-UA sources their own client without forking the rate budget
- Registry.resolveMediaId: add fuzzy title-search fallback (step 3) — when lookupByMapping returns null and the source has `caps.search`, search by title and pick the best candidate above a 0.7 similarity threshold; year guard prevents "Naruto" matching "Naruto: Shippuden"
- Registry.rankPlaybackSources: run source probes in parallel (was sequential)
- sdk.ts: centralize browser User-Agent in buildSources(); browser-heavy sources (allmanga, megaplay, anikoto, gogoanime, goyabu, weebcentral) get browserHttp; API sources (anilist, mal, kitsu, mangadex, mangapill) keep plain http
- Sources (anikoto, gogoanime, goyabu, megaplay): remove per-constructor UA mutation now that sdk.ts owns the split
- Sources (kitsu, mal): info() now receives decoded `r` field directly (Sdk.info decodes first); drop redundant decodeId call in the method body
- Server: add CORS headers to all JSON responses and OPTIONS preflight; add /proxy route; add SSE-based download endpoints (/download/video/progress+file, /download/manga/chapter/progress+file) with 10-minute temp file cleanup
- cli.ts: parse PROXY / PROXY_BASE / PROXY_SIGN_SECRET / PROXY_ALLOWED_HOSTS env vars and thread through to startServer
- Tests: add registry fuzzy-resolution tests, download unit tests, proxy unit/E2E tests, sdkInfo and sdkSources E2E suites, similarity unit tests
- Docs/website: add proxy and download pages, update API reference, add Proxy section to marketing site
- Refactor `Source.stream` interface to return a list of `Stream` candidates (`Stream[]`) rather than a single nested object.
- Update all anime playback sources (allmanga, anikoto, animeparadise, gogoanime, goyabu, megaplay) to return arrays of stream candidates.
- Implement progressive fanning-out of stream resolution across sources in `Registry` and `Sdk`, returning a `ProgressiveResult<Stream>`.
- Add `/episode/:id/streams` Server-Sent Events (SSE) server endpoint for progressive streaming of candidates to the client.
- Rewrite example web application page (Stream.tsx) and API layer to consume the new EventSource stream endpoint and present candidates grouped by language, server, and quality.
- Update CLI, TUI, documentation (README, CLAUDE.md, website mdx), and test suites to reflect the progressive stream API.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant