@api-wrappers/api-core 0.0.1
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.
- package/README.md +463 -0
- package/dist/index.cjs +994 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +755 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +755 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +973 -0
- package/dist/index.mjs.map +1 -0
- package/docs/README.md +42 -0
- package/docs/getting-started.md +93 -0
- package/docs/guides/built-in-plugins.md +119 -0
- package/docs/guides/error-handling.md +72 -0
- package/docs/guides/graphql.md +81 -0
- package/docs/guides/plugins.md +122 -0
- package/docs/guides/rest-requests.md +113 -0
- package/docs/guides/testing.md +88 -0
- package/docs/reference/client.md +53 -0
- package/docs/reference/configuration.md +83 -0
- package/docs/reference/exports.md +74 -0
- package/package.json +54 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
//#region src/types/common.d.ts
|
|
2
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
3
|
+
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
|
|
4
|
+
type QueryPrimitive = string | number | boolean;
|
|
5
|
+
type QueryValue = QueryPrimitive | null | undefined | readonly (QueryPrimitive | null | undefined)[];
|
|
6
|
+
type QueryParams = Record<string, QueryValue>;
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/context/RequestContext.d.ts
|
|
9
|
+
interface RequestContext {
|
|
10
|
+
url: string;
|
|
11
|
+
method: HttpMethod;
|
|
12
|
+
headers: Record<string, string>;
|
|
13
|
+
body?: unknown;
|
|
14
|
+
query?: QueryParams;
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
meta: Record<string, unknown>;
|
|
17
|
+
cacheKey?: string;
|
|
18
|
+
tags?: string[];
|
|
19
|
+
/**
|
|
20
|
+
* Number of retry attempts still available after this one.
|
|
21
|
+
* Equals `maxAttempts - 1 - attempt`, so it counts down from
|
|
22
|
+
* `maxAttempts - 1` on the first attempt to `0` on the last.
|
|
23
|
+
* Read-only from a plugin's perspective; set by BaseHttpClient.
|
|
24
|
+
*/
|
|
25
|
+
retryCount: number;
|
|
26
|
+
/** Zero-based index of the current attempt (0 = first try). */
|
|
27
|
+
attempt: number;
|
|
28
|
+
timeoutMs?: number;
|
|
29
|
+
/**
|
|
30
|
+
* When set by a plugin during `beforeRequest`, `BaseHttpClient` will use
|
|
31
|
+
* this response directly and skip calling the transport altogether.
|
|
32
|
+
* Plugins that short-circuit the network (e.g. cache hits) should populate
|
|
33
|
+
* this field instead of relying solely on `meta`.
|
|
34
|
+
*/
|
|
35
|
+
syntheticResponse?: Response;
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/graphql/types.d.ts
|
|
39
|
+
/**
|
|
40
|
+
* The shape of a single error object inside a GraphQL `errors` array,
|
|
41
|
+
* as specified by the GraphQL over HTTP specification.
|
|
42
|
+
*/
|
|
43
|
+
interface GraphQLErrorDetail {
|
|
44
|
+
/** Human-readable error message. */
|
|
45
|
+
message: string;
|
|
46
|
+
/**
|
|
47
|
+
* Path into the response `data` tree where the error occurred.
|
|
48
|
+
* Each element is a field name (string) or list index (number).
|
|
49
|
+
*/
|
|
50
|
+
path?: (string | number)[];
|
|
51
|
+
/** Source locations in the document that triggered the error. */
|
|
52
|
+
locations?: {
|
|
53
|
+
line: number;
|
|
54
|
+
column: number;
|
|
55
|
+
}[];
|
|
56
|
+
/** Arbitrary extension data attached by the server. */
|
|
57
|
+
extensions?: Record<string, unknown>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Raw response envelope returned by any spec-compliant GraphQL server.
|
|
61
|
+
* `data` is absent on a request-level failure; `errors` is absent on
|
|
62
|
+
* a fully successful response.
|
|
63
|
+
*/
|
|
64
|
+
interface GraphQLResponse<TData = unknown> {
|
|
65
|
+
data?: TData;
|
|
66
|
+
errors?: GraphQLErrorDetail[];
|
|
67
|
+
extensions?: Record<string, unknown>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Options for {@link BaseHttpClient.graphql}.
|
|
71
|
+
*
|
|
72
|
+
* @typeParam TVariables - Shape of the variables object. Defaults to
|
|
73
|
+
* `Record<string, unknown>`. Provide a specific type for compile-time
|
|
74
|
+
* variable checking.
|
|
75
|
+
*/
|
|
76
|
+
interface GraphQLRequestOptions<TVariables extends Record<string, unknown> = Record<string, unknown>> {
|
|
77
|
+
/**
|
|
78
|
+
* The GraphQL query or mutation document string.
|
|
79
|
+
* @example `query GetUser($id: ID!) { user(id: $id) { name } }`
|
|
80
|
+
*/
|
|
81
|
+
query: string;
|
|
82
|
+
/** Variables to substitute into the document. */
|
|
83
|
+
variables?: TVariables;
|
|
84
|
+
/**
|
|
85
|
+
* Name of the operation to execute when the document contains multiple
|
|
86
|
+
* named operations.
|
|
87
|
+
*/
|
|
88
|
+
operationName?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Additional headers merged on top of `ClientConfig.defaultHeaders` for
|
|
91
|
+
* this request only. `content-type: application/json` is always set.
|
|
92
|
+
*/
|
|
93
|
+
headers?: Record<string, string>;
|
|
94
|
+
/**
|
|
95
|
+
* Per-request timeout override in milliseconds. Throws
|
|
96
|
+
* {@link TimeoutError} when exceeded.
|
|
97
|
+
*/
|
|
98
|
+
timeoutMs?: number;
|
|
99
|
+
/**
|
|
100
|
+
* Explicit cache key for {@link createCachePlugin}. The cache plugin
|
|
101
|
+
* skips POST requests by default — provide this to opt a specific
|
|
102
|
+
* operation into caching.
|
|
103
|
+
* @example `"gql:GetUser:${userId}"`
|
|
104
|
+
*/
|
|
105
|
+
cacheKey?: string;
|
|
106
|
+
/**
|
|
107
|
+
* Arbitrary string tags passed through to the `RequestContext`. Useful
|
|
108
|
+
* for metrics grouping or cache invalidation in custom plugins.
|
|
109
|
+
*/
|
|
110
|
+
tags?: string[];
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/context/ResponseContext.d.ts
|
|
114
|
+
/**
|
|
115
|
+
* Represents the result of an HTTP request as it flows through the
|
|
116
|
+
* `afterResponse` plugin chain.
|
|
117
|
+
*
|
|
118
|
+
* Plugins may return a mutated copy to transform `parsedBody` or attach
|
|
119
|
+
* state to `meta`. The final `parsedBody` value is what `client.request`
|
|
120
|
+
* returns to the caller.
|
|
121
|
+
*/
|
|
122
|
+
interface ResponseContext {
|
|
123
|
+
/** The request context that produced this response. */
|
|
124
|
+
request: RequestContext;
|
|
125
|
+
/** The raw `Response` object returned by the transport. */
|
|
126
|
+
response: Response;
|
|
127
|
+
/**
|
|
128
|
+
* Body parsed from the response. JSON responses are parsed with
|
|
129
|
+
* `response.json()`; everything else is returned as a string via
|
|
130
|
+
* `response.text()`. Plugins may replace this with a transformed value.
|
|
131
|
+
*/
|
|
132
|
+
parsedBody?: unknown;
|
|
133
|
+
/**
|
|
134
|
+
* Arbitrary key/value store for plugins to attach response-scoped state
|
|
135
|
+
* without polluting `parsedBody`. Keys should be namespaced by plugin
|
|
136
|
+
* name (e.g. `"cache.served"`, `"logger.durationMs"`).
|
|
137
|
+
*/
|
|
138
|
+
meta: Record<string, unknown>;
|
|
139
|
+
}
|
|
140
|
+
//#endregion
|
|
141
|
+
//#region src/plugin/types.d.ts
|
|
142
|
+
interface ApiPlugin {
|
|
143
|
+
/** Unique display name for debugging. */
|
|
144
|
+
name: string;
|
|
145
|
+
/** Optional stable identifier, useful if names are duplicated. */
|
|
146
|
+
id?: string;
|
|
147
|
+
/**
|
|
148
|
+
* Execution order for beforeRequest (ascending) and afterResponse
|
|
149
|
+
* (descending). Defaults to 100. Plugins with equal priority run
|
|
150
|
+
* in registration order.
|
|
151
|
+
*/
|
|
152
|
+
priority?: number;
|
|
153
|
+
/** When false the plugin is skipped entirely. Defaults to true. */
|
|
154
|
+
enabled?: boolean;
|
|
155
|
+
/** Called once when the client initializes. */
|
|
156
|
+
setup?(client: unknown): MaybePromise<void>;
|
|
157
|
+
/**
|
|
158
|
+
* Called before the transport executes. Return a mutated context
|
|
159
|
+
* or void to keep the existing one.
|
|
160
|
+
*/
|
|
161
|
+
beforeRequest?(ctx: RequestContext): MaybePromise<RequestContext | void>;
|
|
162
|
+
/**
|
|
163
|
+
* Called after a successful transport response. Return a mutated
|
|
164
|
+
* context or void to keep the existing one.
|
|
165
|
+
*/
|
|
166
|
+
afterResponse?(ctx: ResponseContext): MaybePromise<ResponseContext | void>;
|
|
167
|
+
/** Called when the request pipeline throws. */
|
|
168
|
+
onError?(error: unknown, ctx: RequestContext): MaybePromise<void>;
|
|
169
|
+
/** Called when the client is disposed. */
|
|
170
|
+
dispose?(): MaybePromise<void>;
|
|
171
|
+
}
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/transport/types.d.ts
|
|
174
|
+
interface Transport {
|
|
175
|
+
execute(ctx: RequestContext): Promise<Response>;
|
|
176
|
+
}
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region src/client/types.d.ts
|
|
179
|
+
/**
|
|
180
|
+
* Minimal logger interface accepted by {@link ClientConfig.logger} and
|
|
181
|
+
* {@link PluginManager}. Compatible with `console` out of the box.
|
|
182
|
+
*/
|
|
183
|
+
interface LoggerInterface {
|
|
184
|
+
info(message: string, ...args: unknown[]): void;
|
|
185
|
+
warn(message: string, ...args: unknown[]): void;
|
|
186
|
+
error(message: string, ...args: unknown[]): void;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Retry behaviour applied to every request made by this client.
|
|
190
|
+
* Per-request overrides are possible via {@link createRetryPlugin}.
|
|
191
|
+
*/
|
|
192
|
+
interface RetryConfig {
|
|
193
|
+
/**
|
|
194
|
+
* Maximum total attempts, including the first. A value of `1` means no
|
|
195
|
+
* retries (the default when `retry` is omitted from {@link ClientConfig}).
|
|
196
|
+
*/
|
|
197
|
+
maxAttempts: number;
|
|
198
|
+
/** Base delay in ms between attempts before exponential backoff. Defaults to `500`. */
|
|
199
|
+
delayMs?: number;
|
|
200
|
+
/**
|
|
201
|
+
* When `true`, a random multiplier in `[0.5, 1)` is applied on top of
|
|
202
|
+
* exponential backoff so parallel clients don't all retry simultaneously.
|
|
203
|
+
* Defaults to `true`.
|
|
204
|
+
*/
|
|
205
|
+
jitter?: boolean;
|
|
206
|
+
/**
|
|
207
|
+
* HTTP status codes that trigger a retry. `429` also reads the
|
|
208
|
+
* `retry-after` header. Defaults to `[429, 500, 502, 503, 504]`.
|
|
209
|
+
*/
|
|
210
|
+
retriableStatusCodes?: number[];
|
|
211
|
+
}
|
|
212
|
+
/** Configuration passed to {@link BaseHttpClient} or {@link createClient}. */
|
|
213
|
+
interface ClientConfig {
|
|
214
|
+
/**
|
|
215
|
+
* Base URL prepended to every `path` argument. No trailing slash;
|
|
216
|
+
* paths should start with `/`.
|
|
217
|
+
* @example "https://api.example.com/v1"
|
|
218
|
+
*/
|
|
219
|
+
baseUrl: string;
|
|
220
|
+
/**
|
|
221
|
+
* Headers merged into every request. Per-request `headers` take
|
|
222
|
+
* precedence. `content-type: application/json` is always present as the
|
|
223
|
+
* lowest-priority default.
|
|
224
|
+
*/
|
|
225
|
+
defaultHeaders?: Record<string, string>;
|
|
226
|
+
/** Plugins registered for the lifetime of this client. */
|
|
227
|
+
plugins?: ApiPlugin[];
|
|
228
|
+
/**
|
|
229
|
+
* Transport implementation for executing requests. Defaults to
|
|
230
|
+
* {@link fetchTransport}. Swap in tests to avoid real network calls.
|
|
231
|
+
* When both `transport` and `fetch` are provided, `transport` wins.
|
|
232
|
+
*/
|
|
233
|
+
transport?: Transport;
|
|
234
|
+
/**
|
|
235
|
+
* Custom `fetch` implementation used by the default {@link fetchTransport}.
|
|
236
|
+
* Useful for polyfills or interceptors (e.g. `node-fetch`, `undici`).
|
|
237
|
+
* Ignored when a custom `transport` is provided.
|
|
238
|
+
* @example `import fetch from "node-fetch"; createClient({ fetch })`
|
|
239
|
+
*/
|
|
240
|
+
fetch?: typeof globalThis.fetch;
|
|
241
|
+
/**
|
|
242
|
+
* Default request timeout in ms. Overridable per-request via
|
|
243
|
+
* `RequestOptions.timeoutMs`. Throws {@link TimeoutError} when exceeded.
|
|
244
|
+
*/
|
|
245
|
+
timeoutMs?: number;
|
|
246
|
+
/**
|
|
247
|
+
* Global retry policy. Omitting means no retries (`maxAttempts: 1`).
|
|
248
|
+
* Per-request overrides via {@link createRetryPlugin}.
|
|
249
|
+
*/
|
|
250
|
+
retry?: RetryConfig;
|
|
251
|
+
/**
|
|
252
|
+
* Logger used for internal diagnostics (e.g. plugin `onError` handler
|
|
253
|
+
* failures). Defaults to `console`. Pass a no-op logger to silence all
|
|
254
|
+
* internal output; pass a structured logger for production observability.
|
|
255
|
+
* @example `{ info: () => {}, warn: () => {}, error: () => {} }`
|
|
256
|
+
*/
|
|
257
|
+
logger?: LoggerInterface;
|
|
258
|
+
}
|
|
259
|
+
//#endregion
|
|
260
|
+
//#region src/plugin/PluginManager.d.ts
|
|
261
|
+
declare class PluginManager {
|
|
262
|
+
private readonly plugins;
|
|
263
|
+
private readonly logger;
|
|
264
|
+
/**
|
|
265
|
+
* @param logger - Logger used when an `onError` handler itself throws.
|
|
266
|
+
* Defaults to `console`. Pass a no-op object to silence all output.
|
|
267
|
+
*/
|
|
268
|
+
constructor(logger?: LoggerInterface);
|
|
269
|
+
register(plugin: ApiPlugin): void;
|
|
270
|
+
getAll(): readonly ApiPlugin[];
|
|
271
|
+
setup(client: unknown): Promise<void>;
|
|
272
|
+
/**
|
|
273
|
+
* Runs `beforeRequest` in ascending priority order (lowest first).
|
|
274
|
+
* Each plugin may return a mutated context.
|
|
275
|
+
*/
|
|
276
|
+
beforeRequest(ctx: RequestContext): Promise<RequestContext>;
|
|
277
|
+
/**
|
|
278
|
+
* Runs `afterResponse` in descending priority order (highest first).
|
|
279
|
+
* Each plugin may return a mutated context.
|
|
280
|
+
*/
|
|
281
|
+
afterResponse(ctx: ResponseContext): Promise<ResponseContext>;
|
|
282
|
+
/**
|
|
283
|
+
* Runs `onError` on all plugins in registration order. A plugin throwing
|
|
284
|
+
* here is caught and logged via the configured logger but does not
|
|
285
|
+
* interrupt other `onError` handlers.
|
|
286
|
+
*/
|
|
287
|
+
onError(error: unknown, ctx: RequestContext): Promise<void>;
|
|
288
|
+
dispose(): Promise<void>;
|
|
289
|
+
}
|
|
290
|
+
//#endregion
|
|
291
|
+
//#region src/client/BaseHttpClient.d.ts
|
|
292
|
+
/** Per-request options passed to {@link BaseHttpClient.request} and the convenience methods. */
|
|
293
|
+
interface RequestOptions {
|
|
294
|
+
/** HTTP method. Defaults to `"GET"`. */
|
|
295
|
+
method?: HttpMethod;
|
|
296
|
+
/**
|
|
297
|
+
* Additional headers merged on top of `ClientConfig.defaultHeaders`.
|
|
298
|
+
* These take precedence; `content-type: application/json` is always
|
|
299
|
+
* present and is the lowest-priority default.
|
|
300
|
+
*/
|
|
301
|
+
headers?: Record<string, string>;
|
|
302
|
+
/** Request body. Serialised to JSON by {@link fetchTransport}. Ignored for GET and HEAD. */
|
|
303
|
+
body?: unknown;
|
|
304
|
+
/**
|
|
305
|
+
* Query string parameters appended to the URL. `undefined` values are
|
|
306
|
+
* omitted. Numbers and booleans are coerced to strings. Array values are
|
|
307
|
+
* emitted as repeated query parameters.
|
|
308
|
+
*/
|
|
309
|
+
query?: QueryParams;
|
|
310
|
+
/** Optional caller-provided abort signal. Composes with `timeoutMs`. */
|
|
311
|
+
signal?: AbortSignal;
|
|
312
|
+
/**
|
|
313
|
+
* Per-request timeout override in milliseconds. Takes precedence over
|
|
314
|
+
* `ClientConfig.timeoutMs`. Throws {@link TimeoutError} when exceeded.
|
|
315
|
+
*/
|
|
316
|
+
timeoutMs?: number;
|
|
317
|
+
/**
|
|
318
|
+
* Explicit cache key used by {@link createCachePlugin}. When omitted the
|
|
319
|
+
* plugin derives a key from the method, URL, and query string.
|
|
320
|
+
*/
|
|
321
|
+
cacheKey?: string;
|
|
322
|
+
/**
|
|
323
|
+
* Arbitrary string tags attached to the request context. Plugins may use
|
|
324
|
+
* these for cache invalidation, metrics grouping, or filtering.
|
|
325
|
+
*/
|
|
326
|
+
tags?: string[];
|
|
327
|
+
}
|
|
328
|
+
interface ApiResponse<T = unknown> {
|
|
329
|
+
data: T;
|
|
330
|
+
response: Response;
|
|
331
|
+
request: RequestContext;
|
|
332
|
+
meta: Record<string, unknown>;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Core HTTP client. Manages the plugin lifecycle, retry loop, and transport
|
|
336
|
+
* dispatch for all requests.
|
|
337
|
+
*
|
|
338
|
+
* Plugins are initialised lazily on the first call to {@link request} (or any
|
|
339
|
+
* convenience method). Call {@link dispose} when the client is no longer
|
|
340
|
+
* needed so plugins can release timers, connections, or cache handles.
|
|
341
|
+
*
|
|
342
|
+
* Extend this class to add domain-specific methods while keeping the plugin
|
|
343
|
+
* and transport infrastructure intact.
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* ```ts
|
|
347
|
+
* // Prefer createClient() in application code:
|
|
348
|
+
* const client = createClient({ baseUrl: "https://api.example.com/v1" });
|
|
349
|
+
*
|
|
350
|
+
* // Or subclass for wrapper packages:
|
|
351
|
+
* class MyApiClient extends BaseHttpClient {
|
|
352
|
+
* getUser(id: string) { return this.get<User>(`/users/${id}`); }
|
|
353
|
+
* }
|
|
354
|
+
* ```
|
|
355
|
+
*/
|
|
356
|
+
declare class BaseHttpClient {
|
|
357
|
+
protected readonly config: ClientConfig;
|
|
358
|
+
protected readonly pluginManager: PluginManager;
|
|
359
|
+
private initialized;
|
|
360
|
+
private initPromise;
|
|
361
|
+
constructor(config: ClientConfig);
|
|
362
|
+
/** Initializes all plugins. Called lazily on first request. */
|
|
363
|
+
init(): Promise<void>;
|
|
364
|
+
/** Disposes all plugins. Call when the client is no longer needed. */
|
|
365
|
+
dispose(): Promise<void>;
|
|
366
|
+
/**
|
|
367
|
+
* Executes an HTTP request through the full plugin pipeline.
|
|
368
|
+
*
|
|
369
|
+
* Lifecycle per attempt:
|
|
370
|
+
* 1. Build `RequestContext` with merged headers, query, and retry state.
|
|
371
|
+
* 2. Run `beforeRequest` hooks (ascending priority). A plugin may set
|
|
372
|
+
* `ctx.syntheticResponse` to skip the transport entirely (e.g. cache hit).
|
|
373
|
+
* 3. Merge any `retry.*` meta written by {@link createRetryPlugin}.
|
|
374
|
+
* 4. Call transport (skipped when `syntheticResponse` is set).
|
|
375
|
+
* 5. Parse the response body (JSON or text).
|
|
376
|
+
* 6. Run `afterResponse` hooks (descending priority).
|
|
377
|
+
* 7. Retry on retriable status codes; throw on terminal failures.
|
|
378
|
+
*
|
|
379
|
+
* @param path - Path appended to `ClientConfig.baseUrl`. Should start with `/`.
|
|
380
|
+
* @param options - Per-request overrides for method, headers, body, query, etc.
|
|
381
|
+
* @returns The parsed response body cast to `T`.
|
|
382
|
+
* @throws {@link ApiError} for non-2xx responses.
|
|
383
|
+
* @throws {@link RateLimitError} for 429 responses.
|
|
384
|
+
* @throws {@link TimeoutError} when `timeoutMs` is exceeded.
|
|
385
|
+
*/
|
|
386
|
+
request<T = unknown>(path: string, options?: RequestOptions): Promise<T>;
|
|
387
|
+
/**
|
|
388
|
+
* Executes a request and returns the parsed body plus the final response
|
|
389
|
+
* context. Use this in wrappers that need response headers, status, or
|
|
390
|
+
* plugin metadata while keeping the same error/retry behaviour as
|
|
391
|
+
* {@link request}.
|
|
392
|
+
*/
|
|
393
|
+
requestWithResponse<T = unknown>(path: string, options?: RequestOptions): Promise<ApiResponse<T>>;
|
|
394
|
+
/** Sends a GET request. The response body is not cached unless a cache plugin is registered. */
|
|
395
|
+
get<T = unknown>(path: string, options?: Omit<RequestOptions, "method">): Promise<T>;
|
|
396
|
+
post<T = unknown>(path: string, body?: unknown, options?: Omit<RequestOptions, "method" | "body">): Promise<T>;
|
|
397
|
+
put<T = unknown>(path: string, body?: unknown, options?: Omit<RequestOptions, "method" | "body">): Promise<T>;
|
|
398
|
+
patch<T = unknown>(path: string, body?: unknown, options?: Omit<RequestOptions, "method" | "body">): Promise<T>;
|
|
399
|
+
delete<T = unknown>(path: string, options?: Omit<RequestOptions, "method">): Promise<T>;
|
|
400
|
+
head<T = unknown>(path: string, options?: Omit<RequestOptions, "method">): Promise<T>;
|
|
401
|
+
options<T = unknown>(path: string, options?: Omit<RequestOptions, "method">): Promise<T>;
|
|
402
|
+
/**
|
|
403
|
+
* Executes a GraphQL query or mutation against a single endpoint path.
|
|
404
|
+
*
|
|
405
|
+
* The request is a `POST` with `content-type: application/json` carrying
|
|
406
|
+
* `{ query, variables?, operationName? }` as the body. It flows through
|
|
407
|
+
* the full plugin lifecycle (beforeRequest → transport → afterResponse →
|
|
408
|
+
* onError) and respects all retry configuration, exactly like REST calls.
|
|
409
|
+
*
|
|
410
|
+
* **Error handling:**
|
|
411
|
+
* - HTTP-level failures (429, 500, timeout) throw the same error classes
|
|
412
|
+
* as REST requests (`RateLimitError`, `ApiError`, `TimeoutError`).
|
|
413
|
+
* - A successful HTTP 200 that contains a non-empty `errors` array throws
|
|
414
|
+
* {@link GraphQLRequestError}, which extends `ApiError`.
|
|
415
|
+
*
|
|
416
|
+
* **Caching:**
|
|
417
|
+
* The cache plugin skips `POST` requests by default. Pass an explicit
|
|
418
|
+
* `cacheKey` in options to opt a specific operation into caching.
|
|
419
|
+
*
|
|
420
|
+
* @typeParam TData - Shape of the `data` field in the GraphQL response.
|
|
421
|
+
* @typeParam TVariables - Shape of the `variables` object. Defaults to
|
|
422
|
+
* `Record<string, unknown>`.
|
|
423
|
+
* @param path - Endpoint path, e.g. `"/graphql"`. Appended to `baseUrl`.
|
|
424
|
+
* @param options - Query document, variables, and optional per-request overrides.
|
|
425
|
+
* @returns The `data` field from the GraphQL response envelope.
|
|
426
|
+
* @throws {@link GraphQLRequestError} when `response.errors` is non-empty.
|
|
427
|
+
* @throws {@link ApiError} / {@link RateLimitError} / {@link TimeoutError} on
|
|
428
|
+
* HTTP-level failures.
|
|
429
|
+
*
|
|
430
|
+
* @example
|
|
431
|
+
* ```ts
|
|
432
|
+
* const data = await client.graphql<GetUserQuery, GetUserQueryVariables>(
|
|
433
|
+
* "/graphql",
|
|
434
|
+
* { query: GET_USER, variables: { id: "123" } },
|
|
435
|
+
* );
|
|
436
|
+
* ```
|
|
437
|
+
*/
|
|
438
|
+
graphql<TData = unknown, TVariables extends Record<string, unknown> = Record<string, unknown>>(path: string, options: GraphQLRequestOptions<TVariables>): Promise<TData>;
|
|
439
|
+
private waitForRetry;
|
|
440
|
+
}
|
|
441
|
+
//#endregion
|
|
442
|
+
//#region src/client/createClient.d.ts
|
|
443
|
+
/**
|
|
444
|
+
* Factory function that creates a {@link BaseHttpClient} from the given
|
|
445
|
+
* config. Prefer this over `new BaseHttpClient(config)` in application code
|
|
446
|
+
* so that the concrete class stays an implementation detail.
|
|
447
|
+
*
|
|
448
|
+
* @example
|
|
449
|
+
* ```ts
|
|
450
|
+
* const client = createClient({
|
|
451
|
+
* baseUrl: "https://api.example.com/v1",
|
|
452
|
+
* defaultHeaders: { "x-api-key": "secret" },
|
|
453
|
+
* retry: { maxAttempts: 3, delayMs: 300 },
|
|
454
|
+
* plugins: [createLoggerPlugin(), createCachePlugin({ ttlMs: 60_000 })],
|
|
455
|
+
* });
|
|
456
|
+
* ```
|
|
457
|
+
*/
|
|
458
|
+
declare function createClient(config: ClientConfig): BaseHttpClient;
|
|
459
|
+
//#endregion
|
|
460
|
+
//#region src/errors/ApiError.d.ts
|
|
461
|
+
declare class ApiError extends Error {
|
|
462
|
+
readonly status: number;
|
|
463
|
+
readonly responseBody: unknown;
|
|
464
|
+
readonly cause: unknown;
|
|
465
|
+
constructor(message: string, status: number, responseBody?: unknown, cause?: unknown);
|
|
466
|
+
}
|
|
467
|
+
//#endregion
|
|
468
|
+
//#region src/errors/RateLimitError.d.ts
|
|
469
|
+
declare class RateLimitError extends ApiError {
|
|
470
|
+
readonly retryAfterMs: number | undefined;
|
|
471
|
+
constructor(retryAfterMs?: number, responseBody?: unknown, cause?: unknown);
|
|
472
|
+
}
|
|
473
|
+
//#endregion
|
|
474
|
+
//#region src/errors/TimeoutError.d.ts
|
|
475
|
+
declare class TimeoutError extends Error {
|
|
476
|
+
readonly cause: unknown;
|
|
477
|
+
constructor(message?: string, cause?: unknown);
|
|
478
|
+
}
|
|
479
|
+
//#endregion
|
|
480
|
+
//#region src/plugins/auth/types.d.ts
|
|
481
|
+
interface AuthPluginOptions {
|
|
482
|
+
/**
|
|
483
|
+
* Returns the token to attach to each request. Called per request so
|
|
484
|
+
* wrappers can refresh credentials without rebuilding the client.
|
|
485
|
+
*/
|
|
486
|
+
getToken: () => MaybePromise<string | null | undefined>;
|
|
487
|
+
/** Header name to set. Defaults to `authorization`. */
|
|
488
|
+
headerName?: string;
|
|
489
|
+
/**
|
|
490
|
+
* Token scheme prefix. Defaults to `Bearer`. Pass `null` to write the raw
|
|
491
|
+
* token value without a scheme.
|
|
492
|
+
*/
|
|
493
|
+
scheme?: string | null;
|
|
494
|
+
}
|
|
495
|
+
//#endregion
|
|
496
|
+
//#region src/plugins/auth/authPlugin.d.ts
|
|
497
|
+
type TokenInput = string | (() => MaybePromise<string | null | undefined>) | AuthPluginOptions;
|
|
498
|
+
/**
|
|
499
|
+
* Adds an auth token header before each request. The token can be static or
|
|
500
|
+
* loaded asynchronously per request, which covers wrappers with refreshable
|
|
501
|
+
* access tokens.
|
|
502
|
+
*/
|
|
503
|
+
declare function createAuthPlugin(input: TokenInput): ApiPlugin;
|
|
504
|
+
//#endregion
|
|
505
|
+
//#region src/plugins/cache/types.d.ts
|
|
506
|
+
interface CacheStore {
|
|
507
|
+
get(key: string): MaybePromise<unknown | undefined>;
|
|
508
|
+
set(key: string, value: unknown, ttlMs?: number): MaybePromise<void>;
|
|
509
|
+
delete(key: string): MaybePromise<void>;
|
|
510
|
+
clear(): MaybePromise<void>;
|
|
511
|
+
}
|
|
512
|
+
interface CachePluginOptions {
|
|
513
|
+
store?: CacheStore;
|
|
514
|
+
ttlMs?: number;
|
|
515
|
+
methods?: HttpMethod[];
|
|
516
|
+
generateKey?: (ctx: RequestContext) => string;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* The object returned by {@link createCachePlugin}. Extends {@link ApiPlugin}
|
|
520
|
+
* with first-class cache invalidation methods.
|
|
521
|
+
*/
|
|
522
|
+
interface CachePlugin extends ApiPlugin {
|
|
523
|
+
/**
|
|
524
|
+
* Removes a single entry from the cache by its exact key.
|
|
525
|
+
* The key is either the auto-generated `"METHOD:url?query"` string or the
|
|
526
|
+
* explicit `cacheKey` passed in `RequestOptions`.
|
|
527
|
+
*/
|
|
528
|
+
invalidate(key: string): Promise<void>;
|
|
529
|
+
/**
|
|
530
|
+
* Removes all cache entries that were stored with the given tag.
|
|
531
|
+
* Tags are attached to requests via `RequestOptions.tags`. Only entries
|
|
532
|
+
* cached after this plugin was registered will have tag associations.
|
|
533
|
+
*/
|
|
534
|
+
invalidateByTag(tag: string): Promise<void>;
|
|
535
|
+
}
|
|
536
|
+
//#endregion
|
|
537
|
+
//#region src/plugins/cache/cachePlugin.d.ts
|
|
538
|
+
declare function createCachePlugin(options?: CachePluginOptions): CachePlugin;
|
|
539
|
+
//#endregion
|
|
540
|
+
//#region src/plugins/cache/memoryStore.d.ts
|
|
541
|
+
declare class MemoryStore implements CacheStore {
|
|
542
|
+
private readonly store;
|
|
543
|
+
get(key: string): unknown | undefined;
|
|
544
|
+
set(key: string, value: unknown, ttlMs?: number): void;
|
|
545
|
+
delete(key: string): void;
|
|
546
|
+
clear(): void;
|
|
547
|
+
}
|
|
548
|
+
//#endregion
|
|
549
|
+
//#region src/plugins/logger/types.d.ts
|
|
550
|
+
interface LoggerInterface$1 {
|
|
551
|
+
info(message: string, data?: unknown): void;
|
|
552
|
+
warn?(message: string, data?: unknown): void;
|
|
553
|
+
error(message: string, data?: unknown): void;
|
|
554
|
+
}
|
|
555
|
+
interface LoggerPluginOptions {
|
|
556
|
+
logRequest?: boolean;
|
|
557
|
+
logResponse?: boolean;
|
|
558
|
+
logError?: boolean;
|
|
559
|
+
logger?: LoggerInterface$1;
|
|
560
|
+
}
|
|
561
|
+
//#endregion
|
|
562
|
+
//#region src/plugins/logger/loggerPlugin.d.ts
|
|
563
|
+
/**
|
|
564
|
+
* Creates a plugin that logs request start, response status, and errors.
|
|
565
|
+
*
|
|
566
|
+
* Log lines are prefixed with `[api-core]` and include the HTTP method, URL,
|
|
567
|
+
* attempt number (on `beforeRequest`), and status code (on `afterResponse`).
|
|
568
|
+
*
|
|
569
|
+
* Priority `10` means it runs _after_ auth or header-mutation plugins
|
|
570
|
+
* (priority < 10) so the logged URL and headers reflect the final request,
|
|
571
|
+
* but _before_ the cache plugin (priority `20`) so cache hits are still
|
|
572
|
+
* visible in the log.
|
|
573
|
+
*
|
|
574
|
+
* @example
|
|
575
|
+
* ```ts
|
|
576
|
+
* createClient({
|
|
577
|
+
* baseUrl: "https://api.example.com",
|
|
578
|
+
* plugins: [createLoggerPlugin({ logRequest: true, logResponse: true })],
|
|
579
|
+
* });
|
|
580
|
+
* ```
|
|
581
|
+
*/
|
|
582
|
+
declare function createLoggerPlugin(options?: LoggerPluginOptions): ApiPlugin;
|
|
583
|
+
//#endregion
|
|
584
|
+
//#region src/plugins/rateLimit/types.d.ts
|
|
585
|
+
interface RateLimitPluginOptions {
|
|
586
|
+
/**
|
|
587
|
+
* Maximum number of requests allowed to run at the same time.
|
|
588
|
+
* Defaults to `Infinity`.
|
|
589
|
+
*/
|
|
590
|
+
maxConcurrent?: number;
|
|
591
|
+
/**
|
|
592
|
+
* Minimum delay between request starts. Defaults to `0`.
|
|
593
|
+
*/
|
|
594
|
+
minTimeMs?: number;
|
|
595
|
+
/**
|
|
596
|
+
* Maximum number of request starts allowed during `intervalMs`.
|
|
597
|
+
* Both fields must be provided to enable windowed limiting.
|
|
598
|
+
*/
|
|
599
|
+
maxRequestsPerInterval?: number;
|
|
600
|
+
intervalMs?: number;
|
|
601
|
+
}
|
|
602
|
+
//#endregion
|
|
603
|
+
//#region src/plugins/rateLimit/rateLimitPlugin.d.ts
|
|
604
|
+
/**
|
|
605
|
+
* Throttles request starts before they reach the transport. Supports
|
|
606
|
+
* concurrency, minimum spacing, and fixed-window request budgets.
|
|
607
|
+
*/
|
|
608
|
+
declare function createRateLimitPlugin(options?: RateLimitPluginOptions): ApiPlugin;
|
|
609
|
+
//#endregion
|
|
610
|
+
//#region src/plugins/retry/types.d.ts
|
|
611
|
+
interface RetryPluginOptions {
|
|
612
|
+
maxAttempts?: number;
|
|
613
|
+
delayMs?: number;
|
|
614
|
+
jitter?: boolean;
|
|
615
|
+
retriableStatusCodes?: number[];
|
|
616
|
+
}
|
|
617
|
+
//#endregion
|
|
618
|
+
//#region src/plugins/retry/retryPlugin.d.ts
|
|
619
|
+
/**
|
|
620
|
+
* Writes retry configuration into request context meta so the
|
|
621
|
+
* BaseHttpClient retry loop can read it. Use this when you need
|
|
622
|
+
* per-request retry overrides rather than global ClientConfig.retry.
|
|
623
|
+
*/
|
|
624
|
+
declare function createRetryPlugin(options?: RetryPluginOptions): ApiPlugin;
|
|
625
|
+
//#endregion
|
|
626
|
+
//#region src/plugins/timeout/types.d.ts
|
|
627
|
+
interface TimeoutPluginOptions {
|
|
628
|
+
/**
|
|
629
|
+
* Request timeout in milliseconds. Overrides `ClientConfig.timeoutMs` and
|
|
630
|
+
* any per-request `timeoutMs` set before this plugin runs.
|
|
631
|
+
* The abort and {@link TimeoutError} are handled by the transport.
|
|
632
|
+
*/
|
|
633
|
+
timeoutMs: number;
|
|
634
|
+
}
|
|
635
|
+
//#endregion
|
|
636
|
+
//#region src/plugins/timeout/timeoutPlugin.d.ts
|
|
637
|
+
/**
|
|
638
|
+
* Sets `ctx.timeoutMs` on every request so all requests made by this client
|
|
639
|
+
* abort after the configured duration. The actual abort and
|
|
640
|
+
* {@link TimeoutError} are handled by {@link fetchTransport}.
|
|
641
|
+
*
|
|
642
|
+
* Priority `1` ensures the timeout is stamped before any other plugin (e.g.
|
|
643
|
+
* logger, cache) runs — plugins that read `ctx.timeoutMs` will always see it.
|
|
644
|
+
* Use a `beforeRequest` hook with a lower priority to override per-request.
|
|
645
|
+
*
|
|
646
|
+
* Prefer `ClientConfig.timeoutMs` for a static global timeout. Use this
|
|
647
|
+
* plugin when you need to set or change the timeout through the plugin
|
|
648
|
+
* pipeline (e.g. from environment config loaded asynchronously in `setup`).
|
|
649
|
+
*
|
|
650
|
+
* @example
|
|
651
|
+
* ```ts
|
|
652
|
+
* createClient({
|
|
653
|
+
* baseUrl: "https://api.example.com",
|
|
654
|
+
* plugins: [createTimeoutPlugin({ timeoutMs: 5_000 })],
|
|
655
|
+
* });
|
|
656
|
+
* ```
|
|
657
|
+
*/
|
|
658
|
+
declare function createTimeoutPlugin(options: TimeoutPluginOptions): ApiPlugin;
|
|
659
|
+
//#endregion
|
|
660
|
+
//#region src/graphql/GraphQLRequestError.d.ts
|
|
661
|
+
/**
|
|
662
|
+
* Thrown when a GraphQL server returns a well-formed HTTP 200 response that
|
|
663
|
+
* contains a non-empty `errors` array.
|
|
664
|
+
*
|
|
665
|
+
* Extends {@link ApiError} so that code catching `ApiError` also catches
|
|
666
|
+
* GraphQL-level failures. Callers that need to inspect the individual error
|
|
667
|
+
* objects can narrow with `instanceof GraphQLRequestError` and read
|
|
668
|
+
* `graphqlErrors`.
|
|
669
|
+
*
|
|
670
|
+
* When the server returns both `data` and `errors` (partial result), the
|
|
671
|
+
* partial data is available on `partialData` but the error is still thrown —
|
|
672
|
+
* callers must explicitly opt in to consuming partial results.
|
|
673
|
+
*
|
|
674
|
+
* @example
|
|
675
|
+
* ```ts
|
|
676
|
+
* import { GraphQLRequestError } from "@api-wrappers/api-core";
|
|
677
|
+
*
|
|
678
|
+
* try {
|
|
679
|
+
* const data = await client.graphql<MyQuery>("/graphql", { query: QUERY });
|
|
680
|
+
* } catch (err) {
|
|
681
|
+
* if (err instanceof GraphQLRequestError) {
|
|
682
|
+
* for (const e of err.graphqlErrors) {
|
|
683
|
+
* console.error(e.message, e.path);
|
|
684
|
+
* }
|
|
685
|
+
* }
|
|
686
|
+
* }
|
|
687
|
+
* ```
|
|
688
|
+
*/
|
|
689
|
+
declare class GraphQLRequestError extends ApiError {
|
|
690
|
+
/** The errors array from the GraphQL response envelope. */
|
|
691
|
+
readonly graphqlErrors: readonly GraphQLErrorDetail[];
|
|
692
|
+
/**
|
|
693
|
+
* Partial `data` returned alongside `errors`, if any. `undefined` when
|
|
694
|
+
* the server returned no `data` field.
|
|
695
|
+
*/
|
|
696
|
+
readonly partialData: unknown;
|
|
697
|
+
constructor(errors: GraphQLErrorDetail[], partialData?: unknown, cause?: unknown);
|
|
698
|
+
}
|
|
699
|
+
//#endregion
|
|
700
|
+
//#region src/transport/fetchTransport.d.ts
|
|
701
|
+
/**
|
|
702
|
+
* Creates a {@link Transport} backed by the provided `fetch` function.
|
|
703
|
+
* Use this when you need a polyfill or a custom fetch interceptor:
|
|
704
|
+
*
|
|
705
|
+
* ```ts
|
|
706
|
+
* import nodeFetch from "node-fetch";
|
|
707
|
+
* createClient({ fetch: nodeFetch as typeof globalThis.fetch });
|
|
708
|
+
* // — or set it directly on the transport:
|
|
709
|
+
* const transport = createFetchTransport(nodeFetch as typeof globalThis.fetch);
|
|
710
|
+
* ```
|
|
711
|
+
*/
|
|
712
|
+
declare function createFetchTransport(fetchFn?: typeof globalThis.fetch): Transport;
|
|
713
|
+
/**
|
|
714
|
+
* Default {@link Transport} backed by the global `fetch` API.
|
|
715
|
+
*
|
|
716
|
+
* Behaviour:
|
|
717
|
+
* - Builds the final URL from `ctx.url` + `ctx.query` via {@link buildUrl}.
|
|
718
|
+
* - Serialises `ctx.body` to JSON for non-GET/HEAD requests.
|
|
719
|
+
* - Wires an `AbortController` when `ctx.timeoutMs` is set; throws
|
|
720
|
+
* {@link TimeoutError} on abort.
|
|
721
|
+
*
|
|
722
|
+
* Replace this with a custom {@link Transport} in tests, or provide a custom
|
|
723
|
+
* `fetch` function via {@link ClientConfig.fetch}.
|
|
724
|
+
*/
|
|
725
|
+
declare const fetchTransport: Transport;
|
|
726
|
+
//#endregion
|
|
727
|
+
//#region src/utils/buildUrl.d.ts
|
|
728
|
+
/**
|
|
729
|
+
* Appends a query string to a URL. Skips nullish values and repeats keys for
|
|
730
|
+
* array values so APIs like TMDB can accept `with_genres=1&with_genres=2`.
|
|
731
|
+
*/
|
|
732
|
+
declare function buildUrl(base: string, query?: QueryParams): string;
|
|
733
|
+
//#endregion
|
|
734
|
+
//#region src/utils/isPlainObject.d.ts
|
|
735
|
+
declare function isPlainObject(value: unknown): value is Record<string, unknown>;
|
|
736
|
+
//#endregion
|
|
737
|
+
//#region src/utils/mergeHeaders.d.ts
|
|
738
|
+
/**
|
|
739
|
+
* Merges header objects left to right. Keys are normalized to
|
|
740
|
+
* lowercase so merging is case-insensitive. Later sources win.
|
|
741
|
+
*/
|
|
742
|
+
declare function mergeHeaders(...sources: (Record<string, string> | undefined)[]): Record<string, string>;
|
|
743
|
+
//#endregion
|
|
744
|
+
//#region src/utils/resolveUrl.d.ts
|
|
745
|
+
/**
|
|
746
|
+
* Joins a client base URL and request path without requiring callers to keep
|
|
747
|
+
* slashes perfectly aligned. Absolute request URLs are returned unchanged.
|
|
748
|
+
*/
|
|
749
|
+
declare function resolveUrl(baseUrl: string, path: string): string;
|
|
750
|
+
//#endregion
|
|
751
|
+
//#region src/utils/sleep.d.ts
|
|
752
|
+
declare function sleep(ms: number): Promise<void>;
|
|
753
|
+
//#endregion
|
|
754
|
+
export { ApiError, type ApiPlugin, type ApiResponse, type AuthPluginOptions, BaseHttpClient, type CachePlugin, type CachePluginOptions, type CacheStore, type ClientConfig, type GraphQLErrorDetail, GraphQLRequestError, type GraphQLRequestOptions, type GraphQLResponse, type HttpMethod, type LoggerInterface, type LoggerPluginOptions, type MaybePromise, MemoryStore, PluginManager, type QueryParams, type QueryPrimitive, type QueryValue, RateLimitError, type RateLimitPluginOptions, type RequestContext, type RequestOptions, type ResponseContext, type RetryConfig, type RetryPluginOptions, TimeoutError, type TimeoutPluginOptions, type Transport, buildUrl, createAuthPlugin, createCachePlugin, createClient, createFetchTransport, createLoggerPlugin, createRateLimitPlugin, createRetryPlugin, createTimeoutPlugin, fetchTransport, isPlainObject, mergeHeaders, resolveUrl, sleep };
|
|
755
|
+
//# sourceMappingURL=index.d.mts.map
|