@buun_group/gunspec-sdk 0.1.0
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 +129 -0
- package/dist/index.cjs +2342 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +4554 -0
- package/dist/index.d.ts +4554 -0
- package/dist/index.js +2307 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2342 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/core/auth.ts
|
|
4
|
+
function readEnvKey() {
|
|
5
|
+
try {
|
|
6
|
+
if (typeof process !== "undefined" && process.env) {
|
|
7
|
+
return process.env.GUNSPEC_API_KEY;
|
|
8
|
+
}
|
|
9
|
+
} catch {
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const g = globalThis;
|
|
13
|
+
if (typeof g.Deno !== "undefined" && g.Deno.env) {
|
|
14
|
+
return g.Deno.env.get("GUNSPEC_API_KEY");
|
|
15
|
+
}
|
|
16
|
+
} catch {
|
|
17
|
+
}
|
|
18
|
+
return void 0;
|
|
19
|
+
}
|
|
20
|
+
function resolveApiKey(config) {
|
|
21
|
+
if (config.apiKey !== void 0 && config.apiKey !== "") {
|
|
22
|
+
return config.apiKey;
|
|
23
|
+
}
|
|
24
|
+
return readEnvKey();
|
|
25
|
+
}
|
|
26
|
+
function buildAuthHeaders(apiKey) {
|
|
27
|
+
const headers = {};
|
|
28
|
+
if (apiKey !== void 0 && apiKey !== "") {
|
|
29
|
+
headers["X-API-Key"] = apiKey;
|
|
30
|
+
}
|
|
31
|
+
return headers;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/core/errors.ts
|
|
35
|
+
var GunSpecError = class extends Error {
|
|
36
|
+
name = "GunSpecError";
|
|
37
|
+
constructor(message) {
|
|
38
|
+
super(message);
|
|
39
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var APIError = class extends GunSpecError {
|
|
43
|
+
name = "APIError";
|
|
44
|
+
/** HTTP status code returned by the API. */
|
|
45
|
+
status;
|
|
46
|
+
/** Machine-readable error code from the response body (e.g. `"NOT_FOUND"`). */
|
|
47
|
+
code;
|
|
48
|
+
/** The `X-Request-Id` header value, useful for support requests. */
|
|
49
|
+
requestId;
|
|
50
|
+
/** Raw response headers for further inspection. */
|
|
51
|
+
headers;
|
|
52
|
+
constructor(status, code, message, requestId, headers) {
|
|
53
|
+
super(message);
|
|
54
|
+
this.status = status;
|
|
55
|
+
this.code = code;
|
|
56
|
+
this.requestId = requestId;
|
|
57
|
+
this.headers = headers;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var AuthenticationError = class extends APIError {
|
|
61
|
+
name = "AuthenticationError";
|
|
62
|
+
constructor(code, message, requestId, headers) {
|
|
63
|
+
super(401, code, message, requestId, headers);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
var PermissionError = class extends APIError {
|
|
67
|
+
name = "PermissionError";
|
|
68
|
+
constructor(code, message, requestId, headers) {
|
|
69
|
+
super(403, code, message, requestId, headers);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var NotFoundError = class extends APIError {
|
|
73
|
+
name = "NotFoundError";
|
|
74
|
+
constructor(code, message, requestId, headers) {
|
|
75
|
+
super(404, code, message, requestId, headers);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var BadRequestError = class extends APIError {
|
|
79
|
+
name = "BadRequestError";
|
|
80
|
+
constructor(code, message, requestId, headers) {
|
|
81
|
+
super(400, code, message, requestId, headers);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
var RateLimitError = class extends APIError {
|
|
85
|
+
name = "RateLimitError";
|
|
86
|
+
/**
|
|
87
|
+
* Number of seconds the client should wait before retrying, or `null` if
|
|
88
|
+
* the server did not provide a `Retry-After` header.
|
|
89
|
+
*/
|
|
90
|
+
retryAfter;
|
|
91
|
+
constructor(code, message, requestId, headers, retryAfter) {
|
|
92
|
+
super(429, code, message, requestId, headers);
|
|
93
|
+
this.retryAfter = retryAfter;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
var InternalServerError = class extends APIError {
|
|
97
|
+
name = "InternalServerError";
|
|
98
|
+
constructor(code, message, requestId, headers) {
|
|
99
|
+
super(500, code, message, requestId, headers);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
var ConnectionError = class extends GunSpecError {
|
|
103
|
+
name = "ConnectionError";
|
|
104
|
+
/** The original error thrown by `fetch`. */
|
|
105
|
+
cause;
|
|
106
|
+
constructor(message, cause) {
|
|
107
|
+
super(message);
|
|
108
|
+
this.cause = cause;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
var TimeoutError = class extends GunSpecError {
|
|
112
|
+
name = "TimeoutError";
|
|
113
|
+
/** The timeout duration in milliseconds that was exceeded. */
|
|
114
|
+
timeoutMs;
|
|
115
|
+
constructor(timeoutMs) {
|
|
116
|
+
super(`Request timed out after ${timeoutMs}ms`);
|
|
117
|
+
this.timeoutMs = timeoutMs;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
function parseRetryAfter(headers) {
|
|
121
|
+
const raw = headers.get("Retry-After");
|
|
122
|
+
if (raw === null) return null;
|
|
123
|
+
const seconds = Number(raw);
|
|
124
|
+
if (!Number.isNaN(seconds) && seconds >= 0) return seconds;
|
|
125
|
+
const date = Date.parse(raw);
|
|
126
|
+
if (!Number.isNaN(date)) {
|
|
127
|
+
const delta = Math.ceil((date - Date.now()) / 1e3);
|
|
128
|
+
return delta > 0 ? delta : 0;
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
function createAPIError(status, body, requestId, headers) {
|
|
133
|
+
const code = body?.error?.code ?? `HTTP_${status}`;
|
|
134
|
+
const message = body?.error?.message ?? `Request failed with status ${status}`;
|
|
135
|
+
switch (status) {
|
|
136
|
+
case 400:
|
|
137
|
+
return new BadRequestError(code, message, requestId, headers);
|
|
138
|
+
case 401:
|
|
139
|
+
return new AuthenticationError(code, message, requestId, headers);
|
|
140
|
+
case 403:
|
|
141
|
+
return new PermissionError(code, message, requestId, headers);
|
|
142
|
+
case 404:
|
|
143
|
+
return new NotFoundError(code, message, requestId, headers);
|
|
144
|
+
case 429:
|
|
145
|
+
return new RateLimitError(code, message, requestId, headers, parseRetryAfter(headers));
|
|
146
|
+
case 500:
|
|
147
|
+
return new InternalServerError(code, message, requestId, headers);
|
|
148
|
+
default:
|
|
149
|
+
return new APIError(status, code, message, requestId, headers);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/core/retry.ts
|
|
154
|
+
var DEFAULTS = {
|
|
155
|
+
maxRetries: 2,
|
|
156
|
+
initialDelayMs: 500,
|
|
157
|
+
maxDelayMs: 8e3,
|
|
158
|
+
multiplier: 2
|
|
159
|
+
};
|
|
160
|
+
var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "PUT", "DELETE"]);
|
|
161
|
+
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
|
|
162
|
+
function resolveRetryConfig(config) {
|
|
163
|
+
return {
|
|
164
|
+
maxRetries: config?.maxRetries ?? DEFAULTS.maxRetries,
|
|
165
|
+
initialDelayMs: config?.initialDelayMs ?? DEFAULTS.initialDelayMs,
|
|
166
|
+
maxDelayMs: config?.maxDelayMs ?? DEFAULTS.maxDelayMs,
|
|
167
|
+
multiplier: config?.multiplier ?? DEFAULTS.multiplier
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function isRetryable(error, method) {
|
|
171
|
+
if (!IDEMPOTENT_METHODS.has(method.toUpperCase())) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
if (error instanceof ConnectionError || error instanceof TimeoutError) {
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
if (error instanceof APIError) {
|
|
178
|
+
return RETRYABLE_STATUS_CODES.has(error.status);
|
|
179
|
+
}
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
function computeDelay(attempt, config, error) {
|
|
183
|
+
const base = config.initialDelayMs * Math.pow(config.multiplier, attempt);
|
|
184
|
+
const jitter = 1 + (Math.random() * 0.4 - 0.2);
|
|
185
|
+
let delay = Math.min(base * jitter, config.maxDelayMs);
|
|
186
|
+
if (error instanceof RateLimitError && error.retryAfter !== null) {
|
|
187
|
+
const retryAfterMs = error.retryAfter * 1e3;
|
|
188
|
+
delay = Math.max(delay, retryAfterMs);
|
|
189
|
+
}
|
|
190
|
+
return Math.round(delay);
|
|
191
|
+
}
|
|
192
|
+
function sleep(ms, signal) {
|
|
193
|
+
return new Promise((resolve, reject) => {
|
|
194
|
+
if (signal?.aborted) {
|
|
195
|
+
reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const timer = setTimeout(resolve, ms);
|
|
199
|
+
signal?.addEventListener(
|
|
200
|
+
"abort",
|
|
201
|
+
() => {
|
|
202
|
+
clearTimeout(timer);
|
|
203
|
+
reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
204
|
+
},
|
|
205
|
+
{ once: true }
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
async function withRetry(fn, method, config, signal) {
|
|
210
|
+
let lastError;
|
|
211
|
+
for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
|
|
212
|
+
try {
|
|
213
|
+
return await fn();
|
|
214
|
+
} catch (error) {
|
|
215
|
+
lastError = error;
|
|
216
|
+
const isLastAttempt = attempt === config.maxRetries;
|
|
217
|
+
if (isLastAttempt || !isRetryable(error, method)) {
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
const delay = computeDelay(attempt, config, error);
|
|
221
|
+
await sleep(delay, signal);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
throw lastError;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/core/http-client.ts
|
|
228
|
+
var DEFAULT_BASE_URL = "https://api.gunspec.io";
|
|
229
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
230
|
+
var SDK_USER_AGENT = "@gunspec/sdk";
|
|
231
|
+
function serialiseQuery(params) {
|
|
232
|
+
const parts = [];
|
|
233
|
+
for (const [key, value] of Object.entries(params)) {
|
|
234
|
+
if (value === void 0) continue;
|
|
235
|
+
if (Array.isArray(value)) {
|
|
236
|
+
for (const item of value) {
|
|
237
|
+
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(item)}`);
|
|
238
|
+
}
|
|
239
|
+
} else {
|
|
240
|
+
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return parts.length > 0 ? `?${parts.join("&")}` : "";
|
|
244
|
+
}
|
|
245
|
+
function parseRateLimitHeaders(headers) {
|
|
246
|
+
const parse = (name) => {
|
|
247
|
+
const raw = headers.get(name);
|
|
248
|
+
if (raw === null) return null;
|
|
249
|
+
const num = Number(raw);
|
|
250
|
+
return Number.isNaN(num) ? null : num;
|
|
251
|
+
};
|
|
252
|
+
return {
|
|
253
|
+
limit: parse("X-RateLimit-Limit"),
|
|
254
|
+
remaining: parse("X-RateLimit-Remaining"),
|
|
255
|
+
reset: parse("X-RateLimit-Reset")
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function extractRequestId(headers) {
|
|
259
|
+
return headers.get("X-Request-Id") ?? "";
|
|
260
|
+
}
|
|
261
|
+
function composeSignals(timeoutMs, externalSignal) {
|
|
262
|
+
const timeoutController = new AbortController();
|
|
263
|
+
const timer = setTimeout(() => timeoutController.abort(new TimeoutError(timeoutMs)), timeoutMs);
|
|
264
|
+
if (typeof AbortSignal !== "undefined" && "any" in AbortSignal) {
|
|
265
|
+
const signals = [timeoutController.signal];
|
|
266
|
+
if (externalSignal) signals.push(externalSignal);
|
|
267
|
+
const composed2 = AbortSignal.any(signals);
|
|
268
|
+
return {
|
|
269
|
+
signal: composed2,
|
|
270
|
+
cleanup: () => clearTimeout(timer)
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
const composed = new AbortController();
|
|
274
|
+
const onTimeoutAbort = () => composed.abort(timeoutController.signal.reason);
|
|
275
|
+
const onExternalAbort = () => {
|
|
276
|
+
if (externalSignal) composed.abort(externalSignal.reason);
|
|
277
|
+
};
|
|
278
|
+
timeoutController.signal.addEventListener("abort", onTimeoutAbort, { once: true });
|
|
279
|
+
externalSignal?.addEventListener("abort", onExternalAbort, { once: true });
|
|
280
|
+
return {
|
|
281
|
+
signal: composed.signal,
|
|
282
|
+
cleanup: () => {
|
|
283
|
+
clearTimeout(timer);
|
|
284
|
+
timeoutController.signal.removeEventListener("abort", onTimeoutAbort);
|
|
285
|
+
externalSignal?.removeEventListener("abort", onExternalAbort);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
var HttpClient = class {
|
|
290
|
+
baseUrl;
|
|
291
|
+
defaultTimeout;
|
|
292
|
+
defaultHeaders;
|
|
293
|
+
apiKey;
|
|
294
|
+
retryConfig;
|
|
295
|
+
constructor(config = {}) {
|
|
296
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
297
|
+
this.defaultTimeout = config.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
298
|
+
this.apiKey = resolveApiKey(config.auth ?? {});
|
|
299
|
+
this.retryConfig = resolveRetryConfig(config.retry);
|
|
300
|
+
this.defaultHeaders = {
|
|
301
|
+
"Accept": "application/json",
|
|
302
|
+
"User-Agent": SDK_USER_AGENT,
|
|
303
|
+
...config.headers
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Execute an HTTP request and return the unwrapped response.
|
|
308
|
+
*
|
|
309
|
+
* The API's `{ success: true, data: T }` envelope is stripped so that
|
|
310
|
+
* callers receive `T` directly via {@link APIResponse.data}.
|
|
311
|
+
*
|
|
312
|
+
* @typeParam T - The expected type of the `data` field in the response.
|
|
313
|
+
* @param config - Request configuration.
|
|
314
|
+
* @returns The unwrapped API response.
|
|
315
|
+
* @throws {@link APIError} on non-2xx responses.
|
|
316
|
+
* @throws {@link ConnectionError} on network failures.
|
|
317
|
+
* @throws {@link TimeoutError} when the request exceeds the timeout.
|
|
318
|
+
*/
|
|
319
|
+
async request(config) {
|
|
320
|
+
return withRetry(
|
|
321
|
+
() => this.executeRequest(config),
|
|
322
|
+
config.method,
|
|
323
|
+
this.retryConfig,
|
|
324
|
+
config.signal
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Execute an HTTP request and return a paginated response.
|
|
329
|
+
*
|
|
330
|
+
* The API's `{ success: true, data: T[], pagination }` envelope is
|
|
331
|
+
* unwrapped into a {@link PaginatedResponse}.
|
|
332
|
+
*
|
|
333
|
+
* @typeParam T - The element type of the paginated list.
|
|
334
|
+
* @param config - Request configuration.
|
|
335
|
+
* @returns The unwrapped paginated response.
|
|
336
|
+
* @throws {@link APIError} on non-2xx responses.
|
|
337
|
+
* @throws {@link ConnectionError} on network failures.
|
|
338
|
+
* @throws {@link TimeoutError} when the request exceeds the timeout.
|
|
339
|
+
*/
|
|
340
|
+
async requestPaginated(config) {
|
|
341
|
+
return withRetry(
|
|
342
|
+
() => this.executePaginatedRequest(config),
|
|
343
|
+
config.method,
|
|
344
|
+
this.retryConfig,
|
|
345
|
+
config.signal
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Convenience wrapper for a GET request returning a single resource.
|
|
350
|
+
*/
|
|
351
|
+
async get(path, query) {
|
|
352
|
+
return this.request({ method: "GET", path, query });
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Convenience wrapper for a GET request returning a paginated list.
|
|
356
|
+
*/
|
|
357
|
+
async getPaginated(path, query) {
|
|
358
|
+
return this.requestPaginated({ method: "GET", path, query });
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Convenience wrapper for a POST request.
|
|
362
|
+
*/
|
|
363
|
+
async post(path, body, query) {
|
|
364
|
+
return this.request({ method: "POST", path, body, query });
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Convenience wrapper for a PUT request.
|
|
368
|
+
*/
|
|
369
|
+
async put(path, body, query) {
|
|
370
|
+
return this.request({ method: "PUT", path, body, query });
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Convenience wrapper for a DELETE request.
|
|
374
|
+
*/
|
|
375
|
+
async delete(path, query) {
|
|
376
|
+
return this.request({ method: "DELETE", path, query });
|
|
377
|
+
}
|
|
378
|
+
// -----------------------------------------------------------------------
|
|
379
|
+
// Internal execution
|
|
380
|
+
// -----------------------------------------------------------------------
|
|
381
|
+
async executeRequest(config) {
|
|
382
|
+
const response = await this.doFetch(config);
|
|
383
|
+
const requestId = extractRequestId(response.headers);
|
|
384
|
+
const rateLimit = parseRateLimitHeaders(response.headers);
|
|
385
|
+
if (!response.ok) {
|
|
386
|
+
await this.throwAPIError(response, requestId);
|
|
387
|
+
}
|
|
388
|
+
const json = await response.json();
|
|
389
|
+
return {
|
|
390
|
+
data: json.data,
|
|
391
|
+
status: response.status,
|
|
392
|
+
headers: response.headers,
|
|
393
|
+
requestId,
|
|
394
|
+
rateLimit
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
async executePaginatedRequest(config) {
|
|
398
|
+
const response = await this.doFetch(config);
|
|
399
|
+
const requestId = extractRequestId(response.headers);
|
|
400
|
+
const rateLimit = parseRateLimitHeaders(response.headers);
|
|
401
|
+
if (!response.ok) {
|
|
402
|
+
await this.throwAPIError(response, requestId);
|
|
403
|
+
}
|
|
404
|
+
const json = await response.json();
|
|
405
|
+
return {
|
|
406
|
+
data: json.data,
|
|
407
|
+
pagination: json.pagination,
|
|
408
|
+
status: response.status,
|
|
409
|
+
headers: response.headers,
|
|
410
|
+
requestId,
|
|
411
|
+
rateLimit
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Perform the raw `fetch` call with merged headers, query string, timeout,
|
|
416
|
+
* and body serialisation.
|
|
417
|
+
*/
|
|
418
|
+
async doFetch(config) {
|
|
419
|
+
const url = this.buildUrl(config.path, config.query);
|
|
420
|
+
const timeoutMs = config.timeout ?? this.defaultTimeout;
|
|
421
|
+
const { signal, cleanup } = composeSignals(timeoutMs, config.signal);
|
|
422
|
+
const headers = {
|
|
423
|
+
...this.defaultHeaders,
|
|
424
|
+
...buildAuthHeaders(this.apiKey),
|
|
425
|
+
...config.headers
|
|
426
|
+
};
|
|
427
|
+
if (config.body !== void 0) {
|
|
428
|
+
headers["Content-Type"] = "application/json";
|
|
429
|
+
}
|
|
430
|
+
try {
|
|
431
|
+
const response = await fetch(url, {
|
|
432
|
+
method: config.method,
|
|
433
|
+
headers,
|
|
434
|
+
body: config.body !== void 0 ? JSON.stringify(config.body) : void 0,
|
|
435
|
+
signal
|
|
436
|
+
});
|
|
437
|
+
return response;
|
|
438
|
+
} catch (error) {
|
|
439
|
+
if (error instanceof TimeoutError) {
|
|
440
|
+
throw error;
|
|
441
|
+
}
|
|
442
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
443
|
+
if (config.signal?.aborted) {
|
|
444
|
+
throw error;
|
|
445
|
+
}
|
|
446
|
+
throw new TimeoutError(timeoutMs);
|
|
447
|
+
}
|
|
448
|
+
throw new ConnectionError(
|
|
449
|
+
error instanceof Error ? error.message : "Network request failed",
|
|
450
|
+
error
|
|
451
|
+
);
|
|
452
|
+
} finally {
|
|
453
|
+
cleanup();
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Build the full URL from base URL, path, and query parameters.
|
|
458
|
+
*/
|
|
459
|
+
buildUrl(path, query) {
|
|
460
|
+
const qs = query ? serialiseQuery(query) : "";
|
|
461
|
+
return `${this.baseUrl}${path}${qs}`;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Parse an error response body and throw the appropriate {@link APIError}.
|
|
465
|
+
*/
|
|
466
|
+
async throwAPIError(response, requestId) {
|
|
467
|
+
let body = null;
|
|
468
|
+
try {
|
|
469
|
+
body = await response.json();
|
|
470
|
+
} catch {
|
|
471
|
+
}
|
|
472
|
+
throw createAPIError(response.status, body, requestId, response.headers);
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
// src/resources/firearms.ts
|
|
477
|
+
var FirearmsResource = class {
|
|
478
|
+
constructor(client) {
|
|
479
|
+
this.client = client;
|
|
480
|
+
}
|
|
481
|
+
// ---------------------------------------------------------------------------
|
|
482
|
+
// Collection endpoints
|
|
483
|
+
// ---------------------------------------------------------------------------
|
|
484
|
+
/**
|
|
485
|
+
* List firearms with optional filters and pagination.
|
|
486
|
+
*
|
|
487
|
+
* @param params - Optional query parameters for filtering, sorting, and pagination.
|
|
488
|
+
* @returns A paginated list of firearms matching the given filters.
|
|
489
|
+
* @throws {BadRequestError} If any filter value is invalid.
|
|
490
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
491
|
+
*
|
|
492
|
+
* @example
|
|
493
|
+
* ```typescript
|
|
494
|
+
* const result = await client.firearms.list({
|
|
495
|
+
* manufacturer: 'beretta',
|
|
496
|
+
* status: 'in_production',
|
|
497
|
+
* sort: 'name',
|
|
498
|
+
* order: 'asc',
|
|
499
|
+
* page: 1,
|
|
500
|
+
* per_page: 25,
|
|
501
|
+
* });
|
|
502
|
+
* console.log(result.data); // Firearm[]
|
|
503
|
+
* console.log(result.pagination.totalPages);
|
|
504
|
+
* ```
|
|
505
|
+
*/
|
|
506
|
+
async list(params) {
|
|
507
|
+
return this.client.getPaginated("/v1/firearms", params);
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Auto-paginate through all firearms matching the given filters.
|
|
511
|
+
*
|
|
512
|
+
* Returns an async iterator that fetches pages on demand, yielding
|
|
513
|
+
* individual {@link Firearm} objects. Useful for processing large
|
|
514
|
+
* result sets without managing pagination manually.
|
|
515
|
+
*
|
|
516
|
+
* @param params - Optional query parameters for filtering and sorting.
|
|
517
|
+
* @returns An async iterable iterator yielding individual firearms.
|
|
518
|
+
*
|
|
519
|
+
* @example
|
|
520
|
+
* ```typescript
|
|
521
|
+
* for await (const firearm of client.firearms.listAutoPaging({ manufacturer: 'colt' })) {
|
|
522
|
+
* console.log(firearm.name);
|
|
523
|
+
* }
|
|
524
|
+
* ```
|
|
525
|
+
*/
|
|
526
|
+
async *listAutoPaging(params) {
|
|
527
|
+
let page = params?.page ?? 1;
|
|
528
|
+
while (true) {
|
|
529
|
+
const result = await this.list({ ...params, page });
|
|
530
|
+
for (const item of result.data) {
|
|
531
|
+
yield item;
|
|
532
|
+
}
|
|
533
|
+
if (!result.pagination.totalPages || page >= result.pagination.totalPages) break;
|
|
534
|
+
page++;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Full-text search across firearms.
|
|
539
|
+
*
|
|
540
|
+
* @param params - Search query and pagination parameters.
|
|
541
|
+
* @returns A paginated list of firearms matching the search query.
|
|
542
|
+
* @throws {BadRequestError} If the search query is empty or too long.
|
|
543
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
544
|
+
*
|
|
545
|
+
* @example
|
|
546
|
+
* ```typescript
|
|
547
|
+
* const result = await client.firearms.search({ q: '9mm compact' });
|
|
548
|
+
* console.log(result.data.length);
|
|
549
|
+
* ```
|
|
550
|
+
*/
|
|
551
|
+
async search(params) {
|
|
552
|
+
return this.client.getPaginated("/v1/firearms/search", params);
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Compare up to 5 firearms side by side.
|
|
556
|
+
*
|
|
557
|
+
* @param params - Object containing comma-separated firearm IDs.
|
|
558
|
+
* @returns An array of firearms with full details for comparison.
|
|
559
|
+
* @throws {BadRequestError} If more than 5 IDs are provided or any ID is invalid.
|
|
560
|
+
* @throws {NotFoundError} If any of the specified firearms do not exist.
|
|
561
|
+
*
|
|
562
|
+
* @example
|
|
563
|
+
* ```typescript
|
|
564
|
+
* const { data } = await client.firearms.compare({ ids: 'glock-g17,sig-sauer-p320,beretta-92fs' });
|
|
565
|
+
* console.log(data.items.length); // 3
|
|
566
|
+
* ```
|
|
567
|
+
*/
|
|
568
|
+
async compare(params) {
|
|
569
|
+
return this.client.get("/v1/firearms/compare", params);
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Retrieve game metadata for firearms (archetypes, stat ranges, etc.).
|
|
573
|
+
*
|
|
574
|
+
* @param params - Optional archetype filter.
|
|
575
|
+
* @returns Game metadata including archetype definitions and stat ranges.
|
|
576
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
577
|
+
*
|
|
578
|
+
* @example
|
|
579
|
+
* ```typescript
|
|
580
|
+
* const { data } = await client.firearms.gameMeta({ archetype: 'sniper' });
|
|
581
|
+
* ```
|
|
582
|
+
*/
|
|
583
|
+
async gameMeta(params) {
|
|
584
|
+
return this.client.get("/v1/firearms/game-meta", params);
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* List all known action types across the database.
|
|
588
|
+
*
|
|
589
|
+
* @returns An array of distinct action type strings.
|
|
590
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
591
|
+
*
|
|
592
|
+
* @example
|
|
593
|
+
* ```typescript
|
|
594
|
+
* const { data } = await client.firearms.actionTypes();
|
|
595
|
+
* // ['Semi-automatic', 'Bolt action', 'Lever action', ...]
|
|
596
|
+
* ```
|
|
597
|
+
*/
|
|
598
|
+
async actionTypes() {
|
|
599
|
+
return this.client.get("/v1/firearms/action-types");
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Get available filter options for the firearms list endpoint.
|
|
603
|
+
*
|
|
604
|
+
* Returns distinct values for manufacturers, calibers, categories,
|
|
605
|
+
* action types, countries, and statuses that can be used as filter values.
|
|
606
|
+
*
|
|
607
|
+
* @returns An object mapping filter fields to their available values.
|
|
608
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
609
|
+
*
|
|
610
|
+
* @example
|
|
611
|
+
* ```typescript
|
|
612
|
+
* const { data } = await client.firearms.filterOptions();
|
|
613
|
+
* console.log(data.manufacturers); // ['beretta', 'colt', ...]
|
|
614
|
+
* console.log(data.categories); // ['pistol', 'rifle', ...]
|
|
615
|
+
* ```
|
|
616
|
+
*/
|
|
617
|
+
async filterOptions() {
|
|
618
|
+
return this.client.get("/v1/firearms/filter-options");
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Get a random firearm, optionally filtered by category or country.
|
|
622
|
+
*
|
|
623
|
+
* @param params - Optional category and country filters.
|
|
624
|
+
* @returns A single randomly selected firearm.
|
|
625
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
626
|
+
*
|
|
627
|
+
* @example
|
|
628
|
+
* ```typescript
|
|
629
|
+
* const { data } = await client.firearms.random({ category: 'pistol' });
|
|
630
|
+
* console.log(data.name);
|
|
631
|
+
* ```
|
|
632
|
+
*/
|
|
633
|
+
async random(params) {
|
|
634
|
+
return this.client.get("/v1/firearms/random", params);
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Get top firearms ranked by a specific statistic.
|
|
638
|
+
*
|
|
639
|
+
* @param params - The stat to rank by and optional category/limit filters.
|
|
640
|
+
* @returns An ordered array of firearms ranked by the specified stat.
|
|
641
|
+
* @throws {BadRequestError} If the stat value is not a recognized ranking metric.
|
|
642
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
643
|
+
*
|
|
644
|
+
* @example
|
|
645
|
+
* ```typescript
|
|
646
|
+
* const { data } = await client.firearms.top({
|
|
647
|
+
* stat: 'lightest',
|
|
648
|
+
* category: 'pistol',
|
|
649
|
+
* limit: 5,
|
|
650
|
+
* });
|
|
651
|
+
* ```
|
|
652
|
+
*/
|
|
653
|
+
async top(params) {
|
|
654
|
+
return this.client.get("/v1/firearms/top", params);
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Compare two firearms in a head-to-head matchup.
|
|
658
|
+
*
|
|
659
|
+
* @param params - The slugs of the two firearms to compare.
|
|
660
|
+
* @returns A detailed head-to-head comparison with per-stat breakdowns.
|
|
661
|
+
* @throws {NotFoundError} If either firearm does not exist.
|
|
662
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
663
|
+
*
|
|
664
|
+
* @example
|
|
665
|
+
* ```typescript
|
|
666
|
+
* const { data } = await client.firearms.headToHead({ a: 'glock-g17', b: 'sig-sauer-p320' });
|
|
667
|
+
* console.log(data.winner);
|
|
668
|
+
* ```
|
|
669
|
+
*/
|
|
670
|
+
async headToHead(params) {
|
|
671
|
+
return this.client.get("/v1/firearms/head-to-head", params);
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Filter firearms by a specific feature.
|
|
675
|
+
*
|
|
676
|
+
* @param params - The feature name and optional category filter with pagination.
|
|
677
|
+
* @returns A paginated list of firearms that have the specified feature.
|
|
678
|
+
* @throws {BadRequestError} If the feature name is empty.
|
|
679
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
680
|
+
*
|
|
681
|
+
* @example
|
|
682
|
+
* ```typescript
|
|
683
|
+
* const result = await client.firearms.byFeature({ feature: 'threaded-barrel' });
|
|
684
|
+
* ```
|
|
685
|
+
*/
|
|
686
|
+
async byFeature(params) {
|
|
687
|
+
return this.client.getPaginated("/v1/firearms/by-feature", params);
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Filter firearms by action type.
|
|
691
|
+
*
|
|
692
|
+
* @param params - The action type string with pagination.
|
|
693
|
+
* @returns A paginated list of firearms with the specified action type.
|
|
694
|
+
* @throws {BadRequestError} If the action type is empty.
|
|
695
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
696
|
+
*
|
|
697
|
+
* @example
|
|
698
|
+
* ```typescript
|
|
699
|
+
* const result = await client.firearms.byAction({ action: 'semi-automatic' });
|
|
700
|
+
* ```
|
|
701
|
+
*/
|
|
702
|
+
async byAction(params) {
|
|
703
|
+
return this.client.getPaginated("/v1/firearms/by-action", params);
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Filter firearms by frame/component material.
|
|
707
|
+
*
|
|
708
|
+
* @param params - The material name, component type, and pagination.
|
|
709
|
+
* @returns A paginated list of firearms using the specified material.
|
|
710
|
+
* @throws {BadRequestError} If the material or component is invalid.
|
|
711
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
712
|
+
*
|
|
713
|
+
* @example
|
|
714
|
+
* ```typescript
|
|
715
|
+
* const result = await client.firearms.byMaterial({
|
|
716
|
+
* material: 'polymer',
|
|
717
|
+
* component: 'frame',
|
|
718
|
+
* });
|
|
719
|
+
* ```
|
|
720
|
+
*/
|
|
721
|
+
async byMaterial(params) {
|
|
722
|
+
return this.client.getPaginated("/v1/firearms/by-material", params);
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Filter firearms by designer name.
|
|
726
|
+
*
|
|
727
|
+
* @param params - The designer name string with pagination.
|
|
728
|
+
* @returns A paginated list of firearms designed by the specified person.
|
|
729
|
+
* @throws {BadRequestError} If the designer name is empty.
|
|
730
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
731
|
+
*
|
|
732
|
+
* @example
|
|
733
|
+
* ```typescript
|
|
734
|
+
* const result = await client.firearms.byDesigner({ designer: 'John Browning' });
|
|
735
|
+
* ```
|
|
736
|
+
*/
|
|
737
|
+
async byDesigner(params) {
|
|
738
|
+
return this.client.getPaginated("/v1/firearms/by-designer", params);
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Get firearms ranked by computed power rating.
|
|
742
|
+
*
|
|
743
|
+
* @param params - Optional category filter with pagination.
|
|
744
|
+
* @returns A paginated list of firearms with their power ratings.
|
|
745
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
746
|
+
*
|
|
747
|
+
* @example
|
|
748
|
+
* ```typescript
|
|
749
|
+
* const result = await client.firearms.powerRating({ category: 'rifle' });
|
|
750
|
+
* for (const item of result.data) {
|
|
751
|
+
* console.log(`${item.name}: ${item.rating}`);
|
|
752
|
+
* }
|
|
753
|
+
* ```
|
|
754
|
+
*/
|
|
755
|
+
async powerRating(params) {
|
|
756
|
+
return this.client.getPaginated("/v1/firearms/power-rating", params);
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Get firearms arranged in a chronological timeline.
|
|
760
|
+
*
|
|
761
|
+
* @param params - Optional year range, category filter, and pagination.
|
|
762
|
+
* @returns A paginated list of firearms ordered by introduction date.
|
|
763
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
764
|
+
*
|
|
765
|
+
* @example
|
|
766
|
+
* ```typescript
|
|
767
|
+
* const result = await client.firearms.timeline({ from: 1900, to: 1950 });
|
|
768
|
+
* ```
|
|
769
|
+
*/
|
|
770
|
+
async timeline(params) {
|
|
771
|
+
return this.client.getPaginated("/v1/firearms/timeline", params);
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Filter firearms by military conflict.
|
|
775
|
+
*
|
|
776
|
+
* @param params - The conflict identifier string with pagination.
|
|
777
|
+
* @returns A paginated list of firearms used in the specified conflict.
|
|
778
|
+
* @throws {BadRequestError} If the conflict name is empty.
|
|
779
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
780
|
+
*
|
|
781
|
+
* @example
|
|
782
|
+
* ```typescript
|
|
783
|
+
* const result = await client.firearms.byConflict({ conflict: 'world-war-ii' });
|
|
784
|
+
* ```
|
|
785
|
+
*/
|
|
786
|
+
async byConflict(params) {
|
|
787
|
+
return this.client.getPaginated("/v1/firearms/by-conflict", params);
|
|
788
|
+
}
|
|
789
|
+
// ---------------------------------------------------------------------------
|
|
790
|
+
// Single-resource endpoints
|
|
791
|
+
// ---------------------------------------------------------------------------
|
|
792
|
+
/**
|
|
793
|
+
* Get a single firearm by its slug or ID.
|
|
794
|
+
*
|
|
795
|
+
* @param id - The firearm slug (e.g. `"glock-g17"`) or numeric ID.
|
|
796
|
+
* @returns The full firearm record with all available fields.
|
|
797
|
+
* @throws {NotFoundError} If no firearm matches the given identifier.
|
|
798
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
799
|
+
*
|
|
800
|
+
* @example
|
|
801
|
+
* ```typescript
|
|
802
|
+
* const { data } = await client.firearms.get('beretta-92fs');
|
|
803
|
+
* console.log(data.name); // "Beretta 92FS"
|
|
804
|
+
* console.log(data.manufacturerId); // "beretta"
|
|
805
|
+
* ```
|
|
806
|
+
*/
|
|
807
|
+
async get(id) {
|
|
808
|
+
return this.client.get(`/v1/firearms/${encodeURIComponent(id)}`);
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Get all variants of a firearm.
|
|
812
|
+
*
|
|
813
|
+
* @param id - The parent firearm slug or ID.
|
|
814
|
+
* @returns An array of variant firearms derived from the parent.
|
|
815
|
+
* @throws {NotFoundError} If the parent firearm does not exist.
|
|
816
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
817
|
+
*
|
|
818
|
+
* @example
|
|
819
|
+
* ```typescript
|
|
820
|
+
* const { data } = await client.firearms.getVariants('colt-1911');
|
|
821
|
+
* console.log(data.map(v => v.name));
|
|
822
|
+
* ```
|
|
823
|
+
*/
|
|
824
|
+
async getVariants(id) {
|
|
825
|
+
return this.client.get(`/v1/firearms/${encodeURIComponent(id)}/variants`);
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Get images for a firearm.
|
|
829
|
+
*
|
|
830
|
+
* @param id - The firearm slug or ID.
|
|
831
|
+
* @returns An array of image records with URLs, dimensions, and metadata.
|
|
832
|
+
* @throws {NotFoundError} If the firearm does not exist.
|
|
833
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
834
|
+
*
|
|
835
|
+
* @example
|
|
836
|
+
* ```typescript
|
|
837
|
+
* const { data } = await client.firearms.getImages('sig-sauer-p226');
|
|
838
|
+
* for (const img of data) {
|
|
839
|
+
* console.log(img.url, img.width, img.height);
|
|
840
|
+
* }
|
|
841
|
+
* ```
|
|
842
|
+
*/
|
|
843
|
+
async getImages(id) {
|
|
844
|
+
return this.client.get(`/v1/firearms/${encodeURIComponent(id)}/images`);
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Get computed game statistics for a firearm.
|
|
848
|
+
*
|
|
849
|
+
* @param id - The firearm slug or ID.
|
|
850
|
+
* @returns Game-balanced stats including damage, accuracy, range, etc.
|
|
851
|
+
* @throws {NotFoundError} If the firearm does not exist.
|
|
852
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
853
|
+
*
|
|
854
|
+
* @example
|
|
855
|
+
* ```typescript
|
|
856
|
+
* const { data } = await client.firearms.getGameStats('ak-47');
|
|
857
|
+
* console.log(data.damage, data.accuracy, data.range);
|
|
858
|
+
* ```
|
|
859
|
+
*/
|
|
860
|
+
async getGameStats(id) {
|
|
861
|
+
return this.client.get(`/v1/firearms/${encodeURIComponent(id)}/game-stats`);
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Get physical dimensions for a firearm.
|
|
865
|
+
*
|
|
866
|
+
* @param id - The firearm slug or ID.
|
|
867
|
+
* @returns Detailed dimension data (length, height, width, barrel length, weight).
|
|
868
|
+
* @throws {NotFoundError} If the firearm does not exist.
|
|
869
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
870
|
+
*
|
|
871
|
+
* @example
|
|
872
|
+
* ```typescript
|
|
873
|
+
* const { data } = await client.firearms.getDimensions('glock-g19');
|
|
874
|
+
* console.log(`${data.overallLengthMm}mm overall`);
|
|
875
|
+
* ```
|
|
876
|
+
*/
|
|
877
|
+
async getDimensions(id) {
|
|
878
|
+
return this.client.get(`/v1/firearms/${encodeURIComponent(id)}/dimensions`);
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Get known military/law-enforcement adopters of a firearm.
|
|
882
|
+
*
|
|
883
|
+
* @param id - The firearm slug or ID.
|
|
884
|
+
* @returns An array of users/adopters with country and organization data.
|
|
885
|
+
* @throws {NotFoundError} If the firearm does not exist.
|
|
886
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
887
|
+
*
|
|
888
|
+
* @example
|
|
889
|
+
* ```typescript
|
|
890
|
+
* const { data } = await client.firearms.getUsers('beretta-m9');
|
|
891
|
+
* for (const user of data) {
|
|
892
|
+
* console.log(user.country, user.organization);
|
|
893
|
+
* }
|
|
894
|
+
* ```
|
|
895
|
+
*/
|
|
896
|
+
async getUsers(id) {
|
|
897
|
+
return this.client.get(`/v1/firearms/${encodeURIComponent(id)}/users`);
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Get the family tree (lineage) of a firearm.
|
|
901
|
+
*
|
|
902
|
+
* @param id - The firearm slug or ID.
|
|
903
|
+
* @returns A tree structure showing predecessors, descendants, and related models.
|
|
904
|
+
* @throws {NotFoundError} If the firearm does not exist.
|
|
905
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
906
|
+
*
|
|
907
|
+
* @example
|
|
908
|
+
* ```typescript
|
|
909
|
+
* const { data } = await client.firearms.getFamilyTree('m16a2');
|
|
910
|
+
* console.log(data.ancestors, data.descendants);
|
|
911
|
+
* ```
|
|
912
|
+
*/
|
|
913
|
+
async getFamilyTree(id) {
|
|
914
|
+
return this.client.get(`/v1/firearms/${encodeURIComponent(id)}/family-tree`);
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Get firearms similar to a given firearm.
|
|
918
|
+
*
|
|
919
|
+
* @param id - The firearm slug or ID.
|
|
920
|
+
* @returns An array of similar firearms ranked by similarity score.
|
|
921
|
+
* @throws {NotFoundError} If the firearm does not exist.
|
|
922
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
923
|
+
*
|
|
924
|
+
* @example
|
|
925
|
+
* ```typescript
|
|
926
|
+
* const { data } = await client.firearms.getSimilar('glock-g17');
|
|
927
|
+
* for (const match of data) {
|
|
928
|
+
* console.log(match.name, match.similarityScore);
|
|
929
|
+
* }
|
|
930
|
+
* ```
|
|
931
|
+
*/
|
|
932
|
+
async getSimilar(id) {
|
|
933
|
+
return this.client.get(`/v1/firearms/${encodeURIComponent(id)}/similar`);
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Get the worldwide adoption map for a firearm.
|
|
937
|
+
*
|
|
938
|
+
* @param id - The firearm slug or ID.
|
|
939
|
+
* @returns Adoption data by country with usage type and date ranges.
|
|
940
|
+
* @throws {NotFoundError} If the firearm does not exist.
|
|
941
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
942
|
+
*
|
|
943
|
+
* @example
|
|
944
|
+
* ```typescript
|
|
945
|
+
* const { data } = await client.firearms.getAdoptionMap('fn-fal');
|
|
946
|
+
* console.log(data.countries.length, 'countries adopted this firearm');
|
|
947
|
+
* ```
|
|
948
|
+
*/
|
|
949
|
+
async getAdoptionMap(id) {
|
|
950
|
+
return this.client.get(`/v1/firearms/${encodeURIComponent(id)}/adoption-map`);
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Get the full game profile for a firearm.
|
|
954
|
+
*
|
|
955
|
+
* @param id - The firearm slug or ID.
|
|
956
|
+
* @returns Game-specific profile including archetype, tier, and stat breakdown.
|
|
957
|
+
* @throws {NotFoundError} If the firearm does not exist.
|
|
958
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
959
|
+
*
|
|
960
|
+
* @example
|
|
961
|
+
* ```typescript
|
|
962
|
+
* const { data } = await client.firearms.getGameProfile('mp5');
|
|
963
|
+
* console.log(data.archetype, data.tier);
|
|
964
|
+
* ```
|
|
965
|
+
*/
|
|
966
|
+
async getGameProfile(id) {
|
|
967
|
+
return this.client.get(`/v1/firearms/${encodeURIComponent(id)}/game-profile`);
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Get an SVG silhouette (line art) of a firearm.
|
|
971
|
+
*
|
|
972
|
+
* @param id - The firearm slug or ID.
|
|
973
|
+
* @param params - Optional format and stroke customization parameters.
|
|
974
|
+
* @returns Silhouette data in the requested format (raw SVG, data URI, or JSON).
|
|
975
|
+
* @throws {NotFoundError} If the firearm does not exist or has no silhouette.
|
|
976
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
977
|
+
*
|
|
978
|
+
* @example
|
|
979
|
+
* ```typescript
|
|
980
|
+
* const { data } = await client.firearms.getSilhouette('ak-47', {
|
|
981
|
+
* format: 'datauri',
|
|
982
|
+
* stroke_width: 2,
|
|
983
|
+
* stroke_color: '#333',
|
|
984
|
+
* });
|
|
985
|
+
* // Use data.dataUri in an <img> tag
|
|
986
|
+
* ```
|
|
987
|
+
*/
|
|
988
|
+
async getSilhouette(id, params) {
|
|
989
|
+
return this.client.get(
|
|
990
|
+
`/v1/firearms/${encodeURIComponent(id)}/silhouette`,
|
|
991
|
+
params
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Calculate ballistics for a firearm with specific ammunition.
|
|
996
|
+
*
|
|
997
|
+
* @param id - The firearm slug or ID.
|
|
998
|
+
* @param params - Ammunition ID and optional ballistic parameters.
|
|
999
|
+
* @returns Calculated ballistic data including velocity, energy, and trajectory.
|
|
1000
|
+
* @throws {NotFoundError} If the firearm or ammunition does not exist.
|
|
1001
|
+
* @throws {BadRequestError} If the ammunition is incompatible with the firearm.
|
|
1002
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1003
|
+
*
|
|
1004
|
+
* @example
|
|
1005
|
+
* ```typescript
|
|
1006
|
+
* const { data } = await client.firearms.calculate('glock-g17', {
|
|
1007
|
+
* ammo_id: '9mm-federal-hst-124gr',
|
|
1008
|
+
* });
|
|
1009
|
+
* console.log(data.muzzleVelocity, data.muzzleEnergy);
|
|
1010
|
+
* ```
|
|
1011
|
+
*/
|
|
1012
|
+
async calculate(id, params) {
|
|
1013
|
+
return this.client.get(
|
|
1014
|
+
`/v1/firearms/${encodeURIComponent(id)}/calculate`,
|
|
1015
|
+
params
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Load a firearm with ammunition and get combined performance data.
|
|
1020
|
+
*
|
|
1021
|
+
* @param id - The firearm slug or ID.
|
|
1022
|
+
* @param params - Optional ammunition ID to load.
|
|
1023
|
+
* @returns Combined firearm + ammunition data with computed performance metrics.
|
|
1024
|
+
* @throws {NotFoundError} If the firearm does not exist.
|
|
1025
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1026
|
+
*
|
|
1027
|
+
* @example
|
|
1028
|
+
* ```typescript
|
|
1029
|
+
* const { data } = await client.firearms.load('sig-sauer-p226', {
|
|
1030
|
+
* ammo_id: '9mm-winchester-ranger-147gr',
|
|
1031
|
+
* });
|
|
1032
|
+
* ```
|
|
1033
|
+
*/
|
|
1034
|
+
async load(id, params) {
|
|
1035
|
+
return this.client.get(
|
|
1036
|
+
`/v1/firearms/${encodeURIComponent(id)}/load`,
|
|
1037
|
+
params
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
|
|
1042
|
+
// src/resources/manufacturers.ts
|
|
1043
|
+
var ManufacturersResource = class {
|
|
1044
|
+
constructor(client) {
|
|
1045
|
+
this.client = client;
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
1048
|
+
* List manufacturers with optional filters and pagination.
|
|
1049
|
+
*
|
|
1050
|
+
* @param params - Optional query parameters for filtering by country, sorting, and pagination.
|
|
1051
|
+
* @returns A paginated list of manufacturers.
|
|
1052
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1053
|
+
*
|
|
1054
|
+
* @example
|
|
1055
|
+
* ```typescript
|
|
1056
|
+
* const result = await client.manufacturers.list({
|
|
1057
|
+
* country: 'DE',
|
|
1058
|
+
* sort: 'name',
|
|
1059
|
+
* order: 'asc',
|
|
1060
|
+
* per_page: 50,
|
|
1061
|
+
* });
|
|
1062
|
+
* ```
|
|
1063
|
+
*/
|
|
1064
|
+
async list(params) {
|
|
1065
|
+
return this.client.getPaginated("/v1/manufacturers", params);
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Auto-paginate through all manufacturers matching the given filters.
|
|
1069
|
+
*
|
|
1070
|
+
* Returns an async iterator that fetches pages on demand, yielding
|
|
1071
|
+
* individual {@link Manufacturer} objects.
|
|
1072
|
+
*
|
|
1073
|
+
* @param params - Optional query parameters for filtering and sorting.
|
|
1074
|
+
* @returns An async iterable iterator yielding individual manufacturers.
|
|
1075
|
+
*
|
|
1076
|
+
* @example
|
|
1077
|
+
* ```typescript
|
|
1078
|
+
* for await (const mfr of client.manufacturers.listAutoPaging()) {
|
|
1079
|
+
* console.log(mfr.name, mfr.country);
|
|
1080
|
+
* }
|
|
1081
|
+
* ```
|
|
1082
|
+
*/
|
|
1083
|
+
async *listAutoPaging(params) {
|
|
1084
|
+
let page = params?.page ?? 1;
|
|
1085
|
+
while (true) {
|
|
1086
|
+
const result = await this.list({ ...params, page });
|
|
1087
|
+
for (const item of result.data) {
|
|
1088
|
+
yield item;
|
|
1089
|
+
}
|
|
1090
|
+
if (!result.pagination.totalPages || page >= result.pagination.totalPages) break;
|
|
1091
|
+
page++;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Get a single manufacturer by its slug or ID.
|
|
1096
|
+
*
|
|
1097
|
+
* @param id - The manufacturer slug (e.g. `"beretta"`) or numeric ID.
|
|
1098
|
+
* @returns The full manufacturer record.
|
|
1099
|
+
* @throws {NotFoundError} If no manufacturer matches the given identifier.
|
|
1100
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1101
|
+
*
|
|
1102
|
+
* @example
|
|
1103
|
+
* ```typescript
|
|
1104
|
+
* const { data } = await client.manufacturers.get('sig-sauer');
|
|
1105
|
+
* console.log(data.name, data.foundedYear, data.country);
|
|
1106
|
+
* ```
|
|
1107
|
+
*/
|
|
1108
|
+
async get(id) {
|
|
1109
|
+
return this.client.get(`/v1/manufacturers/${encodeURIComponent(id)}`);
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Get all firearms produced by a manufacturer.
|
|
1113
|
+
*
|
|
1114
|
+
* @param id - The manufacturer slug or ID.
|
|
1115
|
+
* @param params - Optional pagination parameters.
|
|
1116
|
+
* @returns A paginated list of firearms from the specified manufacturer.
|
|
1117
|
+
* @throws {NotFoundError} If the manufacturer does not exist.
|
|
1118
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1119
|
+
*
|
|
1120
|
+
* @example
|
|
1121
|
+
* ```typescript
|
|
1122
|
+
* const result = await client.manufacturers.getFirearms('colt', { per_page: 50 });
|
|
1123
|
+
* console.log(`Colt makes ${result.pagination.total} firearms`);
|
|
1124
|
+
* ```
|
|
1125
|
+
*/
|
|
1126
|
+
async getFirearms(id, params) {
|
|
1127
|
+
return this.client.getPaginated(
|
|
1128
|
+
`/v1/manufacturers/${encodeURIComponent(id)}/firearms`,
|
|
1129
|
+
params
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Get a chronological timeline for a manufacturer.
|
|
1134
|
+
*
|
|
1135
|
+
* @param id - The manufacturer slug or ID.
|
|
1136
|
+
* @returns Timeline data with key events, product launches, and milestones.
|
|
1137
|
+
* @throws {NotFoundError} If the manufacturer does not exist.
|
|
1138
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1139
|
+
*
|
|
1140
|
+
* @example
|
|
1141
|
+
* ```typescript
|
|
1142
|
+
* const { data } = await client.manufacturers.getTimeline('browning');
|
|
1143
|
+
* for (const event of data.events) {
|
|
1144
|
+
* console.log(event.year, event.description);
|
|
1145
|
+
* }
|
|
1146
|
+
* ```
|
|
1147
|
+
*/
|
|
1148
|
+
async getTimeline(id) {
|
|
1149
|
+
return this.client.get(`/v1/manufacturers/${encodeURIComponent(id)}/timeline`);
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Get aggregate statistics for a manufacturer.
|
|
1153
|
+
*
|
|
1154
|
+
* @param id - The manufacturer slug or ID.
|
|
1155
|
+
* @returns Statistical summary including firearm counts, caliber distribution, and more.
|
|
1156
|
+
* @throws {NotFoundError} If the manufacturer does not exist.
|
|
1157
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1158
|
+
*
|
|
1159
|
+
* @example
|
|
1160
|
+
* ```typescript
|
|
1161
|
+
* const { data } = await client.manufacturers.getStats('glock');
|
|
1162
|
+
* console.log(data.totalFirearms, data.caliberBreakdown);
|
|
1163
|
+
* ```
|
|
1164
|
+
*/
|
|
1165
|
+
async getStats(id) {
|
|
1166
|
+
return this.client.get(`/v1/manufacturers/${encodeURIComponent(id)}/stats`);
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
|
|
1170
|
+
// src/resources/calibers.ts
|
|
1171
|
+
var CalibersResource = class {
|
|
1172
|
+
constructor(client) {
|
|
1173
|
+
this.client = client;
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* List calibers with optional filters and pagination.
|
|
1177
|
+
*
|
|
1178
|
+
* @param params - Optional query parameters for filtering by cartridge type, primer type, etc.
|
|
1179
|
+
* @returns A paginated list of calibers.
|
|
1180
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1181
|
+
*
|
|
1182
|
+
* @example
|
|
1183
|
+
* ```typescript
|
|
1184
|
+
* const result = await client.calibers.list({
|
|
1185
|
+
* cartridge_type: 'centerfire',
|
|
1186
|
+
* primer_type: 'boxer',
|
|
1187
|
+
* per_page: 50,
|
|
1188
|
+
* });
|
|
1189
|
+
* ```
|
|
1190
|
+
*/
|
|
1191
|
+
async list(params) {
|
|
1192
|
+
return this.client.getPaginated("/v1/calibers", params);
|
|
1193
|
+
}
|
|
1194
|
+
/**
|
|
1195
|
+
* Auto-paginate through all calibers matching the given filters.
|
|
1196
|
+
*
|
|
1197
|
+
* Returns an async iterator that fetches pages on demand, yielding
|
|
1198
|
+
* individual {@link Caliber} objects.
|
|
1199
|
+
*
|
|
1200
|
+
* @param params - Optional query parameters for filtering and sorting.
|
|
1201
|
+
* @returns An async iterable iterator yielding individual calibers.
|
|
1202
|
+
*
|
|
1203
|
+
* @example
|
|
1204
|
+
* ```typescript
|
|
1205
|
+
* for await (const caliber of client.calibers.listAutoPaging()) {
|
|
1206
|
+
* console.log(caliber.name, caliber.bulletDiameterMm);
|
|
1207
|
+
* }
|
|
1208
|
+
* ```
|
|
1209
|
+
*/
|
|
1210
|
+
async *listAutoPaging(params) {
|
|
1211
|
+
let page = params?.page ?? 1;
|
|
1212
|
+
while (true) {
|
|
1213
|
+
const result = await this.list({ ...params, page });
|
|
1214
|
+
for (const item of result.data) {
|
|
1215
|
+
yield item;
|
|
1216
|
+
}
|
|
1217
|
+
if (!result.pagination.totalPages || page >= result.pagination.totalPages) break;
|
|
1218
|
+
page++;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Compare up to 5 calibers side by side.
|
|
1223
|
+
*
|
|
1224
|
+
* @param params - Object containing comma-separated caliber IDs.
|
|
1225
|
+
* @returns An array of calibers with full details for comparison.
|
|
1226
|
+
* @throws {BadRequestError} If more than 5 IDs are provided.
|
|
1227
|
+
* @throws {NotFoundError} If any of the specified calibers do not exist.
|
|
1228
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1229
|
+
*
|
|
1230
|
+
* @example
|
|
1231
|
+
* ```typescript
|
|
1232
|
+
* const { data } = await client.calibers.compare({
|
|
1233
|
+
* ids: '9x19mm-parabellum,45-acp,40-s-w',
|
|
1234
|
+
* });
|
|
1235
|
+
* ```
|
|
1236
|
+
*/
|
|
1237
|
+
async compare(params) {
|
|
1238
|
+
return this.client.get("/v1/calibers/compare", params);
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Get ballistics data for a caliber at a specified distance.
|
|
1242
|
+
*
|
|
1243
|
+
* @param params - Caliber ID and distance in meters.
|
|
1244
|
+
* @returns Ballistic data including velocity, energy, and drop at the specified distance.
|
|
1245
|
+
* @throws {NotFoundError} If the caliber does not exist.
|
|
1246
|
+
* @throws {BadRequestError} If the distance is out of range.
|
|
1247
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1248
|
+
*
|
|
1249
|
+
* @example
|
|
1250
|
+
* ```typescript
|
|
1251
|
+
* const { data } = await client.calibers.ballistics({
|
|
1252
|
+
* id: '9x19mm-parabellum',
|
|
1253
|
+
* distance: 100,
|
|
1254
|
+
* });
|
|
1255
|
+
* console.log(data.velocity, data.energy, data.drop);
|
|
1256
|
+
* ```
|
|
1257
|
+
*/
|
|
1258
|
+
async ballistics(params) {
|
|
1259
|
+
return this.client.get("/v1/calibers/ballistics", params);
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Get a single caliber by its slug or ID.
|
|
1263
|
+
*
|
|
1264
|
+
* @param id - The caliber slug (e.g. `"9x19mm-parabellum"`) or numeric ID.
|
|
1265
|
+
* @returns The full caliber record with all specifications.
|
|
1266
|
+
* @throws {NotFoundError} If no caliber matches the given identifier.
|
|
1267
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1268
|
+
*
|
|
1269
|
+
* @example
|
|
1270
|
+
* ```typescript
|
|
1271
|
+
* const { data } = await client.calibers.get('45-acp');
|
|
1272
|
+
* console.log(data.name, data.bulletDiameterMm, data.caseLengthMm);
|
|
1273
|
+
* ```
|
|
1274
|
+
*/
|
|
1275
|
+
async get(id) {
|
|
1276
|
+
return this.client.get(`/v1/calibers/${encodeURIComponent(id)}`);
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Get all firearms that use a specific caliber.
|
|
1280
|
+
*
|
|
1281
|
+
* @param id - The caliber slug or ID.
|
|
1282
|
+
* @param params - Optional pagination parameters.
|
|
1283
|
+
* @returns A paginated list of firearms chambered in the specified caliber.
|
|
1284
|
+
* @throws {NotFoundError} If the caliber does not exist.
|
|
1285
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1286
|
+
*
|
|
1287
|
+
* @example
|
|
1288
|
+
* ```typescript
|
|
1289
|
+
* const result = await client.calibers.getFirearms('9x19mm-parabellum', { per_page: 50 });
|
|
1290
|
+
* console.log(`${result.pagination.total} firearms use 9mm`);
|
|
1291
|
+
* ```
|
|
1292
|
+
*/
|
|
1293
|
+
async getFirearms(id, params) {
|
|
1294
|
+
return this.client.getPaginated(
|
|
1295
|
+
`/v1/calibers/${encodeURIComponent(id)}/firearms`,
|
|
1296
|
+
params
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Get the parent caliber chain (ancestry) for a caliber.
|
|
1301
|
+
*
|
|
1302
|
+
* @param id - The caliber slug or ID.
|
|
1303
|
+
* @returns An ordered array of calibers from the given caliber up to the root ancestor.
|
|
1304
|
+
* @throws {NotFoundError} If the caliber does not exist.
|
|
1305
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1306
|
+
*
|
|
1307
|
+
* @example
|
|
1308
|
+
* ```typescript
|
|
1309
|
+
* const { data } = await client.calibers.getParentChain('300-blackout');
|
|
1310
|
+
* // [300 Blackout, 5.56x45mm NATO, .223 Remington, ...]
|
|
1311
|
+
* ```
|
|
1312
|
+
*/
|
|
1313
|
+
async getParentChain(id) {
|
|
1314
|
+
return this.client.get(`/v1/calibers/${encodeURIComponent(id)}/parent-chain`);
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Get the full family tree of related calibers.
|
|
1318
|
+
*
|
|
1319
|
+
* @param id - The caliber slug or ID.
|
|
1320
|
+
* @returns An array of calibers in the same family (parent, siblings, children).
|
|
1321
|
+
* @throws {NotFoundError} If the caliber does not exist.
|
|
1322
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1323
|
+
*
|
|
1324
|
+
* @example
|
|
1325
|
+
* ```typescript
|
|
1326
|
+
* const { data } = await client.calibers.getFamily('9x19mm-parabellum');
|
|
1327
|
+
* console.log(data.map(c => c.name));
|
|
1328
|
+
* ```
|
|
1329
|
+
*/
|
|
1330
|
+
async getFamily(id) {
|
|
1331
|
+
return this.client.get(`/v1/calibers/${encodeURIComponent(id)}/family`);
|
|
1332
|
+
}
|
|
1333
|
+
/**
|
|
1334
|
+
* Get ammunition loads available for a caliber.
|
|
1335
|
+
*
|
|
1336
|
+
* @param id - The caliber slug or ID.
|
|
1337
|
+
* @param params - Optional pagination parameters.
|
|
1338
|
+
* @returns A paginated list of ammunition loads for the specified caliber.
|
|
1339
|
+
* @throws {NotFoundError} If the caliber does not exist.
|
|
1340
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1341
|
+
*
|
|
1342
|
+
* @example
|
|
1343
|
+
* ```typescript
|
|
1344
|
+
* const result = await client.calibers.getAmmunition('9x19mm-parabellum', { per_page: 25 });
|
|
1345
|
+
* for (const ammo of result.data) {
|
|
1346
|
+
* console.log(ammo.name, ammo.bulletWeightGrains);
|
|
1347
|
+
* }
|
|
1348
|
+
* ```
|
|
1349
|
+
*/
|
|
1350
|
+
async getAmmunition(id, params) {
|
|
1351
|
+
return this.client.getPaginated(
|
|
1352
|
+
`/v1/calibers/${encodeURIComponent(id)}/ammunition`,
|
|
1353
|
+
params
|
|
1354
|
+
);
|
|
1355
|
+
}
|
|
1356
|
+
};
|
|
1357
|
+
|
|
1358
|
+
// src/resources/categories.ts
|
|
1359
|
+
var CategoriesResource = class {
|
|
1360
|
+
constructor(client) {
|
|
1361
|
+
this.client = client;
|
|
1362
|
+
}
|
|
1363
|
+
/**
|
|
1364
|
+
* List all firearm categories.
|
|
1365
|
+
*
|
|
1366
|
+
* @returns An array of all category records.
|
|
1367
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1368
|
+
*
|
|
1369
|
+
* @example
|
|
1370
|
+
* ```typescript
|
|
1371
|
+
* const { data } = await client.categories.list();
|
|
1372
|
+
* // [{ slug: 'pistol', name: 'Pistol' }, { slug: 'rifle', name: 'Rifle' }, ...]
|
|
1373
|
+
* ```
|
|
1374
|
+
*/
|
|
1375
|
+
async list() {
|
|
1376
|
+
return this.client.get("/v1/categories");
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* Get all firearms within a specific category.
|
|
1380
|
+
*
|
|
1381
|
+
* @param slug - The category slug (e.g. `"pistol"`, `"rifle"`, `"shotgun"`).
|
|
1382
|
+
* @param params - Optional pagination parameters.
|
|
1383
|
+
* @returns A paginated list of firearms in the specified category.
|
|
1384
|
+
* @throws {NotFoundError} If the category slug does not exist.
|
|
1385
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1386
|
+
*
|
|
1387
|
+
* @example
|
|
1388
|
+
* ```typescript
|
|
1389
|
+
* const result = await client.categories.getFirearms('shotgun', {
|
|
1390
|
+
* per_page: 25,
|
|
1391
|
+
* sort: 'name',
|
|
1392
|
+
* order: 'asc',
|
|
1393
|
+
* });
|
|
1394
|
+
* console.log(`${result.pagination.total} shotguns in database`);
|
|
1395
|
+
* ```
|
|
1396
|
+
*/
|
|
1397
|
+
async getFirearms(slug, params) {
|
|
1398
|
+
return this.client.getPaginated(
|
|
1399
|
+
`/v1/categories/${encodeURIComponent(slug)}/firearms`,
|
|
1400
|
+
params
|
|
1401
|
+
);
|
|
1402
|
+
}
|
|
1403
|
+
};
|
|
1404
|
+
|
|
1405
|
+
// src/resources/stats.ts
|
|
1406
|
+
var StatsResource = class {
|
|
1407
|
+
constructor(client) {
|
|
1408
|
+
this.client = client;
|
|
1409
|
+
}
|
|
1410
|
+
/**
|
|
1411
|
+
* Get a high-level summary of the database.
|
|
1412
|
+
*
|
|
1413
|
+
* @returns Summary counts for firearms, manufacturers, calibers, and categories.
|
|
1414
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1415
|
+
*
|
|
1416
|
+
* @example
|
|
1417
|
+
* ```typescript
|
|
1418
|
+
* const { data } = await client.stats.summary();
|
|
1419
|
+
* console.log(`Database has ${data.totalFirearms} firearms`);
|
|
1420
|
+
* ```
|
|
1421
|
+
*/
|
|
1422
|
+
async summary() {
|
|
1423
|
+
return this.client.get("/v1/stats/summary");
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Get firearm counts grouped by production status.
|
|
1427
|
+
*
|
|
1428
|
+
* @returns Counts for in-production, discontinued, and prototype firearms.
|
|
1429
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1430
|
+
*
|
|
1431
|
+
* @example
|
|
1432
|
+
* ```typescript
|
|
1433
|
+
* const { data } = await client.stats.productionStatus();
|
|
1434
|
+
* // { in_production: 342, discontinued: 891, prototype: 12 }
|
|
1435
|
+
* ```
|
|
1436
|
+
*/
|
|
1437
|
+
async productionStatus() {
|
|
1438
|
+
return this.client.get("/v1/stats/production-status");
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Get field coverage statistics across the database.
|
|
1442
|
+
*
|
|
1443
|
+
* Shows the percentage of firearms that have data for each field,
|
|
1444
|
+
* useful for assessing data completeness.
|
|
1445
|
+
*
|
|
1446
|
+
* @returns Per-field coverage percentages.
|
|
1447
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1448
|
+
*
|
|
1449
|
+
* @example
|
|
1450
|
+
* ```typescript
|
|
1451
|
+
* const { data } = await client.stats.fieldCoverage();
|
|
1452
|
+
* console.log(`Weight field: ${data.weight}% coverage`);
|
|
1453
|
+
* ```
|
|
1454
|
+
*/
|
|
1455
|
+
async fieldCoverage() {
|
|
1456
|
+
return this.client.get("/v1/stats/field-coverage");
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* Get the most popular calibers by firearm count.
|
|
1460
|
+
*
|
|
1461
|
+
* @param params - Optional limit parameter.
|
|
1462
|
+
* @returns An ordered list of calibers ranked by the number of firearms using them.
|
|
1463
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1464
|
+
*
|
|
1465
|
+
* @example
|
|
1466
|
+
* ```typescript
|
|
1467
|
+
* const { data } = await client.stats.popularCalibers({ limit: 10 });
|
|
1468
|
+
* for (const entry of data) {
|
|
1469
|
+
* console.log(`${entry.name}: ${entry.count} firearms`);
|
|
1470
|
+
* }
|
|
1471
|
+
* ```
|
|
1472
|
+
*/
|
|
1473
|
+
async popularCalibers(params) {
|
|
1474
|
+
return this.client.get("/v1/stats/calibers/popular", params);
|
|
1475
|
+
}
|
|
1476
|
+
/**
|
|
1477
|
+
* Get the most prolific manufacturers by firearm count.
|
|
1478
|
+
*
|
|
1479
|
+
* @param params - Optional limit and category filter.
|
|
1480
|
+
* @returns An ordered list of manufacturers ranked by the number of firearms produced.
|
|
1481
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1482
|
+
*
|
|
1483
|
+
* @example
|
|
1484
|
+
* ```typescript
|
|
1485
|
+
* const { data } = await client.stats.prolificManufacturers({
|
|
1486
|
+
* limit: 10,
|
|
1487
|
+
* category: 'pistol',
|
|
1488
|
+
* });
|
|
1489
|
+
* ```
|
|
1490
|
+
*/
|
|
1491
|
+
async prolificManufacturers(params) {
|
|
1492
|
+
return this.client.get("/v1/stats/manufacturers/prolific", params);
|
|
1493
|
+
}
|
|
1494
|
+
/**
|
|
1495
|
+
* Get firearm counts grouped by category.
|
|
1496
|
+
*
|
|
1497
|
+
* @returns Counts per category (pistol, rifle, shotgun, etc.).
|
|
1498
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1499
|
+
*
|
|
1500
|
+
* @example
|
|
1501
|
+
* ```typescript
|
|
1502
|
+
* const { data } = await client.stats.byCategory();
|
|
1503
|
+
* // [{ category: 'pistol', count: 512 }, { category: 'rifle', count: 234 }, ...]
|
|
1504
|
+
* ```
|
|
1505
|
+
*/
|
|
1506
|
+
async byCategory() {
|
|
1507
|
+
return this.client.get("/v1/stats/by-category");
|
|
1508
|
+
}
|
|
1509
|
+
/**
|
|
1510
|
+
* Get firearm statistics for a specific decade/era.
|
|
1511
|
+
*
|
|
1512
|
+
* @param params - The decade string (e.g. `"1990s"`).
|
|
1513
|
+
* @returns Statistics for firearms introduced during the specified decade.
|
|
1514
|
+
* @throws {BadRequestError} If the decade format is invalid (must be like `"1990s"`).
|
|
1515
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1516
|
+
*
|
|
1517
|
+
* @example
|
|
1518
|
+
* ```typescript
|
|
1519
|
+
* const { data } = await client.stats.byEra({ decade: '1940s' });
|
|
1520
|
+
* ```
|
|
1521
|
+
*/
|
|
1522
|
+
async byEra(params) {
|
|
1523
|
+
return this.client.get("/v1/stats/by-era", params);
|
|
1524
|
+
}
|
|
1525
|
+
/**
|
|
1526
|
+
* Get statistics about materials used across all firearms.
|
|
1527
|
+
*
|
|
1528
|
+
* @returns Material usage counts and percentages by component type.
|
|
1529
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1530
|
+
*
|
|
1531
|
+
* @example
|
|
1532
|
+
* ```typescript
|
|
1533
|
+
* const { data } = await client.stats.materials();
|
|
1534
|
+
* ```
|
|
1535
|
+
*/
|
|
1536
|
+
async materials() {
|
|
1537
|
+
return this.client.get("/v1/stats/materials");
|
|
1538
|
+
}
|
|
1539
|
+
/**
|
|
1540
|
+
* Get firearm adoption statistics for a specific country.
|
|
1541
|
+
*
|
|
1542
|
+
* @param params - The country code (e.g. `"US"`, `"GB"`).
|
|
1543
|
+
* @returns Adoption data for the specified country.
|
|
1544
|
+
* @throws {BadRequestError} If the country code is invalid.
|
|
1545
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1546
|
+
*
|
|
1547
|
+
* @example
|
|
1548
|
+
* ```typescript
|
|
1549
|
+
* const { data } = await client.stats.adoptionByCountry({ code: 'US' });
|
|
1550
|
+
* ```
|
|
1551
|
+
*/
|
|
1552
|
+
async adoptionByCountry(params) {
|
|
1553
|
+
return this.client.get("/v1/stats/adoption/by-country", params);
|
|
1554
|
+
}
|
|
1555
|
+
/**
|
|
1556
|
+
* Get firearm adoption statistics by usage type.
|
|
1557
|
+
*
|
|
1558
|
+
* @param params - The usage type (e.g. `"military"`, `"law_enforcement"`, `"civilian"`).
|
|
1559
|
+
* @returns Adoption data for the specified usage type.
|
|
1560
|
+
* @throws {BadRequestError} If the type is not a recognized usage category.
|
|
1561
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1562
|
+
*
|
|
1563
|
+
* @example
|
|
1564
|
+
* ```typescript
|
|
1565
|
+
* const { data } = await client.stats.adoptionByType({ type: 'military' });
|
|
1566
|
+
* ```
|
|
1567
|
+
*/
|
|
1568
|
+
async adoptionByType(params) {
|
|
1569
|
+
return this.client.get("/v1/stats/adoption/by-type", params);
|
|
1570
|
+
}
|
|
1571
|
+
/**
|
|
1572
|
+
* Get firearm counts grouped by action type.
|
|
1573
|
+
*
|
|
1574
|
+
* @param params - Optional category filter.
|
|
1575
|
+
* @returns Counts per action type, optionally filtered by category.
|
|
1576
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1577
|
+
*
|
|
1578
|
+
* @example
|
|
1579
|
+
* ```typescript
|
|
1580
|
+
* const { data } = await client.stats.actionTypes({ category: 'rifle' });
|
|
1581
|
+
* ```
|
|
1582
|
+
*/
|
|
1583
|
+
async actionTypes(params) {
|
|
1584
|
+
return this.client.get("/v1/stats/action-types", params);
|
|
1585
|
+
}
|
|
1586
|
+
/**
|
|
1587
|
+
* Get feature frequency statistics across the database.
|
|
1588
|
+
*
|
|
1589
|
+
* @param params - Optional category filter and result limit.
|
|
1590
|
+
* @returns An ordered list of features ranked by frequency of occurrence.
|
|
1591
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1592
|
+
*
|
|
1593
|
+
* @example
|
|
1594
|
+
* ```typescript
|
|
1595
|
+
* const { data } = await client.stats.featureFrequency({
|
|
1596
|
+
* category: 'pistol',
|
|
1597
|
+
* limit: 20,
|
|
1598
|
+
* });
|
|
1599
|
+
* ```
|
|
1600
|
+
*/
|
|
1601
|
+
async featureFrequency(params) {
|
|
1602
|
+
return this.client.get("/v1/stats/feature-frequency", params);
|
|
1603
|
+
}
|
|
1604
|
+
/**
|
|
1605
|
+
* Get caliber popularity trends across historical eras.
|
|
1606
|
+
*
|
|
1607
|
+
* @param params - Optional from/to decade range (e.g. `"1940s"` to `"2020s"`).
|
|
1608
|
+
* @returns Caliber popularity data broken down by decade.
|
|
1609
|
+
* @throws {BadRequestError} If the decade format is invalid.
|
|
1610
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1611
|
+
*
|
|
1612
|
+
* @example
|
|
1613
|
+
* ```typescript
|
|
1614
|
+
* const { data } = await client.stats.caliberPopularityByEra({
|
|
1615
|
+
* from_decade: '1940s',
|
|
1616
|
+
* to_decade: '2020s',
|
|
1617
|
+
* });
|
|
1618
|
+
* ```
|
|
1619
|
+
*/
|
|
1620
|
+
async caliberPopularityByEra(params) {
|
|
1621
|
+
return this.client.get("/v1/stats/caliber-popularity-by-era", params);
|
|
1622
|
+
}
|
|
1623
|
+
};
|
|
1624
|
+
|
|
1625
|
+
// src/resources/game.ts
|
|
1626
|
+
var GameResource = class {
|
|
1627
|
+
constructor(client) {
|
|
1628
|
+
this.client = client;
|
|
1629
|
+
}
|
|
1630
|
+
/**
|
|
1631
|
+
* Get a balance report showing outlier firearms.
|
|
1632
|
+
*
|
|
1633
|
+
* Identifies firearms whose game stats deviate significantly from
|
|
1634
|
+
* the average, useful for game balancing.
|
|
1635
|
+
*
|
|
1636
|
+
* @param params - Optional threshold for outlier detection (0-100).
|
|
1637
|
+
* @returns A balance report with overpowered and underpowered firearms.
|
|
1638
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1639
|
+
*
|
|
1640
|
+
* @example
|
|
1641
|
+
* ```typescript
|
|
1642
|
+
* const { data } = await client.game.balanceReport({ threshold: 15 });
|
|
1643
|
+
* console.log(data.length, 'firearms flagged as outliers');
|
|
1644
|
+
* ```
|
|
1645
|
+
*/
|
|
1646
|
+
async balanceReport(params) {
|
|
1647
|
+
return this.client.get("/v1/game/balance-report", params);
|
|
1648
|
+
}
|
|
1649
|
+
/**
|
|
1650
|
+
* Get a tier list of firearms ranked by a specific stat.
|
|
1651
|
+
*
|
|
1652
|
+
* @param params - Optional category filter and stat to rank by.
|
|
1653
|
+
* @returns A tier list with firearms grouped into S/A/B/C/D/F tiers.
|
|
1654
|
+
* @throws {BadRequestError} If the stat is not a valid game stat.
|
|
1655
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1656
|
+
*
|
|
1657
|
+
* @example
|
|
1658
|
+
* ```typescript
|
|
1659
|
+
* const { data } = await client.game.tierList({
|
|
1660
|
+
* stat: 'accuracy',
|
|
1661
|
+
* category: 'rifle',
|
|
1662
|
+
* });
|
|
1663
|
+
* console.log('S-tier:', data.tiers.S.map(f => f.name));
|
|
1664
|
+
* ```
|
|
1665
|
+
*/
|
|
1666
|
+
async tierList(params) {
|
|
1667
|
+
return this.client.get("/v1/game/tier-list", params);
|
|
1668
|
+
}
|
|
1669
|
+
/**
|
|
1670
|
+
* Get a game-balanced matchup between two firearms.
|
|
1671
|
+
*
|
|
1672
|
+
* @param params - The slugs of the two firearms to compare.
|
|
1673
|
+
* @returns A detailed matchup result with per-stat comparisons and a winner.
|
|
1674
|
+
* @throws {NotFoundError} If either firearm does not exist.
|
|
1675
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1676
|
+
*
|
|
1677
|
+
* @example
|
|
1678
|
+
* ```typescript
|
|
1679
|
+
* const { data } = await client.game.matchups({ a: 'ak-47', b: 'm4-carbine' });
|
|
1680
|
+
* console.log(data.winner, data.statComparisons);
|
|
1681
|
+
* ```
|
|
1682
|
+
*/
|
|
1683
|
+
async matchups(params) {
|
|
1684
|
+
return this.client.get("/v1/game/matchups", params);
|
|
1685
|
+
}
|
|
1686
|
+
/**
|
|
1687
|
+
* Get a roster of firearms best suited for a specific game role.
|
|
1688
|
+
*
|
|
1689
|
+
* @param params - The role to query and optional count limit.
|
|
1690
|
+
* @returns A roster of firearms ranked by suitability for the given role.
|
|
1691
|
+
* @throws {BadRequestError} If the role is not a recognized game role.
|
|
1692
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1693
|
+
*
|
|
1694
|
+
* @example
|
|
1695
|
+
* ```typescript
|
|
1696
|
+
* const { data } = await client.game.roleRoster({
|
|
1697
|
+
* role: 'sniper',
|
|
1698
|
+
* count: 10,
|
|
1699
|
+
* });
|
|
1700
|
+
* for (const firearm of data) {
|
|
1701
|
+
* console.log(firearm.name, firearm.roleScore);
|
|
1702
|
+
* }
|
|
1703
|
+
* ```
|
|
1704
|
+
*/
|
|
1705
|
+
async roleRoster(params) {
|
|
1706
|
+
return this.client.get("/v1/game/role-roster", params);
|
|
1707
|
+
}
|
|
1708
|
+
/**
|
|
1709
|
+
* Get the statistical distribution for a specific game stat.
|
|
1710
|
+
*
|
|
1711
|
+
* Shows how firearms are distributed across value ranges for a given
|
|
1712
|
+
* stat, useful for understanding the spread and identifying balance issues.
|
|
1713
|
+
*
|
|
1714
|
+
* @param params - The stat to analyze (e.g. `"damage"`, `"accuracy"`).
|
|
1715
|
+
* @returns Distribution data including histogram buckets and summary statistics.
|
|
1716
|
+
* @throws {BadRequestError} If the stat is not a valid game stat.
|
|
1717
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1718
|
+
*
|
|
1719
|
+
* @example
|
|
1720
|
+
* ```typescript
|
|
1721
|
+
* const { data } = await client.game.statDistribution({ stat: 'damage' });
|
|
1722
|
+
* console.log(data.mean, data.median, data.buckets);
|
|
1723
|
+
* ```
|
|
1724
|
+
*/
|
|
1725
|
+
async statDistribution(params) {
|
|
1726
|
+
return this.client.get("/v1/game/stat-distribution", params);
|
|
1727
|
+
}
|
|
1728
|
+
};
|
|
1729
|
+
|
|
1730
|
+
// src/resources/game-stats.ts
|
|
1731
|
+
var GameStatsResource = class {
|
|
1732
|
+
constructor(client) {
|
|
1733
|
+
this.client = client;
|
|
1734
|
+
}
|
|
1735
|
+
/**
|
|
1736
|
+
* List all available game stats snapshot versions.
|
|
1737
|
+
*
|
|
1738
|
+
* @returns An array of version records with metadata about each snapshot.
|
|
1739
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1740
|
+
*
|
|
1741
|
+
* @example
|
|
1742
|
+
* ```typescript
|
|
1743
|
+
* const { data } = await client.gameStats.listVersions();
|
|
1744
|
+
* for (const version of data) {
|
|
1745
|
+
* console.log(version.version, version.createdAt, version.description);
|
|
1746
|
+
* }
|
|
1747
|
+
* ```
|
|
1748
|
+
*/
|
|
1749
|
+
async listVersions() {
|
|
1750
|
+
return this.client.get("/v1/game-stats/versions");
|
|
1751
|
+
}
|
|
1752
|
+
/**
|
|
1753
|
+
* List all firearms in a specific game stats snapshot version.
|
|
1754
|
+
*
|
|
1755
|
+
* @param version - The snapshot version string (e.g. `"1.0.0"`).
|
|
1756
|
+
* @param params - Optional pagination parameters.
|
|
1757
|
+
* @returns A paginated list of firearms with their game stats for the given version.
|
|
1758
|
+
* @throws {NotFoundError} If the specified version does not exist.
|
|
1759
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1760
|
+
*
|
|
1761
|
+
* @example
|
|
1762
|
+
* ```typescript
|
|
1763
|
+
* const result = await client.gameStats.listFirearms('1.0.0', { per_page: 50 });
|
|
1764
|
+
* for (const firearm of result.data) {
|
|
1765
|
+
* console.log(firearm.name, firearm.damage);
|
|
1766
|
+
* }
|
|
1767
|
+
* ```
|
|
1768
|
+
*/
|
|
1769
|
+
async listFirearms(version, params) {
|
|
1770
|
+
return this.client.getPaginated(
|
|
1771
|
+
`/v1/game-stats/versions/${encodeURIComponent(version)}/firearms`,
|
|
1772
|
+
params
|
|
1773
|
+
);
|
|
1774
|
+
}
|
|
1775
|
+
/**
|
|
1776
|
+
* Get a single firearm's game stats from a specific snapshot version.
|
|
1777
|
+
*
|
|
1778
|
+
* @param version - The snapshot version string (e.g. `"1.0.0"`).
|
|
1779
|
+
* @param id - The firearm slug or ID.
|
|
1780
|
+
* @returns The firearm's game stats as captured in the specified version.
|
|
1781
|
+
* @throws {NotFoundError} If the version or firearm does not exist in the snapshot.
|
|
1782
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1783
|
+
*
|
|
1784
|
+
* @example
|
|
1785
|
+
* ```typescript
|
|
1786
|
+
* const { data } = await client.gameStats.getFirearm('1.0.0', 'glock-g17');
|
|
1787
|
+
* console.log(data.damage, data.accuracy, data.range);
|
|
1788
|
+
* ```
|
|
1789
|
+
*/
|
|
1790
|
+
async getFirearm(version, id) {
|
|
1791
|
+
return this.client.get(
|
|
1792
|
+
`/v1/game-stats/versions/${encodeURIComponent(version)}/firearms/${encodeURIComponent(id)}`
|
|
1793
|
+
);
|
|
1794
|
+
}
|
|
1795
|
+
};
|
|
1796
|
+
|
|
1797
|
+
// src/resources/ammunition.ts
|
|
1798
|
+
var AmmunitionResource = class {
|
|
1799
|
+
constructor(client) {
|
|
1800
|
+
this.client = client;
|
|
1801
|
+
}
|
|
1802
|
+
/**
|
|
1803
|
+
* List ammunition with optional filters and pagination.
|
|
1804
|
+
*
|
|
1805
|
+
* @param params - Optional query parameters for filtering by caliber, bullet type, etc.
|
|
1806
|
+
* @returns A paginated list of ammunition loads.
|
|
1807
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1808
|
+
*
|
|
1809
|
+
* @example
|
|
1810
|
+
* ```typescript
|
|
1811
|
+
* const result = await client.ammunition.list({
|
|
1812
|
+
* caliber_id: '9x19mm-parabellum',
|
|
1813
|
+
* bullet_type: 'hollow-point',
|
|
1814
|
+
* per_page: 25,
|
|
1815
|
+
* });
|
|
1816
|
+
* ```
|
|
1817
|
+
*/
|
|
1818
|
+
async list(params) {
|
|
1819
|
+
return this.client.getPaginated("/v1/ammunition", params);
|
|
1820
|
+
}
|
|
1821
|
+
/**
|
|
1822
|
+
* Auto-paginate through all ammunition matching the given filters.
|
|
1823
|
+
*
|
|
1824
|
+
* Returns an async iterator that fetches pages on demand, yielding
|
|
1825
|
+
* individual {@link Ammunition} objects.
|
|
1826
|
+
*
|
|
1827
|
+
* @param params - Optional query parameters for filtering and sorting.
|
|
1828
|
+
* @returns An async iterable iterator yielding individual ammunition loads.
|
|
1829
|
+
*
|
|
1830
|
+
* @example
|
|
1831
|
+
* ```typescript
|
|
1832
|
+
* for await (const ammo of client.ammunition.listAutoPaging({ caliber_id: '45-acp' })) {
|
|
1833
|
+
* console.log(ammo.name, ammo.bulletWeightGrains);
|
|
1834
|
+
* }
|
|
1835
|
+
* ```
|
|
1836
|
+
*/
|
|
1837
|
+
async *listAutoPaging(params) {
|
|
1838
|
+
let page = params?.page ?? 1;
|
|
1839
|
+
while (true) {
|
|
1840
|
+
const result = await this.list({ ...params, page });
|
|
1841
|
+
for (const item of result.data) {
|
|
1842
|
+
yield item;
|
|
1843
|
+
}
|
|
1844
|
+
if (!result.pagination.totalPages || page >= result.pagination.totalPages) break;
|
|
1845
|
+
page++;
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
/**
|
|
1849
|
+
* Get a single ammunition load by its slug or ID.
|
|
1850
|
+
*
|
|
1851
|
+
* @param id - The ammunition slug (e.g. `"9mm-federal-hst-124gr"`) or numeric ID.
|
|
1852
|
+
* @returns The full ammunition record with specifications.
|
|
1853
|
+
* @throws {NotFoundError} If no ammunition matches the given identifier.
|
|
1854
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1855
|
+
*
|
|
1856
|
+
* @example
|
|
1857
|
+
* ```typescript
|
|
1858
|
+
* const { data } = await client.ammunition.get('9mm-federal-hst-124gr');
|
|
1859
|
+
* console.log(data.name, data.muzzleVelocityFps, data.bulletWeightGrains);
|
|
1860
|
+
* ```
|
|
1861
|
+
*/
|
|
1862
|
+
async get(id) {
|
|
1863
|
+
return this.client.get(`/v1/ammunition/${encodeURIComponent(id)}`);
|
|
1864
|
+
}
|
|
1865
|
+
/**
|
|
1866
|
+
* Get a bullet profile SVG image for an ammunition load.
|
|
1867
|
+
*
|
|
1868
|
+
* Returns a raw SVG string representing the bullet projectile shape.
|
|
1869
|
+
* This endpoint returns raw SVG content, not the standard JSON envelope.
|
|
1870
|
+
*
|
|
1871
|
+
* @param id - The ammunition slug or ID.
|
|
1872
|
+
* @returns The raw SVG string of the bullet profile.
|
|
1873
|
+
* @throws {NotFoundError} If the ammunition does not exist.
|
|
1874
|
+
*
|
|
1875
|
+
* @example
|
|
1876
|
+
* ```typescript
|
|
1877
|
+
* const svg = await client.ammunition.getBulletSvg('9mm-federal-hst-124gr');
|
|
1878
|
+
* // Insert into DOM: element.innerHTML = svg;
|
|
1879
|
+
* ```
|
|
1880
|
+
*/
|
|
1881
|
+
async getBulletSvg(id) {
|
|
1882
|
+
const response = await this.client.get(
|
|
1883
|
+
`/v1/ammunition/${encodeURIComponent(id)}/bullet.svg`
|
|
1884
|
+
);
|
|
1885
|
+
return response.data;
|
|
1886
|
+
}
|
|
1887
|
+
/**
|
|
1888
|
+
* Get ballistic trajectory data for an ammunition load.
|
|
1889
|
+
*
|
|
1890
|
+
* @param id - The ammunition slug or ID.
|
|
1891
|
+
* @param params - Optional barrel length and distance parameters.
|
|
1892
|
+
* @returns Ballistic trajectory data at specified distances.
|
|
1893
|
+
* @throws {NotFoundError} If the ammunition does not exist.
|
|
1894
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1895
|
+
*
|
|
1896
|
+
* @example
|
|
1897
|
+
* ```typescript
|
|
1898
|
+
* const { data } = await client.ammunition.ballistics('9mm-federal-hst-124gr', {
|
|
1899
|
+
* barrel_length_mm: 102,
|
|
1900
|
+
* distances: '0,25,50,100,200',
|
|
1901
|
+
* });
|
|
1902
|
+
* for (const point of data.trajectory) {
|
|
1903
|
+
* console.log(`${point.distanceM}m: ${point.velocityFps} fps, ${point.energyFtLbs} ft-lbs`);
|
|
1904
|
+
* }
|
|
1905
|
+
* ```
|
|
1906
|
+
*/
|
|
1907
|
+
async ballistics(id, params) {
|
|
1908
|
+
return this.client.get(
|
|
1909
|
+
`/v1/ammunition/${encodeURIComponent(id)}/ballistics`,
|
|
1910
|
+
params
|
|
1911
|
+
);
|
|
1912
|
+
}
|
|
1913
|
+
};
|
|
1914
|
+
|
|
1915
|
+
// src/resources/countries.ts
|
|
1916
|
+
var CountriesResource = class {
|
|
1917
|
+
constructor(client) {
|
|
1918
|
+
this.client = client;
|
|
1919
|
+
}
|
|
1920
|
+
/**
|
|
1921
|
+
* List all countries that have adopted at least one firearm.
|
|
1922
|
+
*
|
|
1923
|
+
* @returns An array of country records with codes and names.
|
|
1924
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1925
|
+
*
|
|
1926
|
+
* @example
|
|
1927
|
+
* ```typescript
|
|
1928
|
+
* const { data } = await client.countries.list();
|
|
1929
|
+
* for (const country of data) {
|
|
1930
|
+
* console.log(country.code, country.name);
|
|
1931
|
+
* }
|
|
1932
|
+
* ```
|
|
1933
|
+
*/
|
|
1934
|
+
async list() {
|
|
1935
|
+
return this.client.get("/v1/countries");
|
|
1936
|
+
}
|
|
1937
|
+
/**
|
|
1938
|
+
* Get the full firearm arsenal for a specific country.
|
|
1939
|
+
*
|
|
1940
|
+
* Returns all firearms adopted by the country, grouped by usage type
|
|
1941
|
+
* (military, law enforcement, etc.).
|
|
1942
|
+
*
|
|
1943
|
+
* @param code - The country code (e.g. `"US"`, `"GB"`, `"DE"`).
|
|
1944
|
+
* @returns The country's arsenal data with firearms grouped by adoption type.
|
|
1945
|
+
* @throws {NotFoundError} If the country code is not found in the database.
|
|
1946
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1947
|
+
*
|
|
1948
|
+
* @example
|
|
1949
|
+
* ```typescript
|
|
1950
|
+
* const { data } = await client.countries.getArsenal('US');
|
|
1951
|
+
* console.log(`${data.country.name} has ${data.totalFirearms} firearms`);
|
|
1952
|
+
* for (const group of data.groups) {
|
|
1953
|
+
* console.log(`${group.type}: ${group.firearms.length} firearms`);
|
|
1954
|
+
* }
|
|
1955
|
+
* ```
|
|
1956
|
+
*/
|
|
1957
|
+
async getArsenal(code) {
|
|
1958
|
+
return this.client.get(`/v1/countries/${encodeURIComponent(code)}/arsenal`);
|
|
1959
|
+
}
|
|
1960
|
+
};
|
|
1961
|
+
|
|
1962
|
+
// src/resources/conflicts.ts
|
|
1963
|
+
var ConflictsResource = class {
|
|
1964
|
+
constructor(client) {
|
|
1965
|
+
this.client = client;
|
|
1966
|
+
}
|
|
1967
|
+
/**
|
|
1968
|
+
* List all military conflicts in the database.
|
|
1969
|
+
*
|
|
1970
|
+
* Returns conflicts with metadata including name, date range, and
|
|
1971
|
+
* participating nations. Use the conflict identifiers with
|
|
1972
|
+
* `client.firearms.byConflict()` to find firearms used in a specific conflict.
|
|
1973
|
+
*
|
|
1974
|
+
* @returns An array of all conflict records.
|
|
1975
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
1976
|
+
*
|
|
1977
|
+
* @example
|
|
1978
|
+
* ```typescript
|
|
1979
|
+
* const { data } = await client.conflicts.list();
|
|
1980
|
+
* for (const conflict of data) {
|
|
1981
|
+
* console.log(`${conflict.name} (${conflict.startYear}-${conflict.endYear})`);
|
|
1982
|
+
* }
|
|
1983
|
+
*
|
|
1984
|
+
* // Then find firearms used in a conflict:
|
|
1985
|
+
* const wwii = await client.firearms.byConflict({ conflict: 'world-war-ii' });
|
|
1986
|
+
* ```
|
|
1987
|
+
*/
|
|
1988
|
+
async list() {
|
|
1989
|
+
return this.client.get("/v1/conflicts");
|
|
1990
|
+
}
|
|
1991
|
+
};
|
|
1992
|
+
|
|
1993
|
+
// src/resources/data-quality.ts
|
|
1994
|
+
var DataQualityResource = class {
|
|
1995
|
+
constructor(client) {
|
|
1996
|
+
this.client = client;
|
|
1997
|
+
}
|
|
1998
|
+
/**
|
|
1999
|
+
* Get overall data coverage statistics for the database.
|
|
2000
|
+
*
|
|
2001
|
+
* Returns per-field and aggregate coverage metrics showing what
|
|
2002
|
+
* percentage of records have data for each field.
|
|
2003
|
+
*
|
|
2004
|
+
* @returns Data coverage statistics including per-field percentages.
|
|
2005
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
2006
|
+
*
|
|
2007
|
+
* @example
|
|
2008
|
+
* ```typescript
|
|
2009
|
+
* const { data } = await client.dataQuality.coverage();
|
|
2010
|
+
* console.log(`Overall: ${data.overallCoverage}%`);
|
|
2011
|
+
* for (const field of data.fields) {
|
|
2012
|
+
* console.log(`${field.name}: ${field.coverage}%`);
|
|
2013
|
+
* }
|
|
2014
|
+
* ```
|
|
2015
|
+
*/
|
|
2016
|
+
async coverage() {
|
|
2017
|
+
return this.client.get("/v1/data/coverage");
|
|
2018
|
+
}
|
|
2019
|
+
/**
|
|
2020
|
+
* Get firearms with confidence scores below a specified threshold.
|
|
2021
|
+
*
|
|
2022
|
+
* Useful for identifying records that may need additional data
|
|
2023
|
+
* verification or enrichment.
|
|
2024
|
+
*
|
|
2025
|
+
* @param params - Optional threshold and pagination parameters.
|
|
2026
|
+
* @returns A paginated list of firearms with their confidence scores.
|
|
2027
|
+
* @throws {BadRequestError} If the threshold is outside the 0-1 range.
|
|
2028
|
+
* @throws {AuthenticationError} If the API key is missing or invalid.
|
|
2029
|
+
*
|
|
2030
|
+
* @example
|
|
2031
|
+
* ```typescript
|
|
2032
|
+
* const result = await client.dataQuality.confidence({
|
|
2033
|
+
* below: 0.5,
|
|
2034
|
+
* per_page: 25,
|
|
2035
|
+
* });
|
|
2036
|
+
* for (const item of result.data) {
|
|
2037
|
+
* console.log(`${item.name}: confidence ${item.confidenceScore}`);
|
|
2038
|
+
* }
|
|
2039
|
+
* ```
|
|
2040
|
+
*/
|
|
2041
|
+
async confidence(params) {
|
|
2042
|
+
return this.client.getPaginated("/v1/data/confidence", params);
|
|
2043
|
+
}
|
|
2044
|
+
};
|
|
2045
|
+
|
|
2046
|
+
// src/resources/favorites.ts
|
|
2047
|
+
var FavoritesResource = class {
|
|
2048
|
+
constructor(client) {
|
|
2049
|
+
this.client = client;
|
|
2050
|
+
}
|
|
2051
|
+
async list(params) {
|
|
2052
|
+
return this.client.getPaginated("/v1/me/favorites", params);
|
|
2053
|
+
}
|
|
2054
|
+
async add(firearmId) {
|
|
2055
|
+
return this.client.post(`/v1/me/favorites/${encodeURIComponent(firearmId)}`);
|
|
2056
|
+
}
|
|
2057
|
+
async remove(firearmId) {
|
|
2058
|
+
return this.client.delete(`/v1/me/favorites/${encodeURIComponent(firearmId)}`);
|
|
2059
|
+
}
|
|
2060
|
+
};
|
|
2061
|
+
|
|
2062
|
+
// src/resources/reports.ts
|
|
2063
|
+
var ReportsResource = class {
|
|
2064
|
+
constructor(client) {
|
|
2065
|
+
this.client = client;
|
|
2066
|
+
}
|
|
2067
|
+
async create(params) {
|
|
2068
|
+
return this.client.post("/v1/me/reports", params);
|
|
2069
|
+
}
|
|
2070
|
+
async list(params) {
|
|
2071
|
+
return this.client.getPaginated("/v1/me/reports", params);
|
|
2072
|
+
}
|
|
2073
|
+
};
|
|
2074
|
+
|
|
2075
|
+
// src/resources/support.ts
|
|
2076
|
+
var SupportResource = class {
|
|
2077
|
+
constructor(client) {
|
|
2078
|
+
this.client = client;
|
|
2079
|
+
}
|
|
2080
|
+
async create(params) {
|
|
2081
|
+
return this.client.post("/v1/me/support", params);
|
|
2082
|
+
}
|
|
2083
|
+
async list(params) {
|
|
2084
|
+
return this.client.getPaginated("/v1/me/support", params);
|
|
2085
|
+
}
|
|
2086
|
+
async get(ticketId) {
|
|
2087
|
+
return this.client.get(`/v1/me/support/${encodeURIComponent(ticketId)}`);
|
|
2088
|
+
}
|
|
2089
|
+
async reply(ticketId, params) {
|
|
2090
|
+
return this.client.post(`/v1/me/support/${encodeURIComponent(ticketId)}/replies`, params);
|
|
2091
|
+
}
|
|
2092
|
+
};
|
|
2093
|
+
|
|
2094
|
+
// src/resources/webhooks.ts
|
|
2095
|
+
var WebhooksResource = class {
|
|
2096
|
+
constructor(client) {
|
|
2097
|
+
this.client = client;
|
|
2098
|
+
}
|
|
2099
|
+
async list(params) {
|
|
2100
|
+
return this.client.getPaginated("/v1/me/webhooks", params);
|
|
2101
|
+
}
|
|
2102
|
+
async create(params) {
|
|
2103
|
+
return this.client.post("/v1/me/webhooks", params);
|
|
2104
|
+
}
|
|
2105
|
+
async get(id) {
|
|
2106
|
+
return this.client.get(`/v1/me/webhooks/${encodeURIComponent(id)}`);
|
|
2107
|
+
}
|
|
2108
|
+
async update(id, params) {
|
|
2109
|
+
return this.client.put(`/v1/me/webhooks/${encodeURIComponent(id)}`, params);
|
|
2110
|
+
}
|
|
2111
|
+
async delete(id) {
|
|
2112
|
+
return this.client.delete(`/v1/me/webhooks/${encodeURIComponent(id)}`);
|
|
2113
|
+
}
|
|
2114
|
+
async test(id) {
|
|
2115
|
+
return this.client.post(`/v1/me/webhooks/${encodeURIComponent(id)}/test`);
|
|
2116
|
+
}
|
|
2117
|
+
};
|
|
2118
|
+
|
|
2119
|
+
// src/resources/usage.ts
|
|
2120
|
+
var UsageResource = class {
|
|
2121
|
+
constructor(client) {
|
|
2122
|
+
this.client = client;
|
|
2123
|
+
}
|
|
2124
|
+
async get(params) {
|
|
2125
|
+
return this.client.get("/v1/me/usage", params);
|
|
2126
|
+
}
|
|
2127
|
+
};
|
|
2128
|
+
|
|
2129
|
+
// src/version.ts
|
|
2130
|
+
var VERSION = "0.1.0";
|
|
2131
|
+
|
|
2132
|
+
// src/client.ts
|
|
2133
|
+
var GunSpec = class {
|
|
2134
|
+
/** Firearms specifications, search, comparison, and related data */
|
|
2135
|
+
firearms;
|
|
2136
|
+
/** Firearm manufacturers and their products */
|
|
2137
|
+
manufacturers;
|
|
2138
|
+
/** Ammunition calibers and cartridge specifications */
|
|
2139
|
+
calibers;
|
|
2140
|
+
/** Firearm categories (pistol, rifle, shotgun, etc.) */
|
|
2141
|
+
categories;
|
|
2142
|
+
/** Aggregate statistics across the database */
|
|
2143
|
+
stats;
|
|
2144
|
+
/** Game development tools — balance reports, tier lists, matchups */
|
|
2145
|
+
game;
|
|
2146
|
+
/** Versioned game stats snapshots for pinning game builds */
|
|
2147
|
+
gameStats;
|
|
2148
|
+
/** Ammunition loads, ballistics, and bullet data */
|
|
2149
|
+
ammunition;
|
|
2150
|
+
/** Countries and their military/law enforcement arsenals */
|
|
2151
|
+
countries;
|
|
2152
|
+
/** Armed conflicts and the firearms used in them */
|
|
2153
|
+
conflicts;
|
|
2154
|
+
/** Data quality metrics (Enterprise tier) */
|
|
2155
|
+
dataQuality;
|
|
2156
|
+
/** User's favorited firearms */
|
|
2157
|
+
favorites;
|
|
2158
|
+
/** Data quality reports */
|
|
2159
|
+
reports;
|
|
2160
|
+
/** Support tickets (paid tiers) */
|
|
2161
|
+
support;
|
|
2162
|
+
/** Webhook endpoint management (studio+ tier) */
|
|
2163
|
+
webhooks;
|
|
2164
|
+
/** API usage statistics */
|
|
2165
|
+
usage;
|
|
2166
|
+
/** The underlying HTTP client (for advanced use) */
|
|
2167
|
+
_client;
|
|
2168
|
+
constructor(options = {}) {
|
|
2169
|
+
this._client = new HttpClient({
|
|
2170
|
+
baseUrl: options.baseURL ?? "https://api.gunspec.io",
|
|
2171
|
+
timeout: options.timeout ?? 3e4,
|
|
2172
|
+
auth: {
|
|
2173
|
+
apiKey: options.apiKey
|
|
2174
|
+
},
|
|
2175
|
+
retry: options.retry,
|
|
2176
|
+
headers: {
|
|
2177
|
+
...options.defaultHeaders,
|
|
2178
|
+
"User-Agent": `gunspec-sdk/typescript/${VERSION}`,
|
|
2179
|
+
"X-SDK-Version": VERSION,
|
|
2180
|
+
"X-SDK-Language": "typescript"
|
|
2181
|
+
}
|
|
2182
|
+
});
|
|
2183
|
+
this.firearms = new FirearmsResource(this._client);
|
|
2184
|
+
this.manufacturers = new ManufacturersResource(this._client);
|
|
2185
|
+
this.calibers = new CalibersResource(this._client);
|
|
2186
|
+
this.categories = new CategoriesResource(this._client);
|
|
2187
|
+
this.stats = new StatsResource(this._client);
|
|
2188
|
+
this.game = new GameResource(this._client);
|
|
2189
|
+
this.gameStats = new GameStatsResource(this._client);
|
|
2190
|
+
this.ammunition = new AmmunitionResource(this._client);
|
|
2191
|
+
this.countries = new CountriesResource(this._client);
|
|
2192
|
+
this.conflicts = new ConflictsResource(this._client);
|
|
2193
|
+
this.dataQuality = new DataQualityResource(this._client);
|
|
2194
|
+
this.favorites = new FavoritesResource(this._client);
|
|
2195
|
+
this.reports = new ReportsResource(this._client);
|
|
2196
|
+
this.support = new SupportResource(this._client);
|
|
2197
|
+
this.webhooks = new WebhooksResource(this._client);
|
|
2198
|
+
this.usage = new UsageResource(this._client);
|
|
2199
|
+
}
|
|
2200
|
+
};
|
|
2201
|
+
|
|
2202
|
+
// src/core/pagination.ts
|
|
2203
|
+
var Page = class _Page {
|
|
2204
|
+
/** The items on this page. */
|
|
2205
|
+
data;
|
|
2206
|
+
/** Pagination metadata returned by the server. */
|
|
2207
|
+
pagination;
|
|
2208
|
+
/** The request ID for this page's HTTP response. */
|
|
2209
|
+
requestId;
|
|
2210
|
+
/** The base query parameters used to fetch this page (without `page`). */
|
|
2211
|
+
baseQuery;
|
|
2212
|
+
/** The fetcher function used to retrieve subsequent pages. */
|
|
2213
|
+
fetcher;
|
|
2214
|
+
/**
|
|
2215
|
+
* @internal
|
|
2216
|
+
* Consumers should not construct `Page` instances directly. Use the
|
|
2217
|
+
* resource methods on the high-level client instead.
|
|
2218
|
+
*/
|
|
2219
|
+
constructor(response, baseQuery, fetcher) {
|
|
2220
|
+
this.data = response.data;
|
|
2221
|
+
this.pagination = response.pagination;
|
|
2222
|
+
this.requestId = response.requestId;
|
|
2223
|
+
this.baseQuery = baseQuery;
|
|
2224
|
+
this.fetcher = fetcher;
|
|
2225
|
+
}
|
|
2226
|
+
/**
|
|
2227
|
+
* Whether there is a next page of results.
|
|
2228
|
+
*
|
|
2229
|
+
* When `totalPages` is available (pro/enterprise tiers) the check is exact.
|
|
2230
|
+
* Otherwise the heuristic is: if the current page returned a full page of
|
|
2231
|
+
* results (i.e. `data.length === limit`), there is likely another page.
|
|
2232
|
+
*/
|
|
2233
|
+
hasNextPage() {
|
|
2234
|
+
if (this.pagination.totalPages !== void 0) {
|
|
2235
|
+
return this.pagination.page < this.pagination.totalPages;
|
|
2236
|
+
}
|
|
2237
|
+
return this.data.length >= this.pagination.limit;
|
|
2238
|
+
}
|
|
2239
|
+
/**
|
|
2240
|
+
* Fetch the next page of results.
|
|
2241
|
+
*
|
|
2242
|
+
* @returns A new {@link Page} instance for the next page.
|
|
2243
|
+
* @throws {Error} If there is no next page. Always check
|
|
2244
|
+
* {@link hasNextPage} first.
|
|
2245
|
+
*/
|
|
2246
|
+
async getNextPage() {
|
|
2247
|
+
if (!this.hasNextPage()) {
|
|
2248
|
+
throw new Error("No more pages available. Check hasNextPage() before calling getNextPage().");
|
|
2249
|
+
}
|
|
2250
|
+
const nextQuery = {
|
|
2251
|
+
...this.baseQuery,
|
|
2252
|
+
page: this.pagination.page + 1
|
|
2253
|
+
};
|
|
2254
|
+
const response = await this.fetcher(nextQuery);
|
|
2255
|
+
return new _Page(response, this.baseQuery, this.fetcher);
|
|
2256
|
+
}
|
|
2257
|
+
/**
|
|
2258
|
+
* Fetch the previous page of results.
|
|
2259
|
+
*
|
|
2260
|
+
* @returns A new {@link Page} instance for the previous page.
|
|
2261
|
+
* @throws {Error} If this is already the first page.
|
|
2262
|
+
*/
|
|
2263
|
+
async getPreviousPage() {
|
|
2264
|
+
if (this.pagination.page <= 1) {
|
|
2265
|
+
throw new Error("Already on the first page. Cannot navigate to a previous page.");
|
|
2266
|
+
}
|
|
2267
|
+
const prevQuery = {
|
|
2268
|
+
...this.baseQuery,
|
|
2269
|
+
page: this.pagination.page - 1
|
|
2270
|
+
};
|
|
2271
|
+
const response = await this.fetcher(prevQuery);
|
|
2272
|
+
return new _Page(response, this.baseQuery, this.fetcher);
|
|
2273
|
+
}
|
|
2274
|
+
/**
|
|
2275
|
+
* Async iterator that yields every item across **all** pages, starting
|
|
2276
|
+
* from the current page.
|
|
2277
|
+
*
|
|
2278
|
+
* Fetches subsequent pages on demand (lazily) so memory usage stays
|
|
2279
|
+
* constant regardless of the total result set size.
|
|
2280
|
+
*
|
|
2281
|
+
* @example
|
|
2282
|
+
* ```ts
|
|
2283
|
+
* const firstPage = await client.firearms.list({ limit: 50 });
|
|
2284
|
+
* for await (const firearm of firstPage) {
|
|
2285
|
+
* // Iterates page 1, then auto-fetches page 2, 3, ... until exhausted.
|
|
2286
|
+
* console.log(firearm.name);
|
|
2287
|
+
* }
|
|
2288
|
+
* ```
|
|
2289
|
+
*/
|
|
2290
|
+
async *[Symbol.asyncIterator]() {
|
|
2291
|
+
let current = this;
|
|
2292
|
+
while (true) {
|
|
2293
|
+
for (const item of current.data) {
|
|
2294
|
+
yield item;
|
|
2295
|
+
}
|
|
2296
|
+
if (!current.hasNextPage()) {
|
|
2297
|
+
break;
|
|
2298
|
+
}
|
|
2299
|
+
current = await current.getNextPage();
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
};
|
|
2303
|
+
function createPage(response, baseQuery, fetcher) {
|
|
2304
|
+
return new Page(response, baseQuery, fetcher);
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
exports.APIError = APIError;
|
|
2308
|
+
exports.AmmunitionResource = AmmunitionResource;
|
|
2309
|
+
exports.AuthenticationError = AuthenticationError;
|
|
2310
|
+
exports.BadRequestError = BadRequestError;
|
|
2311
|
+
exports.CalibersResource = CalibersResource;
|
|
2312
|
+
exports.CategoriesResource = CategoriesResource;
|
|
2313
|
+
exports.ConflictsResource = ConflictsResource;
|
|
2314
|
+
exports.ConnectionError = ConnectionError;
|
|
2315
|
+
exports.CountriesResource = CountriesResource;
|
|
2316
|
+
exports.DataQualityResource = DataQualityResource;
|
|
2317
|
+
exports.FavoritesResource = FavoritesResource;
|
|
2318
|
+
exports.FirearmsResource = FirearmsResource;
|
|
2319
|
+
exports.GameResource = GameResource;
|
|
2320
|
+
exports.GameStatsResource = GameStatsResource;
|
|
2321
|
+
exports.GunSpec = GunSpec;
|
|
2322
|
+
exports.GunSpecError = GunSpecError;
|
|
2323
|
+
exports.HttpClient = HttpClient;
|
|
2324
|
+
exports.InternalServerError = InternalServerError;
|
|
2325
|
+
exports.ManufacturersResource = ManufacturersResource;
|
|
2326
|
+
exports.NotFoundError = NotFoundError;
|
|
2327
|
+
exports.Page = Page;
|
|
2328
|
+
exports.PermissionError = PermissionError;
|
|
2329
|
+
exports.RateLimitError = RateLimitError;
|
|
2330
|
+
exports.ReportsResource = ReportsResource;
|
|
2331
|
+
exports.StatsResource = StatsResource;
|
|
2332
|
+
exports.SupportResource = SupportResource;
|
|
2333
|
+
exports.TimeoutError = TimeoutError;
|
|
2334
|
+
exports.UsageResource = UsageResource;
|
|
2335
|
+
exports.VERSION = VERSION;
|
|
2336
|
+
exports.WebhooksResource = WebhooksResource;
|
|
2337
|
+
exports.buildAuthHeaders = buildAuthHeaders;
|
|
2338
|
+
exports.createAPIError = createAPIError;
|
|
2339
|
+
exports.createPage = createPage;
|
|
2340
|
+
exports.resolveApiKey = resolveApiKey;
|
|
2341
|
+
//# sourceMappingURL=index.cjs.map
|
|
2342
|
+
//# sourceMappingURL=index.cjs.map
|