@quizbase/client 0.2.3 → 0.4.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 +23 -3
- package/dist/index.cjs +78 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +140 -45
- package/dist/index.d.ts +140 -45
- package/dist/index.js +78 -34
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,14 +24,16 @@ Requires Node ≥20 (or any modern browser with `fetch` + `AbortController`).
|
|
|
24
24
|
import { createClient } from '@quizbase/client';
|
|
25
25
|
|
|
26
26
|
const client = createClient({
|
|
27
|
-
apiKey: process.env.QUIZBASE_API_KEY! //
|
|
27
|
+
apiKey: process.env.QUIZBASE_API_KEY! // qb_pk_… (publishable) or qb_sk_… (secret)
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
const random = await client.questions.random({ category: 'science', lang: 'en' });
|
|
30
|
+
const random = await client.questions.random({ category: 'science-and-nature', lang: 'en' });
|
|
31
31
|
console.log(random.data.question);
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
Get a key at [quizbase.runriva.com](https://quizbase.runriva.com).
|
|
34
|
+
Get a key at [quizbase.runriva.com/dashboard/keys](https://quizbase.runriva.com/dashboard/keys). The free tier is **500 requests/day across all your keys** against full production data — enough to ship a real app. Paid tiers at [/pricing](https://quizbase.runriva.com/pricing).
|
|
35
|
+
|
|
36
|
+
Use `qb_pk_*` (publishable) from browsers and edge functions — CORS is enabled. Use `qb_sk_*` (secret) only from servers and CI; CORS is blocked for these. Both formats authenticate identically and share the same per-user quota.
|
|
35
37
|
|
|
36
38
|
## Resources
|
|
37
39
|
|
|
@@ -56,6 +58,24 @@ client.report.create({ questionId, kind, comment });
|
|
|
56
58
|
|
|
57
59
|
Full parameter docs: [docs.quizbase.runriva.com/docs](https://quizbase.runriva.com/docs) and the interactive [API Reference](https://quizbase.runriva.com/docs/api-reference).
|
|
58
60
|
|
|
61
|
+
## Pagination
|
|
62
|
+
|
|
63
|
+
`questions`, `topics`, `tags`, and `subcategories` are cursor-paginated. Use `listAll()` to iterate every item, or `pages()` to iterate page-by-page — both auto-follow `_links.next` and preserve filters across pages.
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
// Iterate every matching question. Filters and limit are reused on each page.
|
|
67
|
+
for await (const q of client.questions.listAll({ lang: 'pl', tags_any: ['einstein'] })) {
|
|
68
|
+
await db.upsert(q);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Iterate pages — useful when you need batch boundaries (checkpoints, logging).
|
|
72
|
+
for await (const page of client.topics.pages({ lang: 'en' })) {
|
|
73
|
+
console.log(page.meta.requestId, page.data.length);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Need manual control? `list()` still exposes `_links.next` and a `cursor` query param.
|
|
78
|
+
|
|
59
79
|
## Errors
|
|
60
80
|
|
|
61
81
|
Every non-2xx response throws `QuizbaseError` carrying the parsed [RFC 9457 Problem Details](https://www.rfc-editor.org/rfc/rfc9457):
|
package/dist/index.cjs
CHANGED
|
@@ -199,26 +199,31 @@ function createClient(options) {
|
|
|
199
199
|
throw lastError ?? new Error("quizbase-client: retry loop exhausted unexpectedly");
|
|
200
200
|
}
|
|
201
201
|
return {
|
|
202
|
-
questions: {
|
|
203
|
-
list
|
|
202
|
+
questions: /* @__PURE__ */ (() => {
|
|
203
|
+
const list = (params) => request({
|
|
204
204
|
endpoint: "questions.list",
|
|
205
205
|
method: "GET",
|
|
206
206
|
path: "/api/v1/questions",
|
|
207
207
|
query: params
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
208
|
+
});
|
|
209
|
+
return {
|
|
210
|
+
list,
|
|
211
|
+
random: (params) => request({
|
|
212
|
+
endpoint: "questions.random",
|
|
213
|
+
method: "GET",
|
|
214
|
+
path: "/api/v1/questions/random",
|
|
215
|
+
query: params
|
|
216
|
+
}),
|
|
217
|
+
get: (id, params) => request({
|
|
218
|
+
endpoint: "questions.get",
|
|
219
|
+
method: "GET",
|
|
220
|
+
path: `/api/v1/questions/${encodeURIComponent(id)}`,
|
|
221
|
+
query: params
|
|
222
|
+
}),
|
|
223
|
+
pages: (params) => paginate(list, params),
|
|
224
|
+
listAll: (params) => paginateItems(list, params)
|
|
225
|
+
};
|
|
226
|
+
})(),
|
|
222
227
|
categories: {
|
|
223
228
|
list: (params) => request({
|
|
224
229
|
endpoint: "categories.list",
|
|
@@ -234,36 +239,51 @@ function createClient(options) {
|
|
|
234
239
|
path: "/api/v1/languages"
|
|
235
240
|
})
|
|
236
241
|
},
|
|
237
|
-
topics: {
|
|
238
|
-
list
|
|
242
|
+
topics: /* @__PURE__ */ (() => {
|
|
243
|
+
const list = (params) => request({
|
|
239
244
|
endpoint: "topics.list",
|
|
240
245
|
method: "GET",
|
|
241
246
|
path: "/api/v1/topics",
|
|
242
247
|
query: params
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
248
|
+
});
|
|
249
|
+
return {
|
|
250
|
+
list,
|
|
251
|
+
get: (slug, params) => request({
|
|
252
|
+
endpoint: "topics.get",
|
|
253
|
+
method: "GET",
|
|
254
|
+
path: `/api/v1/topics/${encodeURIComponent(slug)}`,
|
|
255
|
+
query: params
|
|
256
|
+
}),
|
|
257
|
+
pages: (params) => paginate(list, params),
|
|
258
|
+
listAll: (params) => paginateItems(list, params)
|
|
259
|
+
};
|
|
260
|
+
})(),
|
|
261
|
+
tags: /* @__PURE__ */ (() => {
|
|
262
|
+
const list = (params) => request({
|
|
253
263
|
endpoint: "tags.list",
|
|
254
264
|
method: "GET",
|
|
255
265
|
path: "/api/v1/tags",
|
|
256
266
|
query: params
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
267
|
+
});
|
|
268
|
+
return {
|
|
269
|
+
list,
|
|
270
|
+
pages: (params) => paginate(list, params),
|
|
271
|
+
listAll: (params) => paginateItems(list, params)
|
|
272
|
+
};
|
|
273
|
+
})(),
|
|
274
|
+
subcategories: /* @__PURE__ */ (() => {
|
|
275
|
+
const list = (params) => request({
|
|
261
276
|
endpoint: "subcategories.list",
|
|
262
277
|
method: "GET",
|
|
263
278
|
path: "/api/v1/subcategories",
|
|
264
279
|
query: params
|
|
265
|
-
})
|
|
266
|
-
|
|
280
|
+
});
|
|
281
|
+
return {
|
|
282
|
+
list,
|
|
283
|
+
pages: (params) => paginate(list, params),
|
|
284
|
+
listAll: (params) => paginateItems(list, params)
|
|
285
|
+
};
|
|
286
|
+
})(),
|
|
267
287
|
stats: {
|
|
268
288
|
get: () => request({
|
|
269
289
|
endpoint: "stats.get",
|
|
@@ -296,6 +316,30 @@ function createClient(options) {
|
|
|
296
316
|
}
|
|
297
317
|
};
|
|
298
318
|
}
|
|
319
|
+
function extractCursor(nextUrl) {
|
|
320
|
+
if (!nextUrl) return null;
|
|
321
|
+
try {
|
|
322
|
+
const url = nextUrl.startsWith("http") ? new URL(nextUrl) : new URL(nextUrl, "http://placeholder");
|
|
323
|
+
return url.searchParams.get("cursor");
|
|
324
|
+
} catch {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
async function* paginate(fetchPage, initial) {
|
|
329
|
+
let nextParams = initial;
|
|
330
|
+
while (true) {
|
|
331
|
+
const page = await fetchPage(nextParams);
|
|
332
|
+
yield page;
|
|
333
|
+
const cursor = extractCursor(page._links?.next);
|
|
334
|
+
if (!cursor) return;
|
|
335
|
+
nextParams = { ...nextParams ?? {}, cursor };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async function* paginateItems(fetchPage, initial) {
|
|
339
|
+
for await (const page of paginate(fetchPage, initial)) {
|
|
340
|
+
for (const item of page.data) yield item;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
299
343
|
function buildUrl(baseUrl, path, query) {
|
|
300
344
|
const url = new URL(baseUrl + path);
|
|
301
345
|
if (query) {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { components } from './types.gen.js';\n\nexport type ProblemDetails = components['schemas']['ProblemDetails'];\n\nexport interface QuizbaseErrorOptions {\n\tstatus: number;\n\tproblem: ProblemDetails;\n\trequestId: string | null;\n\tretryAfter: number | null;\n\turl: string;\n\tmethod: string;\n}\n\n/**\n * Thrown for any non-2xx response from the QuizBase API.\n * Carries the parsed RFC 9457 Problem Details body, X-Request-Id for support,\n * and a parsed retry-after (seconds) for 429s.\n */\nexport class QuizbaseError extends Error {\n\treadonly status: number;\n\treadonly problem: ProblemDetails;\n\treadonly requestId: string | null;\n\treadonly retryAfter: number | null;\n\treadonly url: string;\n\treadonly method: string;\n\n\tconstructor(opts: QuizbaseErrorOptions) {\n\t\tconst title = opts.problem.title ?? `HTTP ${opts.status}`;\n\t\tconst detail = opts.problem.detail ? ` — ${opts.problem.detail}` : '';\n\t\tsuper(`QuizBase ${opts.method} ${opts.url} → ${opts.status} ${title}${detail}`);\n\t\tthis.name = 'QuizbaseError';\n\t\tthis.status = opts.status;\n\t\tthis.problem = opts.problem;\n\t\tthis.requestId = opts.requestId;\n\t\tthis.retryAfter = opts.retryAfter;\n\t\tthis.url = opts.url;\n\t\tthis.method = opts.method;\n\t}\n\n\tget type(): string | undefined {\n\t\treturn this.problem.type;\n\t}\n\n\tget isRateLimited(): boolean {\n\t\treturn this.status === 429;\n\t}\n\n\tget isAuthError(): boolean {\n\t\treturn this.status === 401 || this.status === 403;\n\t}\n\n\tget isServerError(): boolean {\n\t\treturn this.status >= 500;\n\t}\n}\n","import { QuizbaseError, type ProblemDetails } from './errors.js';\nimport type { OnRequestHook } from './telemetry.js';\nimport type { components, paths } from './types.gen.js';\n\ntype Schemas = components['schemas'];\n\n/** Logical endpoint identifiers — stable across path-param values, used for telemetry + per-endpoint timeouts. */\nexport type EndpointKey =\n\t| 'questions.list'\n\t| 'questions.random'\n\t| 'questions.get'\n\t| 'categories.list'\n\t| 'languages.list'\n\t| 'topics.list'\n\t| 'topics.get'\n\t| 'tags.list'\n\t| 'subcategories.list'\n\t| 'stats.get'\n\t| 'me.get'\n\t| 'usage.get'\n\t| 'report.create';\n\nconst DEFAULT_TIMEOUTS: Record<EndpointKey, number> = {\n\t'questions.list': 15_000,\n\t'questions.random': 10_000,\n\t'questions.get': 10_000,\n\t'categories.list': 10_000,\n\t'languages.list': 10_000,\n\t'topics.list': 15_000,\n\t'topics.get': 10_000,\n\t'tags.list': 15_000,\n\t'subcategories.list': 15_000,\n\t'stats.get': 10_000,\n\t'me.get': 10_000,\n\t'usage.get': 10_000,\n\t'report.create': 15_000\n};\n\nexport interface ClientOptions {\n\t/** API key. Use `qb_test_pk_*` / `qb_test_sk_*` for development (free, unmetered) and `qb_live_*` in production. */\n\tapiKey: string;\n\t/** Override base URL. Defaults to `https://quizbase.runriva.com`. */\n\tbaseUrl?: string;\n\t/** Default request timeout in ms. Defaults to 30_000. Per-endpoint overrides take precedence. */\n\ttimeout?: number;\n\t/** Per-endpoint timeout overrides keyed by `EndpointKey`. */\n\ttimeouts?: Partial<Record<EndpointKey, number>>;\n\t/** Number of retries for 429 / 5xx / network errors. Defaults to 2 (3 total attempts). */\n\tretries?: number;\n\t/** Optional `fetch` implementation. Defaults to global `fetch`. */\n\tfetch?: typeof fetch;\n\t/** Telemetry hook fired after every HTTP attempt (including retries). */\n\tonRequest?: OnRequestHook;\n\t/** User-Agent suffix appended to the SDK identifier. */\n\tuserAgent?: string;\n}\n\nexport interface QuizbaseClient {\n\tquestions: {\n\t\tlist(\n\t\t\tparams?: paths['/api/v1/questions']['get']['parameters']['query']\n\t\t): Promise<Schemas['QuestionsListResponse']>;\n\t\trandom(\n\t\t\tparams?: paths['/api/v1/questions/random']['get']['parameters']['query']\n\t\t): Promise<Schemas['QuestionsRandomResponse']>;\n\t\tget(\n\t\t\tid: string,\n\t\t\tparams?: paths['/api/v1/questions/{id}']['get']['parameters']['query']\n\t\t): Promise<Schemas['QuestionByIdResponse']>;\n\t};\n\tcategories: {\n\t\tlist(\n\t\t\tparams?: paths['/api/v1/categories']['get']['parameters']['query']\n\t\t): Promise<Schemas['CategoriesResponse']>;\n\t};\n\tlanguages: {\n\t\tlist(): Promise<Schemas['LanguagesResponse']>;\n\t};\n\ttopics: {\n\t\tlist(\n\t\t\tparams?: paths['/api/v1/topics']['get']['parameters']['query']\n\t\t): Promise<Schemas['TopicsListResponse']>;\n\t\tget(\n\t\t\tslug: string,\n\t\t\tparams?: paths['/api/v1/topics/{slug}']['get']['parameters']['query']\n\t\t): Promise<Schemas['TopicDetailResponse']>;\n\t};\n\ttags: {\n\t\tlist(\n\t\t\tparams?: paths['/api/v1/tags']['get']['parameters']['query']\n\t\t): Promise<Schemas['TagsListResponse']>;\n\t};\n\tsubcategories: {\n\t\tlist(\n\t\t\tparams?: paths['/api/v1/subcategories']['get']['parameters']['query']\n\t\t): Promise<Schemas['SubcategoriesListResponse']>;\n\t};\n\tstats: {\n\t\tget(): Promise<Schemas['StatsResponse']>;\n\t};\n\tme: {\n\t\tget(): Promise<Schemas['MeResponse']>;\n\t};\n\tusage: {\n\t\tget(\n\t\t\tparams?: paths['/api/v1/usage']['get']['parameters']['query']\n\t\t): Promise<Schemas['UsageResponse']>;\n\t};\n\treport: {\n\t\tcreate(\n\t\t\tbody: NonNullable<\n\t\t\t\tpaths['/api/v1/report']['post']['requestBody']\n\t\t\t>['content']['application/json']\n\t\t): Promise<Schemas['ReportAcceptedResponse']>;\n\t};\n}\n\nconst DEFAULT_BASE_URL = 'https://quizbase.runriva.com';\nconst SDK_VERSION = '0.1.0';\n\ninterface RequestParams {\n\tendpoint: EndpointKey;\n\tmethod: 'GET' | 'POST';\n\tpath: string;\n\tquery?: Record<string, unknown> | undefined;\n\tbody?: unknown;\n}\n\nexport function createClient(options: ClientOptions): QuizbaseClient {\n\tif (!options.apiKey) {\n\t\tthrow new Error('createClient: `apiKey` is required.');\n\t}\n\tconst baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, '');\n\tconst defaultTimeout = options.timeout ?? 30_000;\n\tconst timeouts: Record<EndpointKey, number> = { ...DEFAULT_TIMEOUTS };\n\tif (options.timeouts) {\n\t\tfor (const [key, value] of Object.entries(options.timeouts) as [\n\t\t\tEndpointKey,\n\t\t\tnumber | undefined\n\t\t][]) {\n\t\t\tif (typeof value === 'number') timeouts[key] = value;\n\t\t}\n\t}\n\tconst retries = Math.max(0, options.retries ?? 2);\n\tconst doFetch = options.fetch ?? globalThis.fetch;\n\tif (typeof doFetch !== 'function') {\n\t\tthrow new Error(\n\t\t\t'createClient: global `fetch` is unavailable. Pass `fetch` option (Node ≥20 or polyfill).'\n\t\t);\n\t}\n\tconst userAgent = `quizbase-client/${SDK_VERSION}${options.userAgent ? ` ${options.userAgent}` : ''}`;\n\n\tasync function request<T>(params: RequestParams): Promise<T> {\n\t\tconst timeoutMs = timeouts[params.endpoint] ?? defaultTimeout;\n\t\tconst url = buildUrl(baseUrl, params.path, params.query);\n\t\tconst requestId = generateRequestId();\n\t\tconst headers: Record<string, string> = {\n\t\t\tAccept: 'application/json',\n\t\t\tAuthorization: `Bearer ${options.apiKey}`,\n\t\t\t'X-Request-Id': requestId,\n\t\t\t'User-Agent': userAgent\n\t\t};\n\t\tlet body: string | undefined;\n\t\tif (params.body !== undefined) {\n\t\t\theaders['Content-Type'] = 'application/json';\n\t\t\tbody = JSON.stringify(params.body);\n\t\t}\n\n\t\tlet attempt = 0;\n\t\tlet lastError: unknown;\n\t\twhile (attempt <= retries) {\n\t\t\tconst startedAt = Date.now();\n\t\t\tconst controller = new AbortController();\n\t\t\tconst timer = setTimeout(() => controller.abort(), timeoutMs);\n\t\t\tlet status = 0;\n\t\t\tlet response: Response | undefined;\n\t\t\tlet attemptError: Error | undefined;\n\t\t\ttry {\n\t\t\t\tresponse = await doFetch(url, {\n\t\t\t\t\tmethod: params.method,\n\t\t\t\t\theaders,\n\t\t\t\t\tbody,\n\t\t\t\t\tsignal: controller.signal\n\t\t\t\t});\n\t\t\t\tstatus = response.status;\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = (await response.json()) as T;\n\t\t\t\t\tawait emit(options.onRequest, {\n\t\t\t\t\t\tmethod: params.method,\n\t\t\t\t\t\tendpoint: params.endpoint,\n\t\t\t\t\t\turl,\n\t\t\t\t\t\tduration: Date.now() - startedAt,\n\t\t\t\t\t\tstatus,\n\t\t\t\t\t\trequestId: response.headers.get('x-request-id') ?? requestId,\n\t\t\t\t\t\tretryCount: attempt,\n\t\t\t\t\t\tfinal: true\n\t\t\t\t\t});\n\t\t\t\t\treturn data;\n\t\t\t\t}\n\t\t\t\tconst problem = await safeProblem(response);\n\t\t\t\tconst retryAfterHeader = response.headers.get('retry-after');\n\t\t\t\tconst retryAfter = retryAfterHeader ? parseRetryAfter(retryAfterHeader) : null;\n\t\t\t\tconst err = new QuizbaseError({\n\t\t\t\t\tstatus,\n\t\t\t\t\tproblem,\n\t\t\t\t\trequestId: response.headers.get('x-request-id') ?? requestId,\n\t\t\t\t\tretryAfter,\n\t\t\t\t\turl,\n\t\t\t\t\tmethod: params.method\n\t\t\t\t});\n\t\t\t\tattemptError = err;\n\t\t\t\tif (shouldRetry(status) && attempt < retries) {\n\t\t\t\t\tawait emit(options.onRequest, {\n\t\t\t\t\t\tmethod: params.method,\n\t\t\t\t\t\tendpoint: params.endpoint,\n\t\t\t\t\t\turl,\n\t\t\t\t\t\tduration: Date.now() - startedAt,\n\t\t\t\t\t\tstatus,\n\t\t\t\t\t\trequestId: err.requestId,\n\t\t\t\t\t\tretryCount: attempt,\n\t\t\t\t\t\tfinal: false,\n\t\t\t\t\t\terror: err\n\t\t\t\t\t});\n\t\t\t\t\tawait sleep(backoffMs(attempt, retryAfter));\n\t\t\t\t\tattempt += 1;\n\t\t\t\t\tlastError = err;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tawait emit(options.onRequest, {\n\t\t\t\t\tmethod: params.method,\n\t\t\t\t\tendpoint: params.endpoint,\n\t\t\t\t\turl,\n\t\t\t\t\tduration: Date.now() - startedAt,\n\t\t\t\t\tstatus,\n\t\t\t\t\trequestId: err.requestId,\n\t\t\t\t\tretryCount: attempt,\n\t\t\t\t\tfinal: true,\n\t\t\t\t\terror: err\n\t\t\t\t});\n\t\t\t\tthrow err;\n\t\t\t} catch (err) {\n\t\t\t\tif (err instanceof QuizbaseError) throw err;\n\t\t\t\tconst networkErr = err instanceof Error ? err : new Error(String(err));\n\t\t\t\tattemptError = networkErr;\n\t\t\t\tif (attempt < retries) {\n\t\t\t\t\tawait emit(options.onRequest, {\n\t\t\t\t\t\tmethod: params.method,\n\t\t\t\t\t\tendpoint: params.endpoint,\n\t\t\t\t\t\turl,\n\t\t\t\t\t\tduration: Date.now() - startedAt,\n\t\t\t\t\t\tstatus,\n\t\t\t\t\t\trequestId: null,\n\t\t\t\t\t\tretryCount: attempt,\n\t\t\t\t\t\tfinal: false,\n\t\t\t\t\t\terror: networkErr\n\t\t\t\t\t});\n\t\t\t\t\tawait sleep(backoffMs(attempt, null));\n\t\t\t\t\tattempt += 1;\n\t\t\t\t\tlastError = networkErr;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tawait emit(options.onRequest, {\n\t\t\t\t\tmethod: params.method,\n\t\t\t\t\tendpoint: params.endpoint,\n\t\t\t\t\turl,\n\t\t\t\t\tduration: Date.now() - startedAt,\n\t\t\t\t\tstatus,\n\t\t\t\t\trequestId: null,\n\t\t\t\t\tretryCount: attempt,\n\t\t\t\t\tfinal: true,\n\t\t\t\t\terror: networkErr\n\t\t\t\t});\n\t\t\t\tthrow networkErr;\n\t\t\t} finally {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tvoid attemptError;\n\t\t\t\tvoid response;\n\t\t\t}\n\t\t}\n\t\tthrow lastError ?? new Error('quizbase-client: retry loop exhausted unexpectedly');\n\t}\n\n\treturn {\n\t\tquestions: {\n\t\t\tlist: (params) =>\n\t\t\t\trequest({\n\t\t\t\t\tendpoint: 'questions.list',\n\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\tpath: '/api/v1/questions',\n\t\t\t\t\tquery: params\n\t\t\t\t}),\n\t\t\trandom: (params) =>\n\t\t\t\trequest({\n\t\t\t\t\tendpoint: 'questions.random',\n\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\tpath: '/api/v1/questions/random',\n\t\t\t\t\tquery: params\n\t\t\t\t}),\n\t\t\tget: (id, params) =>\n\t\t\t\trequest({\n\t\t\t\t\tendpoint: 'questions.get',\n\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\tpath: `/api/v1/questions/${encodeURIComponent(id)}`,\n\t\t\t\t\tquery: params\n\t\t\t\t})\n\t\t},\n\t\tcategories: {\n\t\t\tlist: (params) =>\n\t\t\t\trequest({\n\t\t\t\t\tendpoint: 'categories.list',\n\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\tpath: '/api/v1/categories',\n\t\t\t\t\tquery: params\n\t\t\t\t})\n\t\t},\n\t\tlanguages: {\n\t\t\tlist: () =>\n\t\t\t\trequest({\n\t\t\t\t\tendpoint: 'languages.list',\n\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\tpath: '/api/v1/languages'\n\t\t\t\t})\n\t\t},\n\t\ttopics: {\n\t\t\tlist: (params) =>\n\t\t\t\trequest({\n\t\t\t\t\tendpoint: 'topics.list',\n\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\tpath: '/api/v1/topics',\n\t\t\t\t\tquery: params\n\t\t\t\t}),\n\t\t\tget: (slug, params) =>\n\t\t\t\trequest({\n\t\t\t\t\tendpoint: 'topics.get',\n\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\tpath: `/api/v1/topics/${encodeURIComponent(slug)}`,\n\t\t\t\t\tquery: params\n\t\t\t\t})\n\t\t},\n\t\ttags: {\n\t\t\tlist: (params) =>\n\t\t\t\trequest({\n\t\t\t\t\tendpoint: 'tags.list',\n\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\tpath: '/api/v1/tags',\n\t\t\t\t\tquery: params\n\t\t\t\t})\n\t\t},\n\t\tsubcategories: {\n\t\t\tlist: (params) =>\n\t\t\t\trequest({\n\t\t\t\t\tendpoint: 'subcategories.list',\n\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\tpath: '/api/v1/subcategories',\n\t\t\t\t\tquery: params\n\t\t\t\t})\n\t\t},\n\t\tstats: {\n\t\t\tget: () =>\n\t\t\t\trequest({\n\t\t\t\t\tendpoint: 'stats.get',\n\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\tpath: '/api/v1/stats'\n\t\t\t\t})\n\t\t},\n\t\tme: {\n\t\t\tget: () =>\n\t\t\t\trequest({\n\t\t\t\t\tendpoint: 'me.get',\n\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\tpath: '/api/v1/me'\n\t\t\t\t})\n\t\t},\n\t\tusage: {\n\t\t\tget: (params) =>\n\t\t\t\trequest({\n\t\t\t\t\tendpoint: 'usage.get',\n\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\tpath: '/api/v1/usage',\n\t\t\t\t\tquery: params\n\t\t\t\t})\n\t\t},\n\t\treport: {\n\t\t\tcreate: (body) =>\n\t\t\t\trequest({\n\t\t\t\t\tendpoint: 'report.create',\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\tpath: '/api/v1/report',\n\t\t\t\t\tbody\n\t\t\t\t})\n\t\t}\n\t};\n}\n\nfunction buildUrl(baseUrl: string, path: string, query: Record<string, unknown> | undefined): string {\n\tconst url = new URL(baseUrl + path);\n\tif (query) {\n\t\tfor (const [key, value] of Object.entries(query)) {\n\t\t\tif (value === undefined || value === null) continue;\n\t\t\tif (Array.isArray(value)) {\n\t\t\t\tfor (const item of value) {\n\t\t\t\t\tif (item !== undefined && item !== null) {\n\t\t\t\t\t\turl.searchParams.append(key, String(item));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\turl.searchParams.append(key, String(value));\n\t\t\t}\n\t\t}\n\t}\n\treturn url.toString();\n}\n\nfunction generateRequestId(): string {\n\tconst cryptoApi: Crypto | undefined = globalThis.crypto;\n\tif (cryptoApi && typeof cryptoApi.randomUUID === 'function') {\n\t\treturn cryptoApi.randomUUID();\n\t}\n\treturn `req-${Math.random().toString(36).slice(2)}-${Date.now().toString(36)}`;\n}\n\nasync function safeProblem(response: Response): Promise<ProblemDetails> {\n\ttry {\n\t\tconst data = (await response.json()) as ProblemDetails;\n\t\tif (data && typeof data === 'object') return data;\n\t} catch {\n\t\t// fall through\n\t}\n\treturn {\n\t\ttype: 'about:blank',\n\t\ttitle: response.statusText || `HTTP ${response.status}`,\n\t\tstatus: response.status,\n\t\tdetail: '',\n\t\tinstance: '',\n\t\tcode: 'unknown'\n\t} satisfies ProblemDetails;\n}\n\nfunction shouldRetry(status: number): boolean {\n\treturn status === 429 || (status >= 500 && status < 600);\n}\n\nfunction parseRetryAfter(value: string): number | null {\n\tconst seconds = Number.parseInt(value, 10);\n\tif (Number.isFinite(seconds) && seconds >= 0) return seconds;\n\tconst date = Date.parse(value);\n\tif (Number.isFinite(date)) {\n\t\tconst diff = Math.max(0, Math.ceil((date - Date.now()) / 1000));\n\t\treturn diff;\n\t}\n\treturn null;\n}\n\nfunction backoffMs(attempt: number, retryAfterSeconds: number | null): number {\n\tif (retryAfterSeconds !== null) return retryAfterSeconds * 1000;\n\tconst base = 250;\n\tconst exp = base * 2 ** attempt;\n\tconst jitter = Math.random() * 100;\n\treturn Math.min(exp + jitter, 5_000);\n}\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function emit(\n\thook: OnRequestHook | undefined,\n\tevent: Parameters<OnRequestHook>[0]\n): Promise<void> {\n\tif (!hook) return;\n\ttry {\n\t\tawait hook(event);\n\t} catch {\n\t\t// telemetry must never break the caller\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { components } from './types.gen.js';\n\nexport type ProblemDetails = components['schemas']['ProblemDetails'];\n\nexport interface QuizbaseErrorOptions {\n\tstatus: number;\n\tproblem: ProblemDetails;\n\trequestId: string | null;\n\tretryAfter: number | null;\n\turl: string;\n\tmethod: string;\n}\n\n/**\n * Thrown for any non-2xx response from the QuizBase API.\n * Carries the parsed RFC 9457 Problem Details body, X-Request-Id for support,\n * and a parsed retry-after (seconds) for 429s.\n */\nexport class QuizbaseError extends Error {\n\treadonly status: number;\n\treadonly problem: ProblemDetails;\n\treadonly requestId: string | null;\n\treadonly retryAfter: number | null;\n\treadonly url: string;\n\treadonly method: string;\n\n\tconstructor(opts: QuizbaseErrorOptions) {\n\t\tconst title = opts.problem.title ?? `HTTP ${opts.status}`;\n\t\tconst detail = opts.problem.detail ? ` — ${opts.problem.detail}` : '';\n\t\tsuper(`QuizBase ${opts.method} ${opts.url} → ${opts.status} ${title}${detail}`);\n\t\tthis.name = 'QuizbaseError';\n\t\tthis.status = opts.status;\n\t\tthis.problem = opts.problem;\n\t\tthis.requestId = opts.requestId;\n\t\tthis.retryAfter = opts.retryAfter;\n\t\tthis.url = opts.url;\n\t\tthis.method = opts.method;\n\t}\n\n\tget type(): string | undefined {\n\t\treturn this.problem.type;\n\t}\n\n\tget isRateLimited(): boolean {\n\t\treturn this.status === 429;\n\t}\n\n\tget isAuthError(): boolean {\n\t\treturn this.status === 401 || this.status === 403;\n\t}\n\n\tget isServerError(): boolean {\n\t\treturn this.status >= 500;\n\t}\n}\n","import { QuizbaseError, type ProblemDetails } from './errors.js';\nimport type { OnRequestHook } from './telemetry.js';\nimport type { components, paths } from './types.gen.js';\n\ntype Schemas = components['schemas'];\n\n/** Logical endpoint identifiers — stable across path-param values, used for telemetry + per-endpoint timeouts. */\nexport type EndpointKey =\n\t| 'questions.list'\n\t| 'questions.random'\n\t| 'questions.get'\n\t| 'categories.list'\n\t| 'languages.list'\n\t| 'topics.list'\n\t| 'topics.get'\n\t| 'tags.list'\n\t| 'subcategories.list'\n\t| 'stats.get'\n\t| 'me.get'\n\t| 'usage.get'\n\t| 'report.create';\n\nconst DEFAULT_TIMEOUTS: Record<EndpointKey, number> = {\n\t'questions.list': 15_000,\n\t'questions.random': 10_000,\n\t'questions.get': 10_000,\n\t'categories.list': 10_000,\n\t'languages.list': 10_000,\n\t'topics.list': 15_000,\n\t'topics.get': 10_000,\n\t'tags.list': 15_000,\n\t'subcategories.list': 15_000,\n\t'stats.get': 10_000,\n\t'me.get': 10_000,\n\t'usage.get': 10_000,\n\t'report.create': 15_000\n};\n\nexport interface ClientOptions {\n\t/** API key — `qb_pk_*` (publishable, CORS-safe for browsers) or `qb_sk_*` (secret, backend-only). Get one at https://quizbase.runriva.com/dashboard/keys. */\n\tapiKey: string;\n\t/** Override base URL. Defaults to `https://quizbase.runriva.com`. */\n\tbaseUrl?: string;\n\t/** Default request timeout in ms. Defaults to 30_000. Per-endpoint overrides take precedence. */\n\ttimeout?: number;\n\t/** Per-endpoint timeout overrides keyed by `EndpointKey`. */\n\ttimeouts?: Partial<Record<EndpointKey, number>>;\n\t/** Number of retries for 429 / 5xx / network errors. Defaults to 2 (3 total attempts). */\n\tretries?: number;\n\t/** Optional `fetch` implementation. Defaults to global `fetch`. */\n\tfetch?: typeof fetch;\n\t/** Telemetry hook fired after every HTTP attempt (including retries). */\n\tonRequest?: OnRequestHook;\n\t/** User-Agent suffix appended to the SDK identifier. */\n\tuserAgent?: string;\n}\n\ntype QuestionsListParams = paths['/api/v1/questions']['get']['parameters']['query'];\ntype TopicsListParams = paths['/api/v1/topics']['get']['parameters']['query'];\ntype TagsListParams = paths['/api/v1/tags']['get']['parameters']['query'];\ntype SubcategoriesListParams = paths['/api/v1/subcategories']['get']['parameters']['query'];\n\nexport interface QuizbaseClient {\n\tquestions: {\n\t\tlist(params?: QuestionsListParams): Promise<Schemas['QuestionsListResponse']>;\n\t\trandom(\n\t\t\tparams?: paths['/api/v1/questions/random']['get']['parameters']['query']\n\t\t): Promise<Schemas['QuestionsRandomResponse']>;\n\t\tget(\n\t\t\tid: string,\n\t\t\tparams?: paths['/api/v1/questions/{id}']['get']['parameters']['query']\n\t\t): Promise<Schemas['QuestionByIdResponse']>;\n\t\t/** Iterate every page of `/questions`, auto-following `_links.next`. */\n\t\tpages(params?: QuestionsListParams): AsyncIterableIterator<Schemas['QuestionsListResponse']>;\n\t\t/** Iterate every question across all pages. */\n\t\tlistAll(params?: QuestionsListParams): AsyncIterableIterator<Schemas['Question']>;\n\t};\n\tcategories: {\n\t\tlist(\n\t\t\tparams?: paths['/api/v1/categories']['get']['parameters']['query']\n\t\t): Promise<Schemas['CategoriesResponse']>;\n\t};\n\tlanguages: {\n\t\tlist(): Promise<Schemas['LanguagesResponse']>;\n\t};\n\ttopics: {\n\t\tlist(params?: TopicsListParams): Promise<Schemas['TopicsListResponse']>;\n\t\tget(\n\t\t\tslug: string,\n\t\t\tparams?: paths['/api/v1/topics/{slug}']['get']['parameters']['query']\n\t\t): Promise<Schemas['TopicDetailResponse']>;\n\t\t/** Iterate every page of `/topics`, auto-following `_links.next`. */\n\t\tpages(params?: TopicsListParams): AsyncIterableIterator<Schemas['TopicsListResponse']>;\n\t\t/** Iterate every topic across all pages. */\n\t\tlistAll(params?: TopicsListParams): AsyncIterableIterator<Schemas['TopicEntry']>;\n\t};\n\ttags: {\n\t\tlist(params?: TagsListParams): Promise<Schemas['TagsListResponse']>;\n\t\t/** Iterate every page of `/tags`, auto-following `_links.next`. */\n\t\tpages(params?: TagsListParams): AsyncIterableIterator<Schemas['TagsListResponse']>;\n\t\t/** Iterate every tag across all pages. */\n\t\tlistAll(params?: TagsListParams): AsyncIterableIterator<Schemas['RawSlugEntry']>;\n\t};\n\tsubcategories: {\n\t\tlist(params?: SubcategoriesListParams): Promise<Schemas['SubcategoriesListResponse']>;\n\t\t/** Iterate every page of `/subcategories`, auto-following `_links.next`. */\n\t\tpages(\n\t\t\tparams?: SubcategoriesListParams\n\t\t): AsyncIterableIterator<Schemas['SubcategoriesListResponse']>;\n\t\t/** Iterate every subcategory across all pages. */\n\t\tlistAll(params?: SubcategoriesListParams): AsyncIterableIterator<Schemas['RawSlugEntry']>;\n\t};\n\tstats: {\n\t\tget(): Promise<Schemas['StatsResponse']>;\n\t};\n\tme: {\n\t\tget(): Promise<Schemas['MeResponse']>;\n\t};\n\tusage: {\n\t\tget(\n\t\t\tparams?: paths['/api/v1/usage']['get']['parameters']['query']\n\t\t): Promise<Schemas['UsageResponse']>;\n\t};\n\treport: {\n\t\tcreate(\n\t\t\tbody: NonNullable<\n\t\t\t\tpaths['/api/v1/report']['post']['requestBody']\n\t\t\t>['content']['application/json']\n\t\t): Promise<Schemas['ReportAcceptedResponse']>;\n\t};\n}\n\nconst DEFAULT_BASE_URL = 'https://quizbase.runriva.com';\nconst SDK_VERSION = '0.1.0';\n\ninterface RequestParams {\n\tendpoint: EndpointKey;\n\tmethod: 'GET' | 'POST';\n\tpath: string;\n\tquery?: Record<string, unknown> | undefined;\n\tbody?: unknown;\n}\n\nexport function createClient(options: ClientOptions): QuizbaseClient {\n\tif (!options.apiKey) {\n\t\tthrow new Error('createClient: `apiKey` is required.');\n\t}\n\tconst baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, '');\n\tconst defaultTimeout = options.timeout ?? 30_000;\n\tconst timeouts: Record<EndpointKey, number> = { ...DEFAULT_TIMEOUTS };\n\tif (options.timeouts) {\n\t\tfor (const [key, value] of Object.entries(options.timeouts) as [\n\t\t\tEndpointKey,\n\t\t\tnumber | undefined\n\t\t][]) {\n\t\t\tif (typeof value === 'number') timeouts[key] = value;\n\t\t}\n\t}\n\tconst retries = Math.max(0, options.retries ?? 2);\n\tconst doFetch = options.fetch ?? globalThis.fetch;\n\tif (typeof doFetch !== 'function') {\n\t\tthrow new Error(\n\t\t\t'createClient: global `fetch` is unavailable. Pass `fetch` option (Node ≥20 or polyfill).'\n\t\t);\n\t}\n\tconst userAgent = `quizbase-client/${SDK_VERSION}${options.userAgent ? ` ${options.userAgent}` : ''}`;\n\n\tasync function request<T>(params: RequestParams): Promise<T> {\n\t\tconst timeoutMs = timeouts[params.endpoint] ?? defaultTimeout;\n\t\tconst url = buildUrl(baseUrl, params.path, params.query);\n\t\tconst requestId = generateRequestId();\n\t\tconst headers: Record<string, string> = {\n\t\t\tAccept: 'application/json',\n\t\t\tAuthorization: `Bearer ${options.apiKey}`,\n\t\t\t'X-Request-Id': requestId,\n\t\t\t'User-Agent': userAgent\n\t\t};\n\t\tlet body: string | undefined;\n\t\tif (params.body !== undefined) {\n\t\t\theaders['Content-Type'] = 'application/json';\n\t\t\tbody = JSON.stringify(params.body);\n\t\t}\n\n\t\tlet attempt = 0;\n\t\tlet lastError: unknown;\n\t\twhile (attempt <= retries) {\n\t\t\tconst startedAt = Date.now();\n\t\t\tconst controller = new AbortController();\n\t\t\tconst timer = setTimeout(() => controller.abort(), timeoutMs);\n\t\t\tlet status = 0;\n\t\t\tlet response: Response | undefined;\n\t\t\tlet attemptError: Error | undefined;\n\t\t\ttry {\n\t\t\t\tresponse = await doFetch(url, {\n\t\t\t\t\tmethod: params.method,\n\t\t\t\t\theaders,\n\t\t\t\t\tbody,\n\t\t\t\t\tsignal: controller.signal\n\t\t\t\t});\n\t\t\t\tstatus = response.status;\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tconst data = (await response.json()) as T;\n\t\t\t\t\tawait emit(options.onRequest, {\n\t\t\t\t\t\tmethod: params.method,\n\t\t\t\t\t\tendpoint: params.endpoint,\n\t\t\t\t\t\turl,\n\t\t\t\t\t\tduration: Date.now() - startedAt,\n\t\t\t\t\t\tstatus,\n\t\t\t\t\t\trequestId: response.headers.get('x-request-id') ?? requestId,\n\t\t\t\t\t\tretryCount: attempt,\n\t\t\t\t\t\tfinal: true\n\t\t\t\t\t});\n\t\t\t\t\treturn data;\n\t\t\t\t}\n\t\t\t\tconst problem = await safeProblem(response);\n\t\t\t\tconst retryAfterHeader = response.headers.get('retry-after');\n\t\t\t\tconst retryAfter = retryAfterHeader ? parseRetryAfter(retryAfterHeader) : null;\n\t\t\t\tconst err = new QuizbaseError({\n\t\t\t\t\tstatus,\n\t\t\t\t\tproblem,\n\t\t\t\t\trequestId: response.headers.get('x-request-id') ?? requestId,\n\t\t\t\t\tretryAfter,\n\t\t\t\t\turl,\n\t\t\t\t\tmethod: params.method\n\t\t\t\t});\n\t\t\t\tattemptError = err;\n\t\t\t\tif (shouldRetry(status) && attempt < retries) {\n\t\t\t\t\tawait emit(options.onRequest, {\n\t\t\t\t\t\tmethod: params.method,\n\t\t\t\t\t\tendpoint: params.endpoint,\n\t\t\t\t\t\turl,\n\t\t\t\t\t\tduration: Date.now() - startedAt,\n\t\t\t\t\t\tstatus,\n\t\t\t\t\t\trequestId: err.requestId,\n\t\t\t\t\t\tretryCount: attempt,\n\t\t\t\t\t\tfinal: false,\n\t\t\t\t\t\terror: err\n\t\t\t\t\t});\n\t\t\t\t\tawait sleep(backoffMs(attempt, retryAfter));\n\t\t\t\t\tattempt += 1;\n\t\t\t\t\tlastError = err;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tawait emit(options.onRequest, {\n\t\t\t\t\tmethod: params.method,\n\t\t\t\t\tendpoint: params.endpoint,\n\t\t\t\t\turl,\n\t\t\t\t\tduration: Date.now() - startedAt,\n\t\t\t\t\tstatus,\n\t\t\t\t\trequestId: err.requestId,\n\t\t\t\t\tretryCount: attempt,\n\t\t\t\t\tfinal: true,\n\t\t\t\t\terror: err\n\t\t\t\t});\n\t\t\t\tthrow err;\n\t\t\t} catch (err) {\n\t\t\t\tif (err instanceof QuizbaseError) throw err;\n\t\t\t\tconst networkErr = err instanceof Error ? err : new Error(String(err));\n\t\t\t\tattemptError = networkErr;\n\t\t\t\tif (attempt < retries) {\n\t\t\t\t\tawait emit(options.onRequest, {\n\t\t\t\t\t\tmethod: params.method,\n\t\t\t\t\t\tendpoint: params.endpoint,\n\t\t\t\t\t\turl,\n\t\t\t\t\t\tduration: Date.now() - startedAt,\n\t\t\t\t\t\tstatus,\n\t\t\t\t\t\trequestId: null,\n\t\t\t\t\t\tretryCount: attempt,\n\t\t\t\t\t\tfinal: false,\n\t\t\t\t\t\terror: networkErr\n\t\t\t\t\t});\n\t\t\t\t\tawait sleep(backoffMs(attempt, null));\n\t\t\t\t\tattempt += 1;\n\t\t\t\t\tlastError = networkErr;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tawait emit(options.onRequest, {\n\t\t\t\t\tmethod: params.method,\n\t\t\t\t\tendpoint: params.endpoint,\n\t\t\t\t\turl,\n\t\t\t\t\tduration: Date.now() - startedAt,\n\t\t\t\t\tstatus,\n\t\t\t\t\trequestId: null,\n\t\t\t\t\tretryCount: attempt,\n\t\t\t\t\tfinal: true,\n\t\t\t\t\terror: networkErr\n\t\t\t\t});\n\t\t\t\tthrow networkErr;\n\t\t\t} finally {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tvoid attemptError;\n\t\t\t\tvoid response;\n\t\t\t}\n\t\t}\n\t\tthrow lastError ?? new Error('quizbase-client: retry loop exhausted unexpectedly');\n\t}\n\n\treturn {\n\t\tquestions: (() => {\n\t\t\tconst list = (params?: QuestionsListParams) =>\n\t\t\t\trequest<Schemas['QuestionsListResponse']>({\n\t\t\t\t\tendpoint: 'questions.list',\n\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\tpath: '/api/v1/questions',\n\t\t\t\t\tquery: params\n\t\t\t\t});\n\t\t\treturn {\n\t\t\t\tlist,\n\t\t\t\trandom: (params) =>\n\t\t\t\t\trequest({\n\t\t\t\t\t\tendpoint: 'questions.random',\n\t\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\t\tpath: '/api/v1/questions/random',\n\t\t\t\t\t\tquery: params\n\t\t\t\t\t}),\n\t\t\t\tget: (id, params) =>\n\t\t\t\t\trequest({\n\t\t\t\t\t\tendpoint: 'questions.get',\n\t\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\t\tpath: `/api/v1/questions/${encodeURIComponent(id)}`,\n\t\t\t\t\t\tquery: params\n\t\t\t\t\t}),\n\t\t\t\tpages: (params) => paginate(list, params),\n\t\t\t\tlistAll: (params) => paginateItems(list, params)\n\t\t\t} satisfies QuizbaseClient['questions'];\n\t\t})(),\n\t\tcategories: {\n\t\t\tlist: (params) =>\n\t\t\t\trequest({\n\t\t\t\t\tendpoint: 'categories.list',\n\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\tpath: '/api/v1/categories',\n\t\t\t\t\tquery: params\n\t\t\t\t})\n\t\t},\n\t\tlanguages: {\n\t\t\tlist: () =>\n\t\t\t\trequest({\n\t\t\t\t\tendpoint: 'languages.list',\n\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\tpath: '/api/v1/languages'\n\t\t\t\t})\n\t\t},\n\t\ttopics: (() => {\n\t\t\tconst list = (params?: TopicsListParams) =>\n\t\t\t\trequest<Schemas['TopicsListResponse']>({\n\t\t\t\t\tendpoint: 'topics.list',\n\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\tpath: '/api/v1/topics',\n\t\t\t\t\tquery: params\n\t\t\t\t});\n\t\t\treturn {\n\t\t\t\tlist,\n\t\t\t\tget: (slug, params) =>\n\t\t\t\t\trequest({\n\t\t\t\t\t\tendpoint: 'topics.get',\n\t\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\t\tpath: `/api/v1/topics/${encodeURIComponent(slug)}`,\n\t\t\t\t\t\tquery: params\n\t\t\t\t\t}),\n\t\t\t\tpages: (params) => paginate(list, params),\n\t\t\t\tlistAll: (params) => paginateItems(list, params)\n\t\t\t} satisfies QuizbaseClient['topics'];\n\t\t})(),\n\t\ttags: (() => {\n\t\t\tconst list = (params?: TagsListParams) =>\n\t\t\t\trequest<Schemas['TagsListResponse']>({\n\t\t\t\t\tendpoint: 'tags.list',\n\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\tpath: '/api/v1/tags',\n\t\t\t\t\tquery: params\n\t\t\t\t});\n\t\t\treturn {\n\t\t\t\tlist,\n\t\t\t\tpages: (params) => paginate(list, params),\n\t\t\t\tlistAll: (params) => paginateItems(list, params)\n\t\t\t} satisfies QuizbaseClient['tags'];\n\t\t})(),\n\t\tsubcategories: (() => {\n\t\t\tconst list = (params?: SubcategoriesListParams) =>\n\t\t\t\trequest<Schemas['SubcategoriesListResponse']>({\n\t\t\t\t\tendpoint: 'subcategories.list',\n\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\tpath: '/api/v1/subcategories',\n\t\t\t\t\tquery: params\n\t\t\t\t});\n\t\t\treturn {\n\t\t\t\tlist,\n\t\t\t\tpages: (params) => paginate(list, params),\n\t\t\t\tlistAll: (params) => paginateItems(list, params)\n\t\t\t} satisfies QuizbaseClient['subcategories'];\n\t\t})(),\n\t\tstats: {\n\t\t\tget: () =>\n\t\t\t\trequest({\n\t\t\t\t\tendpoint: 'stats.get',\n\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\tpath: '/api/v1/stats'\n\t\t\t\t})\n\t\t},\n\t\tme: {\n\t\t\tget: () =>\n\t\t\t\trequest({\n\t\t\t\t\tendpoint: 'me.get',\n\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\tpath: '/api/v1/me'\n\t\t\t\t})\n\t\t},\n\t\tusage: {\n\t\t\tget: (params) =>\n\t\t\t\trequest({\n\t\t\t\t\tendpoint: 'usage.get',\n\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\tpath: '/api/v1/usage',\n\t\t\t\t\tquery: params\n\t\t\t\t})\n\t\t},\n\t\treport: {\n\t\t\tcreate: (body) =>\n\t\t\t\trequest({\n\t\t\t\t\tendpoint: 'report.create',\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\tpath: '/api/v1/report',\n\t\t\t\t\tbody\n\t\t\t\t})\n\t\t}\n\t};\n}\n\ninterface PaginatedParams {\n\tcursor?: string;\n\t[key: string]: unknown;\n}\n\ninterface PaginatedPage<T> {\n\tdata: T[];\n\t_links?: { next?: string; prev?: string };\n}\n\n/**\n * Extract the `cursor` query param from an absolute or relative `_links.next` URL.\n * Returns `null` when the URL is absent or malformed — caller treats that as end-of-stream.\n */\nfunction extractCursor(nextUrl: string | undefined): string | null {\n\tif (!nextUrl) return null;\n\ttry {\n\t\tconst url = nextUrl.startsWith('http')\n\t\t\t? new URL(nextUrl)\n\t\t\t: new URL(nextUrl, 'http://placeholder');\n\t\treturn url.searchParams.get('cursor');\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nasync function* paginate<P extends PaginatedParams, R extends PaginatedPage<unknown>>(\n\tfetchPage: (params?: P) => Promise<R>,\n\tinitial: P | undefined\n): AsyncIterableIterator<R> {\n\tlet nextParams: P | undefined = initial;\n\twhile (true) {\n\t\tconst page = await fetchPage(nextParams);\n\t\tyield page;\n\t\tconst cursor = extractCursor(page._links?.next);\n\t\tif (!cursor) return;\n\t\tnextParams = { ...(nextParams ?? ({} as P)), cursor } as P;\n\t}\n}\n\nasync function* paginateItems<P extends PaginatedParams, T>(\n\tfetchPage: (params?: P) => Promise<PaginatedPage<T>>,\n\tinitial: P | undefined\n): AsyncIterableIterator<T> {\n\tfor await (const page of paginate(fetchPage, initial)) {\n\t\tfor (const item of page.data) yield item;\n\t}\n}\n\nfunction buildUrl(baseUrl: string, path: string, query: Record<string, unknown> | undefined): string {\n\tconst url = new URL(baseUrl + path);\n\tif (query) {\n\t\tfor (const [key, value] of Object.entries(query)) {\n\t\t\tif (value === undefined || value === null) continue;\n\t\t\tif (Array.isArray(value)) {\n\t\t\t\tfor (const item of value) {\n\t\t\t\t\tif (item !== undefined && item !== null) {\n\t\t\t\t\t\turl.searchParams.append(key, String(item));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\turl.searchParams.append(key, String(value));\n\t\t\t}\n\t\t}\n\t}\n\treturn url.toString();\n}\n\nfunction generateRequestId(): string {\n\tconst cryptoApi: Crypto | undefined = globalThis.crypto;\n\tif (cryptoApi && typeof cryptoApi.randomUUID === 'function') {\n\t\treturn cryptoApi.randomUUID();\n\t}\n\treturn `req-${Math.random().toString(36).slice(2)}-${Date.now().toString(36)}`;\n}\n\nasync function safeProblem(response: Response): Promise<ProblemDetails> {\n\ttry {\n\t\tconst data = (await response.json()) as ProblemDetails;\n\t\tif (data && typeof data === 'object') return data;\n\t} catch {\n\t\t// fall through\n\t}\n\treturn {\n\t\ttype: 'about:blank',\n\t\ttitle: response.statusText || `HTTP ${response.status}`,\n\t\tstatus: response.status,\n\t\tdetail: '',\n\t\tinstance: '',\n\t\tcode: 'unknown'\n\t} satisfies ProblemDetails;\n}\n\nfunction shouldRetry(status: number): boolean {\n\treturn status === 429 || (status >= 500 && status < 600);\n}\n\nfunction parseRetryAfter(value: string): number | null {\n\tconst seconds = Number.parseInt(value, 10);\n\tif (Number.isFinite(seconds) && seconds >= 0) return seconds;\n\tconst date = Date.parse(value);\n\tif (Number.isFinite(date)) {\n\t\tconst diff = Math.max(0, Math.ceil((date - Date.now()) / 1000));\n\t\treturn diff;\n\t}\n\treturn null;\n}\n\nfunction backoffMs(attempt: number, retryAfterSeconds: number | null): number {\n\tif (retryAfterSeconds !== null) return retryAfterSeconds * 1000;\n\tconst base = 250;\n\tconst exp = base * 2 ** attempt;\n\tconst jitter = Math.random() * 100;\n\treturn Math.min(exp + jitter, 5_000);\n}\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function emit(\n\thook: OnRequestHook | undefined,\n\tevent: Parameters<OnRequestHook>[0]\n): Promise<void> {\n\tif (!hook) return;\n\ttry {\n\t\tawait hook(event);\n\t} catch {\n\t\t// telemetry must never break the caller\n\t}\n}\n"]}
|