@gamecore-api/sdk 0.26.0 → 0.26.2

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/AGENTS.md CHANGED
@@ -11,9 +11,9 @@ Browser- and Node-safe TypeScript client for the GameCore API — a multi-tenant
11
11
  BaaS for game-currency / digital-key storefronts. Zero runtime dependencies.
12
12
 
13
13
  ```ts
14
- import { GameCore } from "@gamecore-api/sdk";
14
+ import { GameCoreClient } from "@gamecore-api/sdk";
15
15
 
16
- const api = new GameCore({
16
+ const gc = new GameCoreClient({
17
17
  apiKey: "gc_live_…",
18
18
  baseUrl: "https://api.gamecore-api.tech",
19
19
  locale: "en", // ← drives Accept-Language; "ru" or "en"
@@ -36,15 +36,15 @@ share keys across tenants.
36
36
 
37
37
  | Namespace | Purpose | Read first |
38
38
  |---|---|---|
39
- | `api.catalog` | Games, products, search, categories, recommendations | `examples/01-quickstart.ts` |
40
- | `api.cart` | Server-synced cart for logged-in users | `examples/01-quickstart.ts` |
41
- | `api.checkout` | Create payment, get gateway redirect URL | `examples/01-quickstart.ts` |
42
- | `api.orders` | Order list, detail, status polling | `examples/03-error-handling.ts` |
43
- | `api.profile` | User profile, balance, address book | — |
44
- | `api.auth` | Telegram login, email/password, JWT refresh | — |
45
- | `api.favorites`, `api.coupons`, `api.referrals`, `api.reviews`, `api.topup`, `api.giftCards`, `api.announcements`, `api.analytics`, `api.seo` | Domain-specific endpoints | — |
46
- | `api.sse` | Server-Sent Events stream (order updates) | — |
47
- | `api.site` | Site config — homepage layout, footer, theme, modules | — |
39
+ | `gc.catalog` | Games, products, search, categories, recommendations | `examples/01-quickstart.ts` |
40
+ | `gc.cart` | Server-synced cart for logged-in users | `examples/01-quickstart.ts` |
41
+ | `gc.checkout` | Create payment, get gateway redirect URL | `examples/01-quickstart.ts` |
42
+ | `gc.orders` | Order list, detail, status polling | `examples/03-error-handling.ts` |
43
+ | `gc.profile` | User profile, balance, address book | — |
44
+ | `gc.auth` | Telegram login, email/password, JWT refresh | — |
45
+ | `gc.favorites`, `api.coupons`, `api.referrals`, `api.reviews`, `api.topup`, `api.giftCards`, `api.announcements`, `api.analytics`, `api.seo` | Domain-specific endpoints | — |
46
+ | `gc.sse` | Server-Sent Events stream (order updates) | — |
47
+ | `gc.site` | Site config — homepage layout, footer, theme, modules | — |
48
48
 
49
49
  Server-only helpers live under the `/server` entry point:
50
50
 
@@ -57,7 +57,7 @@ import { verifyWebhookSignature } from "@gamecore-api/sdk/server";
57
57
  ## Locale (since 0.25.0)
58
58
 
59
59
  - Pass `locale: "ru" | "en"` in constructor → SDK sends `Accept-Language` automatically on every request.
60
- - Override at runtime: `api.setLocale("en")` / `api.getLocale()`.
60
+ - Override at runtime: `gc.setLocale("en")` / `gc.getLocale()`.
61
61
  - The backend overlays **game names**, short descriptions, and descriptions in the requested locale. Slugs / IDs never change.
62
62
  - Original-language responses: just don't set `locale`.
63
63
 
@@ -9,15 +9,15 @@
9
9
  * backend (Next.js route, Express, ElysiaJS, etc.).
10
10
  */
11
11
 
12
- import { GameCore } from "@gamecore-api/sdk";
12
+ import { GameCoreClient } from "@gamecore-api/sdk";
13
13
 
14
- const api = new GameCore({
14
+ const gc = new GameCoreClient({
15
15
  apiKey: process.env.GAMECORE_API_KEY!,
16
16
  baseUrl: "https://api.gamecore-api.tech",
17
17
  });
18
18
 
19
19
  // 1. Browse the catalog (first 12 in-stock games)
20
- const { data: games } = await api.catalog.getGames({
20
+ const { data: games } = await gc.catalog.getGames({
21
21
  limit: 12,
22
22
  inStockOnly: true,
23
23
  });
@@ -26,36 +26,72 @@ console.log(
26
26
  games.map((g) => g.slug),
27
27
  );
28
28
 
29
- // 2. Open a game detail page — pick the first in-stock game from list
29
+ // 2. Open a game detail page — pick the first in-stock game from the list
30
30
  const game = games[0];
31
31
  if (!game) {
32
32
  console.log("No in-stock games for this site. Done.");
33
33
  process.exit(0);
34
34
  }
35
- const detail = await api.catalog.getGame(game.slug);
36
- const product = detail.products?.[0];
35
+ await gc.catalog.getGame(game.slug); // detail page lookup (returns GameDetail)
36
+
37
+ // 3. Pull products for that game — getGame returns metadata, not products.
38
+ const products = await gc.catalog.getProducts(game.slug);
39
+ const product = products[0];
37
40
  if (!product) {
38
41
  console.log(`Game ${game.slug} has no products. Done.`);
39
42
  process.exit(0);
40
43
  }
41
- console.log("picked product:", product.id, product.name);
44
+ console.log("picked product:", product.id, product.name, "price:", product.price);
42
45
 
43
- // 3. Create checkout (guest — pass email; no Telegram/VK login required).
46
+ // 4. Create checkout (guest — pass email; no Telegram/VK login required).
44
47
  // For balance-based payment, the user must be authenticated first.
45
- const checkout = await api.checkout.create({
46
- items: [{ productId: product.id, qty: 1, deliveryData: {} }],
47
- paymentMethod: "antilopay", // any gateway slug from getPaymentMethods()
48
+ //
49
+ // Item fields:
50
+ // productId — REQUIRED, the supplier product
51
+ // • gameId — REQUIRED (despite being typed optional). It's
52
+ // the game slug; the backend reads it directly to
53
+ // route Steam top-ups, SuperPass bundles, and
54
+ // Robux-via-pass through their special validation
55
+ // paths. A missing `gameId` crashes the request.
56
+ // • amount — quantity for fixed-price items (default 1) OR
57
+ // the top-up amount in local currency for Steam /
58
+ // variable-amount products.
59
+ // • deliveryData — product-specific fields (account name, server
60
+ // id, etc.). For simple gift cards this can be
61
+ // an empty object.
62
+ const checkout = await gc.checkout.create({
48
63
  email: "buyer@example.com",
64
+ items: [
65
+ {
66
+ productId: product.id,
67
+ gameId: game.slug,
68
+ amount: 1,
69
+ deliveryData: {},
70
+ },
71
+ ],
72
+ paymentMethod: "antilopay", // gateway slug from checkout.getPaymentMethods()
49
73
  });
50
- console.log("payment URL:", checkout.gatewayPaymentUrl);
51
- console.log("payment code:", checkout.paymentCode);
52
74
 
53
- // 4. Poll status until the user pays (or 5 minutes pass)
75
+ if (!checkout.payment) {
76
+ console.error("Checkout did not return a payment record:", checkout);
77
+ process.exit(1);
78
+ }
79
+ console.log("payment code:", checkout.payment.code);
80
+ console.log("payment URL:", checkout.payment.paymentUrl);
81
+
82
+ // 5. Poll status until the user pays (or 5 minutes pass)
54
83
  for (let i = 0; i < 30; i++) {
55
- const status = await api.checkout.getStatus(checkout.paymentCode);
84
+ const status = await gc.checkout.getStatus(checkout.payment.code);
56
85
  console.log(`[${i}] status=${status.payment.status}`);
57
- if (status.payment.status === "completed" || status.payment.status === "failed") {
58
- console.log("orders:", status.orders);
86
+ if (
87
+ status.payment.status === "completed" ||
88
+ status.payment.status === "failed" ||
89
+ status.payment.status === "cancelled"
90
+ ) {
91
+ console.log(
92
+ "orders:",
93
+ status.orders.map((o) => ({ code: o.code, status: o.status })),
94
+ );
59
95
  break;
60
96
  }
61
97
  await new Promise((r) => setTimeout(r, 10_000));
@@ -8,20 +8,20 @@
8
8
  * Three ways to pick a locale (highest priority first):
9
9
  * 1. ?locale=en on a per-request basis
10
10
  * 2. constructor option `locale: "en"` → adds Accept-Language header
11
- * 3. nothing → defaults to "ru"
11
+ * 3. nothing → API falls back to RU
12
12
  *
13
13
  * Run with:
14
14
  * GAMECORE_API_KEY=gc_live_... bun run examples/02-locale-switching.ts
15
15
  */
16
16
 
17
- import { GameCore } from "@gamecore-api/sdk";
17
+ import { GameCoreClient } from "@gamecore-api/sdk";
18
18
 
19
19
  const apiKey = process.env.GAMECORE_API_KEY!;
20
20
  const baseUrl = "https://api.gamecore-api.tech";
21
21
 
22
22
  // Approach A — one client per locale (simplest)
23
- const ru = new GameCore({ apiKey, baseUrl, locale: "ru" });
24
- const en = new GameCore({ apiKey, baseUrl, locale: "en" });
23
+ const ru = new GameCoreClient({ apiKey, baseUrl, locale: "ru" });
24
+ const en = new GameCoreClient({ apiKey, baseUrl, locale: "en" });
25
25
 
26
26
  const slug = "shp"; // a game we know has both RU and EN
27
27
  const ruDetail = await ru.catalog.getGame(slug);
@@ -32,20 +32,25 @@ console.log("EN short:", enDetail.shortDescription);
32
32
 
33
33
  // Approach B — single client, switch at runtime (useful for SSR
34
34
  // where the locale comes from the request headers).
35
- const api = new GameCore({ apiKey, baseUrl });
36
- api.setLocale("en");
37
- console.log("locale now:", api.getLocale());
38
- const homepage = await api.catalog.getHomepageGames();
35
+ const gc = new GameCoreClient({ apiKey, baseUrl });
36
+ gc.setLocale("en");
37
+ console.log("locale now:", gc.getLocale());
38
+ const homepage = await gc.catalog.getHomepageGames();
39
39
  console.log("homepage first:", homepage[0]?.name);
40
40
 
41
41
  // Approach C — per-call override. Useful when most traffic is one
42
42
  // locale but a specific page needs the other (e.g. an admin tool
43
43
  // rendering an EN preview from an otherwise-RU client).
44
- const { data: enGames } = await api.catalog.getGames({
44
+ const enGame = await gc.catalog.getGame(slug, "en");
45
+ console.log("per-call EN:", enGame.name);
46
+
47
+ // Approach D — locale on the paginated list endpoint. Same per-call
48
+ // override pattern but via a typed option.
49
+ const { data: enGames } = await gc.catalog.getGames({
45
50
  limit: 5,
46
51
  locale: "en",
47
52
  });
48
53
  console.log(
49
- "per-call EN:",
54
+ "per-call EN list:",
50
55
  enGames.map((g) => g.name),
51
56
  );
@@ -6,7 +6,7 @@
6
6
  * (machine-readable string set by the API for known cases).
7
7
  *
8
8
  * try {
9
- * await api.checkout.create(...);
9
+ * await gc.checkout.create(...);
10
10
  * } catch (e) {
11
11
  * if (e instanceof GameCoreError) {
12
12
  * if (e.status === 401) { /* re-auth *\/ }
@@ -19,9 +19,9 @@
19
19
  * GAMECORE_API_KEY=gc_live_... bun run examples/03-error-handling.ts
20
20
  */
21
21
 
22
- import { GameCore, GameCoreError } from "@gamecore-api/sdk";
22
+ import { GameCoreClient, GameCoreError } from "@gamecore-api/sdk";
23
23
 
24
- const api = new GameCore({
24
+ const gc = new GameCoreClient({
25
25
  apiKey: process.env.GAMECORE_API_KEY!,
26
26
  baseUrl: "https://api.gamecore-api.tech",
27
27
 
@@ -36,7 +36,7 @@ const api = new GameCore({
36
36
 
37
37
  // Force a 404 to demonstrate the error shape
38
38
  try {
39
- await api.catalog.getGame("definitely-not-a-real-game-slug-9999");
39
+ await gc.catalog.getGame("definitely-not-a-real-game-slug-9999");
40
40
  } catch (e) {
41
41
  if (e instanceof GameCoreError) {
42
42
  console.log("caught:", {
@@ -54,13 +54,13 @@ try {
54
54
  //
55
55
  // • 401 + code "UNAUTHORIZED" → API key invalid or revoked. Stop
56
56
  // the request, re-fetch a fresh key from your secret store.
57
- // • 401 + code "TOKEN_EXPIRED" → user session expired. Triggers
57
+ // • 410 + code "TOKEN_EXPIRED" → user JWT expired. Triggers
58
58
  // `onAuthError` if you provided one.
59
59
  // • 429 + code "RATE_LIMITED" → token bucket. Read the `Retry-After`
60
60
  // header if present, otherwise back off ~1s and retry.
61
61
  // • 400 with field-level details → check `message` for the offending
62
62
  // field (server returns plain-English reason for predictable cases
63
- // like "qty must be ≥ 1" or "delivery data missing for product X").
63
+ // like "amount must be ≥ 1" or "delivery data missing for product X").
64
64
 
65
65
  // Retry helper with exponential backoff for transient errors
66
66
  async function withRetry<T>(fn: () => Promise<T>, attempts = 3): Promise<T> {
@@ -79,5 +79,5 @@ async function withRetry<T>(fn: () => Promise<T>, attempts = 3): Promise<T> {
79
79
  throw lastError;
80
80
  }
81
81
 
82
- const games = await withRetry(() => api.catalog.getHomepageGames());
82
+ const games = await withRetry(() => gc.catalog.getHomepageGames());
83
83
  console.log("got", games.length, "games");
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 04 — Webhook verification (server-side only)
3
3
  *
4
- * GameCore sends webhooks (order.completed, payment.failed, etc.) to
4
+ * GameCore sends webhooks (order.completed, payment.received, etc.) to
5
5
  * a URL you register in the site-admin. Each request is signed with
6
6
  * HMAC-SHA256 using your webhook secret. ALWAYS verify before
7
7
  * trusting the body — without verification, an attacker who guesses
@@ -16,10 +16,24 @@
16
16
  * RAW body (don't let your framework parse it before you verify).
17
17
  */
18
18
 
19
- import { verifyWebhookSignature, parseWebhookPayload } from "@gamecore-api/sdk/server";
19
+ import {
20
+ parseWebhookPayload,
21
+ verifyWebhookSignature,
22
+ } from "@gamecore-api/sdk/server";
20
23
 
21
24
  // — Express example —
22
- import type { Request, Response } from "express";
25
+ //
26
+ // Type imports are inlined as `unknown` here so the example type-checks
27
+ // without `@types/express` in this package. In a real project, replace
28
+ // with `import type { Request, Response } from "express"`.
29
+ type Request = {
30
+ body: string | Buffer;
31
+ header(name: string): string | undefined;
32
+ };
33
+ type Response = {
34
+ status(code: number): Response;
35
+ send(body: string): Response;
36
+ };
23
37
 
24
38
  const WEBHOOK_SECRET = process.env.GAMECORE_WEBHOOK_SECRET!; // from site-admin
25
39
 
@@ -45,17 +59,27 @@ export async function handleWebhook(req: Request, res: Response) {
45
59
  return;
46
60
  }
47
61
 
48
- const event = parseWebhookPayload(rawBody);
49
- switch (event.type) {
50
- case "order.completed":
51
- console.log("order delivered:", event.data.orderCode);
52
- // e.g. flip your internal order state, send a fulfillment email, etc.
62
+ const payload = parseWebhookPayload(rawBody);
63
+ // payload.event is the WebhookEvent union; payload.data is
64
+ // `Record<string, unknown>` because the shape varies per event.
65
+ // Cast inside each branch when you've checked the discriminant.
66
+ switch (payload.event) {
67
+ case "order.completed": {
68
+ const data = payload.data as { orderCode?: string };
69
+ console.log("order delivered:", data.orderCode);
70
+ break;
71
+ }
72
+ case "payment.received": {
73
+ const data = payload.data as { paymentCode?: string; totalAmount?: number };
74
+ console.log("payment received:", data.paymentCode, data.totalAmount);
53
75
  break;
54
- case "payment.completed":
55
- console.log("payment received:", event.data.paymentCode);
76
+ }
77
+ case "order.failed":
78
+ case "order.cancelled":
79
+ console.log("order ended:", payload.event, payload.data);
56
80
  break;
57
81
  default:
58
- console.log("unhandled event:", event.type);
82
+ console.log("unhandled event:", payload.event);
59
83
  }
60
84
 
61
85
  res.status(200).send("ok");
@@ -72,8 +96,8 @@ export async function handleWebhook(req: Request, res: Response) {
72
96
  // if (!verifyWebhookSignature(raw, sig, WEBHOOK_SECRET)) {
73
97
  // return new Response("invalid", { status: 401 });
74
98
  // }
75
- // const event = parseWebhookPayload(raw);
76
- // // ... handle event
99
+ // const payload = parseWebhookPayload(raw);
100
+ // // ... switch on payload.event
77
101
  // return new Response("ok");
78
102
  // }
79
103
  // return new Response("not found", { status: 404 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gamecore-api/sdk",
3
- "version": "0.26.0",
3
+ "version": "0.26.2",
4
4
  "description": "TypeScript SDK for GameCore API — browser-safe, zero dependencies",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -19,7 +19,8 @@
19
19
  "files": ["dist", "examples", "AGENTS.md"],
20
20
  "scripts": {
21
21
  "build": "bun build src/index.ts --outdir dist --target browser && bun build src/server.ts --outdir dist --target node && bun x tsc --emitDeclarationOnly",
22
- "typecheck": "bun x tsc --noEmit"
22
+ "typecheck": "bun x tsc --noEmit",
23
+ "typecheck:examples": "bun x tsc --project tsconfig.examples.json"
23
24
  },
24
25
  "devDependencies": {
25
26
  "@types/node": "^25.5.0",