Skip to content

Commit 3a23253

Browse files
committed
Release igdb-wrapper 0.6.1
1 parent f2cc3df commit 3a23253

6 files changed

Lines changed: 75 additions & 62 deletions

File tree

.github/workflows/release.yml

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,54 @@ jobs:
3434
- name: Install dependencies
3535
run: bun install --frozen-lockfile
3636

37+
- name: Typecheck
38+
run: bun run typecheck
39+
40+
- name: Run tests
41+
run: bun run test
42+
3743
- name: Build
3844
run: bun run build
3945

46+
- name: Resolve release tag
47+
id: release
48+
run: |
49+
NAME="$(node -p "require('./package.json').name")"
50+
VERSION="$(node -p "require('./package.json').version")"
51+
echo "tag=${NAME}@${VERSION}" >> "$GITHUB_OUTPUT"
52+
53+
- name: Generate and verify release notes
54+
env:
55+
GH_TOKEN: ${{ github.token }}
56+
TAG: ${{ steps.release.outputs.tag }}
57+
run: |
58+
gh api "repos/${GITHUB_REPOSITORY}/releases/generate-notes" \
59+
-f tag_name="$TAG" \
60+
-f target_commitish="$GITHUB_SHA" \
61+
--jq '.body // ""' > release-notes.md
62+
if ! grep -q '[^[:space:]]' release-notes.md; then
63+
echo "GitHub generated empty release notes for ${TAG}" >&2
64+
exit 1
65+
fi
66+
4067
- name: Create version PR or publish
68+
id: changesets
4169
uses: changesets/action@v1
4270
with:
4371
version: bun run changeset version
4472
publish: npm publish --access public --provenance
45-
createGithubReleases: true
73+
createGithubReleases: false
4674
env:
4775
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
76+
77+
- name: Create GitHub release
78+
if: steps.changesets.outputs.published == 'true'
79+
env:
80+
GH_TOKEN: ${{ github.token }}
81+
TAG: ${{ steps.release.outputs.tag }}
82+
run: |
83+
if gh release view "$TAG" >/dev/null 2>&1; then
84+
gh release edit "$TAG" --title "$TAG" --notes-file release-notes.md
85+
else
86+
gh release create "$TAG" --verify-tag --title "$TAG" --notes-file release-notes.md
87+
fi

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# @api-wrappers/igdb-wrapper
22

3+
## 0.6.1
4+
5+
### Patch Changes
6+
7+
- Route protobuf requests through api-core so plugins, retries, timeouts, and shared transport behaviour apply consistently.
8+
39
## 0.6.0
410

511
### Minor Changes

bun.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@api-wrappers/igdb-wrapper",
3-
"version": "0.6.0",
3+
"version": "0.6.1",
44
"description": "Type-safe TypeScript client for the IGDB API with a fluent query builder, automatic retries, and built-in rate limiting",
55
"module": "./dist/index.mjs",
66
"type": "module",
@@ -69,6 +69,6 @@
6969
"typescript": "^5"
7070
},
7171
"dependencies": {
72-
"@api-wrappers/api-core": "^0.0.2"
72+
"@api-wrappers/api-core": "^0.0.3"
7373
}
7474
}

src/__tests__/igdb-client.test.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ describe("IGDBClient", () => {
189189

190190
test("supports meta and protobuf requests", async () => {
191191
const protobufBytes = new Uint8Array([8, 1, 18, 4]);
192+
const coreRequestUrls: string[] = [];
192193
const fetchMock = (async (input, init) => {
193194
const url = String(input);
194195

@@ -210,6 +211,7 @@ describe("IGDBClient", () => {
210211
expect(init?.method).toBe("POST");
211212
expect(headers.get("accept")).toBe("application/octet-stream");
212213
expect(headers.get("authorization")).toBe("Bearer access-token");
214+
expect(headers.get("x-core-request")).toBe("yes");
213215
expect(init?.body).toBe("fields id,name; limit 1;");
214216
return new Response(protobufBytes, {
215217
headers: { "content-type": "application/octet-stream" },
@@ -219,7 +221,25 @@ describe("IGDBClient", () => {
219221
throw new Error(`Unexpected request: ${url}`);
220222
}) as typeof fetch;
221223

222-
const client = new IGDBClient({ ...testConfig, fetch: fetchMock });
224+
const client = new IGDBClient({
225+
...testConfig,
226+
fetch: fetchMock,
227+
plugins: [
228+
{
229+
name: "protobuf-core-probe",
230+
beforeRequest(ctx) {
231+
if (ctx.url.endsWith(".pb")) {
232+
coreRequestUrls.push(ctx.url);
233+
return {
234+
...ctx,
235+
headers: { ...ctx.headers, "x-core-request": "yes" },
236+
};
237+
}
238+
return ctx;
239+
},
240+
},
241+
],
242+
});
223243

224244
await expect(client.games.meta()).resolves.toEqual([
225245
{ name: "name", type: "String" },
@@ -230,6 +250,7 @@ describe("IGDBClient", () => {
230250
await client.dispose();
231251

232252
expect([...bytes]).toEqual([...protobufBytes]);
253+
expect(coreRequestUrls).toEqual(["https://api.igdb.com/v4/games.pb"]);
233254
});
234255

235256
test("builds IGDB image URLs and tag numbers from documented formulas", () => {

src/http/HttpClient.ts

Lines changed: 2 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,8 @@ interface IGDBCountResponse {
4646

4747
export class HttpClient {
4848
readonly #client: ReturnType<typeof createClient>;
49-
readonly #auth: AuthManager;
50-
readonly #clientId: string;
51-
readonly #fetch: typeof globalThis.fetch;
52-
readonly #timeoutMs: number | undefined;
5349

5450
constructor(options: HttpClientOptions) {
55-
this.#auth = options.auth;
56-
this.#clientId = options.clientId;
57-
this.#fetch = options.fetch ?? globalThis.fetch;
58-
this.#timeoutMs = options.timeoutMs;
5951
this.#client = createClient({
6052
baseUrl: IGDB_BASE,
6153
defaultHeaders: {
@@ -95,38 +87,18 @@ export class HttpClient {
9587

9688
async requestProtobuf(endpoint: string, body: string): Promise<ArrayBuffer> {
9789
const normalized = normalizeEndpoint(endpoint).replace(/\.pb$/, "");
98-
const url = `${IGDB_BASE}/${normalized}.pb`;
99-
const token = await this.#auth.getAccessToken();
100-
const controller =
101-
this.#timeoutMs === undefined ? null : new AbortController();
102-
const timeout =
103-
controller === null
104-
? undefined
105-
: setTimeout(() => controller.abort(), this.#timeoutMs);
10690

10791
try {
108-
const response = await this.#fetch(url, {
92+
return await this.#client.request<ArrayBuffer>(`/${normalized}.pb`, {
10993
body,
11094
headers: {
11195
accept: "application/octet-stream",
112-
authorization: `Bearer ${token}`,
113-
"client-id": this.#clientId,
114-
"content-type": "text/plain",
11596
},
11697
method: "POST",
117-
signal: controller?.signal,
98+
responseType: "arrayBuffer",
11899
});
119-
120-
if (!response.ok) {
121-
throw await toIGDBResponseError(response, `${normalized}.pb`);
122-
}
123-
124-
return response.arrayBuffer();
125100
} catch (error) {
126-
if (error instanceof IGDBError) throw error;
127101
throw toIGDBError(error, `${normalized}.pb`);
128-
} finally {
129-
if (timeout) clearTimeout(timeout);
130102
}
131103
}
132104

@@ -183,32 +155,6 @@ function normalizeEndpoint(endpoint: string): string {
183155
return endpoint.replace(/^\/+/, "").replace(/\/+$/, "");
184156
}
185157

186-
async function toIGDBResponseError(
187-
response: Response,
188-
endpoint: string,
189-
): Promise<IGDBError> {
190-
const body = await response.text().catch(() => "");
191-
192-
if (response.status === 401) {
193-
return new IGDBAuthError("Unauthorized - check your client credentials");
194-
}
195-
196-
if (response.status === 429) {
197-
return new IGDBRateLimitError(readRetryAfterMs(response));
198-
}
199-
200-
return new IGDBError(`HTTP ${response.status} on /${endpoint}: ${body}`);
201-
}
202-
203-
function readRetryAfterMs(response: Response): number | undefined {
204-
const raw = response.headers.get("retry-after");
205-
if (!raw) return undefined;
206-
const seconds = Number(raw);
207-
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000);
208-
const date = Date.parse(raw);
209-
return Number.isNaN(date) ? undefined : Math.max(0, date - Date.now());
210-
}
211-
212158
function toIGDBError(error: unknown, endpoint: string): IGDBError {
213159
const cause = unwrapPluginError(error);
214160
if (cause instanceof IGDBError) return cause;

0 commit comments

Comments
 (0)