Skip to content

Commit 6061069

Browse files
committed
Align wrapper with IGDB API
1 parent b29d731 commit 6061069

23 files changed

Lines changed: 1786 additions & 314 deletions

.changeset/igdb-api-alignment.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@api-wrappers/igdb-wrapper": minor
3+
---
4+
5+
Align the wrapper with the current IGDB v4 API surface by adding all documented endpoints, broader APICalypse query support, multi-query, webhooks, metadata, protobuf, image URL, and tag-number helpers.

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,14 @@ const games = await client.games
2222

2323
## Features
2424

25-
- **Fully type-safe** — field selection and where conditions are inferred from your model types, with no stringly-typed field names
26-
- **Fluent query builder** — chainable `.select()`, `.where()`, `.sort()`, `.limit()`, `.offset()`, `.search()`
25+
- **All current IGDB v4 endpoints** — every endpoint listed in the official API docs is exposed on `IGDBClient`
26+
- **Fully type-safe** — field selection and where conditions are inferred from model types where available, with raw field helpers for new IGDB fields
27+
- **Fluent query builder** — chainable `.select()`, `.fields()`, `.exclude()`, `.where()`, `.whereRaw()`, `.sort()`, `.limit()`, `.offset()`, `.search()`
2728
- **Automatic retries** — exponential backoff on transient failures, configurable
2829
- **Rate limiting** — respects IGDB's concurrency limits out of the box
2930
- **Pagination** — async generator via `.paginate()`, plus `.count()` for UI pagination
31+
- **Multi-query and webhooks** — helpers for `/multiquery` and IGDB webhook management
32+
- **Reference helpers** — image URL and tag-number helpers, endpoint `/meta`, and protobuf response access
3033
- **Structured errors**`IGDBAuthError`, `IGDBRateLimitError`, `IGDBNotFoundError`, `IGDBValidationError`
3134

3235
---
@@ -75,7 +78,7 @@ console.log(game?.name); // "Elden Ring"
7578
|---|---|
7679
| [Getting Started](./docs/getting-started.md) | Installation, credentials, and your first query |
7780
| [Querying](./docs/querying.md) | Full query builder API — select, where, sort, paginate |
78-
| [Endpoints](./docs/endpoints.md) | Games, Genres, Platforms, Companies |
81+
| [Endpoints](./docs/endpoints.md) | All IGDB v4 endpoint properties and raw endpoint access |
7982
| [Error Handling](./docs/error-handling.md) | All error types and how to handle them |
8083
| [Configuration](./docs/configuration.md) | Retry, rate limiting, and advanced options |
8184
| [API Reference](./docs/api-reference.md) | Complete method signatures |

docs/api-reference.md

Lines changed: 50 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,24 @@ Complete signatures for all public APIs.
88

99
```ts
1010
class IGDBClient {
11-
readonly games: GamesEndpoint;
12-
readonly genres: GenresEndpoint;
13-
readonly platforms: PlatformsEndpoint;
14-
readonly companies: CompaniesEndpoint;
11+
readonly games: IGDBEndpoint<Game>;
12+
readonly genres: IGDBEndpoint<Genre>;
13+
readonly platforms: IGDBEndpoint<Platform>;
14+
readonly companies: IGDBEndpoint<Company>;
15+
// ...plus every endpoint listed in docs/endpoints.md
1516

1617
constructor(config: IGDBClientConfig);
18+
endpoint<TModel extends IGDBEntity = IGDBAnyEntity>(path: string): IGDBEndpoint<TModel>;
19+
request<T = IGDBAnyEntity>(endpoint: string, query: string): Promise<T[]>;
20+
count(endpoint: string, query?: string): Promise<number>;
21+
meta(endpoint: string): Promise<MetaField[]>;
22+
requestProtobuf(endpoint: string, query: string): Promise<ArrayBuffer>;
23+
multiQuery<T = IGDBAnyEntity>(query: string): Promise<Array<MultiQueryResult<T>>>;
24+
createWebhook(endpoint: string, options: CreateWebhookOptions): Promise<Webhook>;
25+
listWebhooks(): Promise<Webhook[]>;
26+
getWebhook(id: number | string): Promise<Webhook>;
27+
deleteWebhook(id: number | string): Promise<Webhook>;
28+
testWebhook(endpoint: string, webhookId: number | string, entityId: number | string): Promise<unknown>;
1729
dispose(): Promise<void>;
1830
}
1931

@@ -47,6 +59,8 @@ class QueryBuilder<TModel, TShape = TModel> {
4759
select<TNewShape extends Record<string, unknown>>(
4860
selector: (proxy: SelectProxy<TModel>) => TNewShape
4961
): QueryBuilder<TModel, TNewShape>;
62+
fields(...fields: string[]): QueryBuilder<TModel, TShape>;
63+
exclude(...fields: string[]): QueryBuilder<TModel, TShape>;
5064

5165
// Filtering
5266
where(
@@ -55,6 +69,7 @@ class QueryBuilder<TModel, TShape = TModel> {
5569
helpers: WhereHelpers
5670
) => Condition | Condition[]
5771
): QueryBuilder<TModel, TShape>;
72+
whereRaw(condition: string): QueryBuilder<TModel, TShape>;
5873

5974
// Sorting
6075
sort(
@@ -68,6 +83,7 @@ class QueryBuilder<TModel, TShape = TModel> {
6883

6984
// Full-text search
7085
search(term: string): QueryBuilder<TModel, TShape>;
86+
apicalypse(clause: string): QueryBuilder<TModel, TShape>;
7187

7288
// Execution
7389
execute(): Promise<TShape[]>;
@@ -93,6 +109,7 @@ Passed as the second argument to `.where()` callbacks.
93109
interface WhereHelpers {
94110
or(...conditions: Condition[]): Condition;
95111
and(...conditions: Condition[]): Condition;
112+
raw(expression: string): Condition;
96113
}
97114
```
98115

@@ -111,51 +128,44 @@ interface ConditionBuilder<T> {
111128
lt(value: T): Condition;
112129
lte(value: T): Condition;
113130
in(values: T[]): Condition;
131+
notIn(values: T[]): Condition;
114132
contains(value: T): Condition;
133+
containsAll(values: T[]): Condition;
134+
excludesAll(values: T[]): Condition;
135+
exact(values: T[]): Condition;
136+
isNull(): Condition;
137+
notNull(): Condition;
138+
startsWith(value: string, options?: TextMatchOptions): Condition;
139+
endsWith(value: string, options?: TextMatchOptions): Condition;
140+
containsText(value: string, options?: TextMatchOptions): Condition;
115141
}
116142
```
117143

118144
---
119145

120-
## Endpoint classes
146+
## Endpoint class
121147

122-
### GamesEndpoint
148+
All client endpoint properties use the same class.
123149

124150
```ts
125-
class GamesEndpoint {
126-
query(): QueryBuilder<Game>;
127-
findMany(): QueryBuilder<Game>; // query().limit(50)
128-
findById(id: number): Promise<Game>; // throws IGDBNotFoundError if missing
129-
search(term: string): QueryBuilder<Game>; // query().search(term).limit(50)
151+
class IGDBEndpoint<TModel extends IGDBEntity = IGDBEntity> {
152+
readonly path: string;
153+
query(): QueryBuilder<TModel>;
154+
findMany(): QueryBuilder<TModel>;
155+
findById(id: number): Promise<TModel>;
156+
search(term: string): QueryBuilder<TModel>;
157+
request<TShape = TModel>(query: string): Promise<TShape[]>;
158+
count(query?: string): Promise<number>;
159+
meta(): Promise<MetaField[]>;
160+
requestProtobuf(query: string): Promise<ArrayBuffer>;
130161
}
131162
```
132163

133-
### GenresEndpoint
164+
## Reference Helpers
134165

135166
```ts
136-
class GenresEndpoint {
137-
query(): QueryBuilder<Genre>;
138-
findMany(): QueryBuilder<Genre>;
139-
}
140-
```
141-
142-
### PlatformsEndpoint
143-
144-
```ts
145-
class PlatformsEndpoint {
146-
query(): QueryBuilder<Platform>;
147-
findMany(): QueryBuilder<Platform>;
148-
}
149-
```
150-
151-
### CompaniesEndpoint
152-
153-
```ts
154-
class CompaniesEndpoint {
155-
query(): QueryBuilder<Company>;
156-
findMany(): QueryBuilder<Company>;
157-
search(term: string): QueryBuilder<Company>;
158-
}
167+
buildImageUrl("image_id", { size: "cover_big", retina: true });
168+
createTagNumber("genre", 5);
159169
```
160170

161171
---
@@ -198,60 +208,9 @@ interface RateLimitPluginOptions {
198208

199209
## Models
200210

201-
```ts
202-
interface Game {
203-
id: number;
204-
name: string;
205-
slug: string;
206-
summary: string;
207-
storyline: string;
208-
rating: number;
209-
rating_count: number;
210-
aggregated_rating: number;
211-
aggregated_rating_count: number;
212-
first_release_date: number;
213-
cover: Cover;
214-
genres: Genre[];
215-
platforms: Platform[];
216-
involved_companies: InvolvedCompany[];
217-
similar_games: Game[];
218-
url: string;
219-
status: number;
220-
category: number;
221-
}
222-
223-
interface Cover {
224-
id: number;
225-
image_id: string;
226-
width: number;
227-
height: number;
228-
}
211+
All endpoint model types are exported from the package root, including `Game`,
212+
`Artwork`, `AgeRating`, `CompanyWebsite`, `PopularityPrimitive`, `Webhook`, and
213+
the shared base types `IGDBEntity`, `NamedEntity`, and `ImageEntity`.
229214

230-
interface Genre {
231-
id: number;
232-
name: string;
233-
slug: string;
234-
}
235-
236-
interface Platform {
237-
id: number;
238-
name: string;
239-
slug: string;
240-
abbreviation: string;
241-
}
242-
243-
interface Company {
244-
id: number;
245-
name: string;
246-
description: string;
247-
country: number;
248-
slug: string;
249-
}
250-
251-
interface InvolvedCompany {
252-
id: number;
253-
company: Company;
254-
developer: boolean;
255-
publisher: boolean;
256-
}
257-
```
215+
Model fields are optional except for core identifiers and a few established
216+
fields, because IGDB only returns fields requested by the APICalypse body.

docs/configuration.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,9 @@ retry: { maxAttempts: 1 }
5959
## Rate Limiting
6060

6161
The built-in rate limiter uses api-core's rate limit plugin. Defaults are
62-
`maxConcurrent: 4` and `minTimeMs: 250`, which keeps requests comfortably within
63-
IGDB's free tier limit of 4 requests per second.
62+
`maxConcurrent: 8`, `maxRequestsPerInterval: 4`, `intervalMs: 1000`, and
63+
`minTimeMs: 250`, matching IGDB's documented 4 requests-per-second rate limit
64+
and 8 open-request concurrency limit.
6465

6566
```ts
6667
interface RateLimitPluginOptions {
@@ -89,7 +90,8 @@ const client = new IGDBClient({
8990
```ts
9091
rateLimit: {
9192
maxConcurrent: 8,
92-
minTimeMs: 100,
93+
maxRequestsPerInterval: 4,
94+
intervalMs: 1000,
9395
}
9496
```
9597

0 commit comments

Comments
 (0)