diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 9338e8e..e467023 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -22,7 +22,7 @@ labels: bug ## Environment -- `@dpianelli/chesscom` version: +- `chesscom-sdk` version: - Runtime + version (Node / Deno / Bun / browser): ## Extra context diff --git a/.prettierignore b/.prettierignore index 3d53196..1a5324e 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,3 +2,6 @@ dist coverage CHANGELOG.md + +# Local Claude Code harness files. +.claude diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 01ee9f1..f4cdad0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing -Thanks for your interest in `@dpianelli/chesscom`! This is an unofficial, +Thanks for your interest in `chesscom-sdk`! This is an unofficial, community SDK for the Chess.com Published-Data API. ## Prerequisites diff --git a/README.md b/README.md index 47fdac8..738590b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# @dpianelli/chesscom +# chesscom-sdk -[![npm version](https://img.shields.io/npm/v/@dpianelli/chesscom.svg)](https://www.npmjs.com/package/@dpianelli/chesscom) -[![license](https://img.shields.io/npm/l/@dpianelli/chesscom.svg)](./LICENSE) -[![types](https://img.shields.io/npm/types/@dpianelli/chesscom.svg)](https://www.npmjs.com/package/@dpianelli/chesscom) +[![npm version](https://img.shields.io/npm/v/chesscom-sdk.svg)](https://www.npmjs.com/package/chesscom-sdk) +[![license](https://img.shields.io/npm/l/chesscom-sdk.svg)](./LICENSE) +[![types](https://img.shields.io/npm/types/chesscom-sdk.svg)](https://www.npmjs.com/package/chesscom-sdk) > **Unofficial** TypeScript SDK for the [Chess.com Published-Data API](https://www.chess.com/news/view/published-data-api). > Not affiliated with, endorsed by, or sponsored by Chess.com. @@ -23,13 +23,13 @@ rate limiting, ETag caching, runtime response validation, and lazy pagination. ## Install ```bash -npm install @dpianelli/chesscom +npm install chesscom-sdk ``` ## Quickstart ```ts -import { ChessComClient } from "@dpianelli/chesscom"; +import { ChessComClient } from "chesscom-sdk"; const client = new ChessComClient({ // Required by Chess.com — include an app name and a contact. @@ -179,7 +179,7 @@ Every error thrown by the SDK extends `ChessComError` and carries a discriminant `kind`. Branch with `instanceof` or `switch (err.kind)`. ```ts -import { ChessComError, NotFoundError } from "@dpianelli/chesscom"; +import { ChessComError, NotFoundError } from "chesscom-sdk"; try { await client.getPlayer("does-not-exist"); @@ -224,7 +224,7 @@ The client revalidates with ETags (`If-None-Match`) and serves the cached body o implementing `CacheStore`: ```ts -import type { CacheStore, CacheEntry } from "@dpianelli/chesscom"; +import type { CacheStore, CacheEntry } from "chesscom-sdk"; class RedisCacheStore implements CacheStore { constructor(private redis: import("ioredis").Redis) {} diff --git a/RELEASING.md b/RELEASING.md index ad68ae4..8cc90b4 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -30,7 +30,7 @@ These must be configured once by a maintainer: "Allow GitHub Actions to create and approve pull requests". (release-please needs this to open its release PR.) 2. **npm trusted publishing (OIDC)** — on npmjs.com, open the package settings - for `@dpianelli/chesscom` → Trusted Publisher → add a GitHub Actions publisher + for `chesscom-sdk` → Trusted Publisher → add a GitHub Actions publisher for repo `denispianelli/chesscom`, workflow `.github/workflows/publish.yml`. No `NPM_TOKEN` secret is then needed. diff --git a/SPEC.md b/SPEC.md index 6954ea4..69616f5 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1,4 +1,4 @@ -# `@dpianelli/chesscom` — Technical spec +# `chesscom-sdk` — Technical spec > Unofficial TypeScript SDK for the [Chess.com Published-Data API](https://www.chess.com/news/view/published-data-api). > Not affiliated with, endorsed by, or sponsored by Chess.com. @@ -13,7 +13,7 @@ choice, not drift. | Aspect | Decision | | -------------- | ----------------------------------------------------------- | -| **npm name** | `@dpianelli/chesscom` (scope = npm username `dpianelli`) | +| **npm name** | `chesscom-sdk` (scope = npm username `dpianelli`) | | **Language** | TypeScript, isomorphic (Node 18+, Deno, Bun, browser) | | **Runtime** | native global `fetch` — no HTTP library | | **Dependency** | **Exactly one: `zod`** (runtime response validation) | @@ -119,7 +119,7 @@ test/ ## 4. Public surface (flat methods) ```ts -import { ChessComClient } from "@dpianelli/chesscom"; +import { ChessComClient } from "chesscom-sdk"; const client = new ChessComClient({ userAgent: "myapp/1.0 (me@example.com)", // MANDATORY diff --git a/STYLE.md b/STYLE.md index 9b1bfce..5dd23f6 100644 --- a/STYLE.md +++ b/STYLE.md @@ -1,4 +1,4 @@ -# `@dpianelli/chesscom` — Conventions (clean code) +# `chesscom-sdk` — Conventions (clean code) This document sets the code conventions that keep the project clean and consistent over time. Guiding principle: **clean, not ceremonious.** We prefer diff --git a/eslint.config.js b/eslint.config.js index 3b5eca6..038a6ba 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -5,7 +5,7 @@ import prettier from "eslint-config-prettier"; import { defineConfig, globalIgnores } from "eslint/config"; export default defineConfig( - globalIgnores(["dist", "node_modules", "coverage"]), + globalIgnores(["dist", "node_modules", "coverage", ".claude", "examples"]), eslint.configs.recommended, tseslint.configs.strictTypeChecked, tseslint.configs.stylisticTypeChecked, diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..2937ebe --- /dev/null +++ b/examples/README.md @@ -0,0 +1,30 @@ +# Examples + +Runnable, self-contained scripts showing common `chesscom-sdk` usage. They +import the package by name (`chesscom-sdk`) — exactly how you'd use it in your +own project. + +## Running them against this checkout + +The examples resolve `chesscom-sdk` to the local build via Node's +[self-referencing](https://nodejs.org/api/packages.html#self-referencing-a-package-using-its-name), +so build once, then run any file with a TypeScript-aware runner: + +```bash +npm run build +npx tsx examples/player-profile.ts +``` + +> Node 22 can also run these directly with `node --experimental-strip-types +examples/player-profile.ts`; on Node 23.6+ the flag is unnecessary. + +Every request to Chess.com **requires** a descriptive `User-Agent` (an app name +and a contact). Edit the `userAgent` in each example before running against the +live API. + +| File | Shows | +| ------------------------------------------------------------ | -------------------------------------------- | +| [`player-profile.ts`](./player-profile.ts) | Fetch a profile + rating stats | +| [`stream-games.ts`](./stream-games.ts) | Lazily iterate monthly archives with filters | +| [`leaderboards-and-puzzle.ts`](./leaderboards-and-puzzle.ts) | Leaderboards, titled players, daily puzzle | +| [`error-handling.ts`](./error-handling.ts) | Typed errors (404, rate limit, validation) | diff --git a/examples/error-handling.ts b/examples/error-handling.ts new file mode 100644 index 0000000..7968526 --- /dev/null +++ b/examples/error-handling.ts @@ -0,0 +1,33 @@ +/** + * Every failure mode is a typed error you can narrow on. The SDK throws a + * `NotFoundError` for 404s, a `RateLimitError` for 429s, a `ValidationError` + * when a response doesn't match its schema, and so on — all subclasses of + * `ChessComError`. + * + * npm run build && npx tsx examples/error-handling.ts + */ +import { + ChessComClient, + ChessComError, + NotFoundError, + RateLimitError, +} from "chesscom-sdk"; + +const client = new ChessComClient({ + userAgent: "chesscom-sdk-examples/1.0 (you@example.com)", +}); + +try { + // A username that (almost certainly) does not exist → 404. + await client.getPlayer("this-player-should-not-exist-xyz-123"); +} catch (error) { + if (error instanceof NotFoundError) { + console.log("✓ Caught NotFoundError — player does not exist."); + } else if (error instanceof RateLimitError) { + console.log("Rate limited — back off and retry later."); + } else if (error instanceof ChessComError) { + console.log(`Other Chess.com error (${error.kind}): ${error.message}`); + } else { + throw error; + } +} diff --git a/examples/leaderboards-and-puzzle.ts b/examples/leaderboards-and-puzzle.ts new file mode 100644 index 0000000..2ebd536 --- /dev/null +++ b/examples/leaderboards-and-puzzle.ts @@ -0,0 +1,24 @@ +/** + * Read-only "discovery" endpoints: live leaderboards, titled players, and the + * daily puzzle. + * + * npm run build && npx tsx examples/leaderboards-and-puzzle.ts + */ +import { ChessComClient } from "chesscom-sdk"; + +const client = new ChessComClient({ + userAgent: "chesscom-sdk-examples/1.0 (you@example.com)", +}); + +const leaderboards = await client.getLeaderboards(); +console.log("Top 5 — live blitz:"); +for (const entry of (leaderboards.live_blitz ?? []).slice(0, 5)) { + console.log(` #${entry.rank} ${entry.username} (${entry.score})`); +} + +const grandmasters = await client.getTitledPlayers("GM"); +console.log(`\nGrandmasters with a Chess.com account: ${grandmasters.length}`); + +const puzzle = await client.getDailyPuzzle(); +console.log(`\nDaily puzzle: "${puzzle.title}"`); +console.log(` ${puzzle.url}`); diff --git a/examples/player-profile.ts b/examples/player-profile.ts new file mode 100644 index 0000000..d73031b --- /dev/null +++ b/examples/player-profile.ts @@ -0,0 +1,29 @@ +/** + * Fetch a player's public profile and rating stats. + * + * npm run build && npx tsx examples/player-profile.ts [username] + */ +import { ChessComClient } from "chesscom-sdk"; + +const username = process.argv[2] ?? "hikaru"; + +const client = new ChessComClient({ + // Required by Chess.com — include an app name and a contact. + userAgent: "chesscom-sdk-examples/1.0 (you@example.com)", +}); + +const [profile, stats] = await Promise.all([ + client.getPlayer(username), + client.getPlayerStats(username), +]); + +console.log(`${profile.name ?? profile.username} (@${profile.username})`); +console.log(` followers: ${profile.followers ?? 0}`); +console.log(` country: ${profile.country}`); + +const blitz = stats.chess_blitz?.last?.rating; +const rapid = stats.chess_rapid?.last?.rating; +const bullet = stats.chess_bullet?.last?.rating; +console.log( + ` ratings: blitz ${blitz ?? "—"} · rapid ${rapid ?? "—"} · bullet ${bullet ?? "—"}`, +); diff --git a/examples/stream-games.ts b/examples/stream-games.ts new file mode 100644 index 0000000..e5ab8db --- /dev/null +++ b/examples/stream-games.ts @@ -0,0 +1,32 @@ +/** + * Lazily iterate a player's games across monthly archives. The SDK hides the + * pagination, fetches one month at a time (rate-limited + ETag-cached), and + * yields game by game. Here we keep only rated blitz games since a given month. + * + * npm run build && npx tsx examples/stream-games.ts [username] [since YYYY-MM] + */ +import { ChessComClient } from "chesscom-sdk"; + +const username = process.argv[2] ?? "magnuscarlsen"; +const since = process.argv[3] ?? "2024-01"; + +const client = new ChessComClient({ + userAgent: "chesscom-sdk-examples/1.0 (you@example.com)", +}); + +let count = 0; +for await (const game of client.streamPlayerGames(username, { + since, + rated: true, + timeClass: "blitz", +})) { + console.log( + `${game.end_time} ${game.white.username} vs ${game.black.username} ${game.url}`, + ); + // Stop after 20 so the example stays quick — drop this to walk everything. + if (++count >= 20) break; +} + +console.log( + `\nShowed ${count} rated blitz game(s) for ${username} since ${since}.`, +); diff --git a/package-lock.json b/package-lock.json index b22c870..a248c7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "@dpianelli/chesscom", + "name": "chesscom-sdk", "version": "1.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@dpianelli/chesscom", + "name": "chesscom-sdk", "version": "1.0.1", "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 0b0ce87..2b921a1 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,23 @@ { - "name": "@dpianelli/chesscom", + "name": "chesscom-sdk", "version": "1.0.1", "description": "Unofficial TypeScript SDK for the Chess.com Published-Data API. Not affiliated with Chess.com.", "keywords": [ "chess.com", "chesscom", + "chess-com", "chess", "chess-api", + "chess-com-api", + "chesscom-api", + "published-data-api", "api", + "api-client", "sdk", - "typescript" + "typescript", + "pgn", + "zod", + "esm" ], "author": "dpianelli", "license": "MIT", diff --git a/release-please-config.json b/release-please-config.json index ba619ab..ca474b8 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -3,7 +3,7 @@ "packages": { ".": { "release-type": "node", - "package-name": "@dpianelli/chesscom", + "package-name": "chesscom-sdk", "changelog-path": "CHANGELOG.md", "bump-minor-pre-major": true, "include-component-in-tag": false diff --git a/src/index.ts b/src/index.ts index b3430a1..45405cc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ /** - * `@dpianelli/chesscom` — Unofficial TypeScript SDK for the Chess.com + * `chesscom-sdk` — Unofficial TypeScript SDK for the Chess.com * Published-Data API. * * Public surface is assembled here. The high-level client lands next;