@quizbase/client 0.4.0 → 0.5.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @quizbase/client
2
2
 
3
- TypeScript SDK for the [QuizBase API](https://quizbase.runriva.com) — multilingual trivia API with 1.4M+ quiz-ready questions blended from 11 open-licensed sources, English and Polish at launch.
3
+ TypeScript SDK for the [QuizBase API](https://quizbase.runriva.com) — a multilingual trivia API. 1.4M+ blended, deduped questions from 11 open datasets (CC, MIT), English and Polish at launch. Per-record license, author, and source on every response.
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/@quizbase/client.svg)](https://www.npmjs.com/package/@quizbase/client)
6
6
  [![types](https://img.shields.io/npm/types/@quizbase/client.svg)](https://www.npmjs.com/package/@quizbase/client)
@@ -28,7 +28,7 @@ const client = createClient({
28
28
  });
29
29
 
30
30
  const random = await client.questions.random({ category: 'science-and-nature', lang: 'en' });
31
- console.log(random.data.question);
31
+ console.log(random.data[0].text);
32
32
  ```
33
33
 
34
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).
@@ -58,6 +58,8 @@ client.report.create({ questionId, kind, comment });
58
58
 
59
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).
60
60
 
61
+ > **Tip:** every question has a stable UUID `id` that never changes. Save it client-side and compose it into multi-language quizzes, daily challenges, anti-cheat, and more — see [Client patterns](#client-patterns-with-stable-ids) below.
62
+
61
63
  ## Pagination
62
64
 
63
65
  `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.
@@ -76,6 +78,58 @@ for await (const page of client.topics.pages({ lang: 'en' })) {
76
78
 
77
79
  Need manual control? `list()` still exposes `_links.next` and a `cursor` query param.
78
80
 
81
+ ## Client patterns with stable IDs
82
+
83
+ Every question carries a stable UUID v7 `id` that never changes after import. The SDK is intentionally **thin** — no `?seed=`, no `?daily=`, no built-in deduplication. The stable id is the primitive you compose into mechanics. Six patterns cover most real apps:
84
+
85
+ ### Same question across languages
86
+
87
+ ```ts
88
+ const en = await client.questions.random({ lang: 'en' });
89
+ const id = en.data[0].id;
90
+ const pl = await client.questions.get(id, { lang: 'pl' });
91
+ const es = await client.questions.get(id, { lang: 'es' });
92
+ // Same canonical question, three languages — UUID is the link.
93
+ ```
94
+
95
+ ### Don't repeat — exclude seen questions
96
+
97
+ ```ts
98
+ const seen = new Set<string>();
99
+ const batch = await client.questions.random({
100
+ category: 'science-and-nature',
101
+ amount: 20,
102
+ exclude: [...seen]
103
+ });
104
+ batch.data.forEach((q) => seen.add(q.id));
105
+ ```
106
+
107
+ `exclude` accepts up to 250 UUIDs. For longer histories keep `seen` client-side and filter after the fetch.
108
+
109
+ ### Daily challenge — same question for everyone today
110
+
111
+ ```ts
112
+ const day = new Date().toISOString().slice(0, 10); // "2026-05-19"
113
+ const idx = [...day].reduce((h, c) => (h * 31 + c.charCodeAt(0)) | 0, 7);
114
+ const pool = await client.questions.list({ category: 'history', limit: 50 });
115
+ const todays = pool.data[Math.abs(idx) % pool.data.length];
116
+ // Persist `todays.id` server-side so retries serve the same question.
117
+ ```
118
+
119
+ ### Multiplayer sync — both players see the same question
120
+
121
+ Your matchmaker picks one `id` (via `random` or `list`), broadcasts to both clients, and both clients call `questions.get(id, { lang })`. Same UUID, per-player language preference. Zero state on QuizBase — your matchmaker owns the round.
122
+
123
+ ### Server-side anti-cheat — never ship `correctAnswer` to the client
124
+
125
+ Use `qb_sk_*` (secret key) from your server to fetch full questions including `correctAnswer`. Forward only `id` + `text` + a shuffled `[correctAnswer, ...incorrectAnswers]` to the client. Validate the submitted answer server-side by re-fetching with the same `id`. Browsers cannot reach `qb_sk_*` — CORS blocks it.
126
+
127
+ ### Stable Anki / Quizlet flashcards across updates
128
+
129
+ Save `{id, lang}` as your card key. When the upstream question changes (typo fix, distractor swap, translation refresh), `questions.get(id, { lang })` returns the updated content under the same id — your card auto-updates without re-import. Soft-deleted questions return 404; treat that as "card removed upstream".
130
+
131
+ Full code samples and edge cases at [/docs/api/questions-by-id § What you can do with a stable id](https://quizbase.runriva.com/docs/api/questions-by-id). For MCP agents, the same playbook is exposed as the `client_mechanics_patterns` prompt.
132
+
79
133
  ## Errors
80
134
 
81
135
  Every non-2xx response throws `QuizbaseError` carrying the parsed [RFC 9457 Problem Details](https://www.rfc-editor.org/rfc/rfc9457):
@@ -164,7 +218,7 @@ All responses are typed from the OpenAPI spec. Hover over `client.questions.rand
164
218
  - **GitHub:** [maciejdzierzek/quizbase-sdk-ts](https://github.com/maciejdzierzek/quizbase-sdk-ts)
165
219
  - **Issues / feedback:** [GitHub Issues](https://github.com/maciejdzierzek/quizbase-sdk-ts/issues)
166
220
  - **Releases:** automated by [release-please](https://github.com/googleapis/release-please) from conventional commits
167
- - **Drift guard:** the main repo [`tests/api/sdk-contract.spec.ts`](https://github.com/maciejdzierzek/quizbase) verifies the latest published SDK still matches `/openapi.json`
221
+ - **Drift guard:** every release is verified against the live OpenAPI spec at [quizbase.runriva.com/openapi.json](https://quizbase.runriva.com/openapi.json) types reflect the current API surface.
168
222
 
169
223
  ## License
170
224
 
package/dist/index.d.cts CHANGED
@@ -857,9 +857,10 @@ interface paths {
857
857
  topic?: string;
858
858
  topics_any?: string;
859
859
  subcategory?: string;
860
- quality?: "high";
860
+ /** @description Quality preset: "high" (default) skips needs_review=true; "all" includes everything */
861
+ quality?: "high" | "all";
861
862
  regions?: string;
862
- source?: "opentdb" | "opentriviaqa" | "mkqa" | "mmlu-prox" | "wikipedia" | "community" | "runriva" | "ai-generated" | "purchased" | "quizbase";
863
+ source?: "arc" | "creak" | "entityq" | "kqa-pro" | "mintaka" | "mkqa" | "nq-open" | "opentdb" | "opentriviaqa" | "qasc" | "quizbase" | "webq";
863
864
  license?: "CC-BY-SA-4.0" | "CC-BY-SA-3.0" | "CC-BY-4.0" | "MIT" | "proprietary";
864
865
  count?: "estimate" | "exact" | "none";
865
866
  };
@@ -958,9 +959,10 @@ interface paths {
958
959
  topic?: string;
959
960
  topics_any?: string;
960
961
  subcategory?: string;
961
- quality?: "high";
962
+ /** @description Quality preset: "high" (default) skips needs_review=true; "all" includes everything */
963
+ quality?: "high" | "all";
962
964
  regions?: string;
963
- source?: "opentdb" | "opentriviaqa" | "mkqa" | "mmlu-prox" | "wikipedia" | "community" | "runriva" | "ai-generated" | "purchased" | "quizbase";
965
+ source?: "arc" | "creak" | "entityq" | "kqa-pro" | "mintaka" | "mkqa" | "nq-open" | "opentdb" | "opentriviaqa" | "qasc" | "quizbase" | "webq";
964
966
  license?: "CC-BY-SA-4.0" | "CC-BY-SA-3.0" | "CC-BY-4.0" | "MIT" | "proprietary";
965
967
  exclude?: string;
966
968
  };
@@ -1213,6 +1215,12 @@ interface components {
1213
1215
  */
1214
1216
  regions: string[];
1215
1217
  attribution: components["schemas"]["Attribution"];
1218
+ /**
1219
+ * @description Quality flag. Present only when the client requested non-default content via `?quality=all` (REST) or `quality: "all"` (MCP) — and always on `/api/v1/questions/{id}` deep-link responses. For default `?quality=high` all returned questions are by definition `high`, so the field is omitted to avoid redundancy. `needs_review` = flagged for moderation review (low-quality distractors, trivial answer-in-question patterns).
1220
+ * @example high
1221
+ * @enum {string}
1222
+ */
1223
+ quality?: "high" | "needs_review";
1216
1224
  /**
1217
1225
  * Format: uuid
1218
1226
  * @description Direct parent (usually English source) when this is a translation.
package/dist/index.d.ts CHANGED
@@ -857,9 +857,10 @@ interface paths {
857
857
  topic?: string;
858
858
  topics_any?: string;
859
859
  subcategory?: string;
860
- quality?: "high";
860
+ /** @description Quality preset: "high" (default) skips needs_review=true; "all" includes everything */
861
+ quality?: "high" | "all";
861
862
  regions?: string;
862
- source?: "opentdb" | "opentriviaqa" | "mkqa" | "mmlu-prox" | "wikipedia" | "community" | "runriva" | "ai-generated" | "purchased" | "quizbase";
863
+ source?: "arc" | "creak" | "entityq" | "kqa-pro" | "mintaka" | "mkqa" | "nq-open" | "opentdb" | "opentriviaqa" | "qasc" | "quizbase" | "webq";
863
864
  license?: "CC-BY-SA-4.0" | "CC-BY-SA-3.0" | "CC-BY-4.0" | "MIT" | "proprietary";
864
865
  count?: "estimate" | "exact" | "none";
865
866
  };
@@ -958,9 +959,10 @@ interface paths {
958
959
  topic?: string;
959
960
  topics_any?: string;
960
961
  subcategory?: string;
961
- quality?: "high";
962
+ /** @description Quality preset: "high" (default) skips needs_review=true; "all" includes everything */
963
+ quality?: "high" | "all";
962
964
  regions?: string;
963
- source?: "opentdb" | "opentriviaqa" | "mkqa" | "mmlu-prox" | "wikipedia" | "community" | "runriva" | "ai-generated" | "purchased" | "quizbase";
965
+ source?: "arc" | "creak" | "entityq" | "kqa-pro" | "mintaka" | "mkqa" | "nq-open" | "opentdb" | "opentriviaqa" | "qasc" | "quizbase" | "webq";
964
966
  license?: "CC-BY-SA-4.0" | "CC-BY-SA-3.0" | "CC-BY-4.0" | "MIT" | "proprietary";
965
967
  exclude?: string;
966
968
  };
@@ -1213,6 +1215,12 @@ interface components {
1213
1215
  */
1214
1216
  regions: string[];
1215
1217
  attribution: components["schemas"]["Attribution"];
1218
+ /**
1219
+ * @description Quality flag. Present only when the client requested non-default content via `?quality=all` (REST) or `quality: "all"` (MCP) — and always on `/api/v1/questions/{id}` deep-link responses. For default `?quality=high` all returned questions are by definition `high`, so the field is omitted to avoid redundancy. `needs_review` = flagged for moderation review (low-quality distractors, trivial answer-in-question patterns).
1220
+ * @example high
1221
+ * @enum {string}
1222
+ */
1223
+ quality?: "high" | "needs_review";
1216
1224
  /**
1217
1225
  * Format: uuid
1218
1226
  * @description Direct parent (usually English source) when this is a translation.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quizbase/client",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "TypeScript SDK for QuizBase API — typed client, retry, RFC 9457 errors, telemetry hook. Generated from openapi.json.",
5
5
  "license": "MIT",
6
6
  "author": "Maciej Dzierżek",