scorezilla 0.1.0-next.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/API.md +305 -0
- package/CHANGELOG.md +24 -0
- package/COMPATIBILITY.md +170 -0
- package/LICENSE +21 -0
- package/README.md +212 -0
- package/VERSIONING.md +119 -0
- package/dist/index.cjs +686 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +557 -0
- package/dist/index.d.ts +557 -0
- package/dist/index.js +680 -0
- package/dist/index.js.map +1 -0
- package/dist/phaser.cjs +6 -0
- package/dist/phaser.cjs.map +1 -0
- package/dist/phaser.d.cts +2 -0
- package/dist/phaser.d.ts +2 -0
- package/dist/phaser.js +4 -0
- package/dist/phaser.js.map +1 -0
- package/dist/react.cjs +6 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +2 -0
- package/dist/react.d.ts +2 -0
- package/dist/react.js +4 -0
- package/dist/react.js.map +1 -0
- package/dist/server-browser-stub.cjs +8 -0
- package/dist/server-browser-stub.cjs.map +1 -0
- package/dist/server-browser-stub.d.cts +2 -0
- package/dist/server-browser-stub.d.ts +2 -0
- package/dist/server-browser-stub.js +6 -0
- package/dist/server-browser-stub.js.map +1 -0
- package/dist/server.cjs +8 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +2 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +6 -0
- package/dist/server.js.map +1 -0
- package/package.json +142 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
/** Minimal fetch shape — broader than `typeof fetch` so polyfills and
|
|
2
|
+
* test stubs (`vi.fn()`, `node-fetch`, etc.) typecheck cleanly. */
|
|
3
|
+
type FetchImpl = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* SDK configuration.
|
|
7
|
+
*
|
|
8
|
+
* The `ScorezillaConfig` is a TypeScript-level discriminated union of
|
|
9
|
+
* `PublicKeyConfig` and `SecretKeyConfig`. The mutual-exclusivity (you may
|
|
10
|
+
* pass `publicKey` OR `secretKey`, never both) is enforced at compile time:
|
|
11
|
+
* passing both fields fails type-checking before the runtime check fires.
|
|
12
|
+
*
|
|
13
|
+
* The runtime check in {@link validateConfig} is the second line of defense
|
|
14
|
+
* for consumers using plain JS or `as any` casts.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** Shared options across both auth modes. */
|
|
18
|
+
interface BaseConfig {
|
|
19
|
+
/** API base URL (no trailing slash required). Defaults to {@link DEFAULT_BASE_URL}. */
|
|
20
|
+
baseUrl?: string;
|
|
21
|
+
/** Custom fetch implementation — defaults to `globalThis.fetch`. Pass
|
|
22
|
+
* `node-fetch`, `undici`, or a mock here. The explicit signature
|
|
23
|
+
* (`(RequestInfo | URL, init?) => Promise<Response>`) is broader than
|
|
24
|
+
* `typeof fetch` so common polyfills typecheck cleanly. */
|
|
25
|
+
fetch?: FetchImpl;
|
|
26
|
+
/** Per-request timeout in milliseconds. Defaults to 30 s. */
|
|
27
|
+
timeoutMs?: number;
|
|
28
|
+
/** Maximum retry attempts on transient failures. Defaults to 2 (so the
|
|
29
|
+
* worst-case total request count is 3). */
|
|
30
|
+
maxRetries?: number;
|
|
31
|
+
/** Override the default `User-Agent` header (Node/Workers/Bun/Deno —
|
|
32
|
+
* browsers silently ignore the value). */
|
|
33
|
+
userAgent?: string;
|
|
34
|
+
}
|
|
35
|
+
/** Public-key auth: browser-safe path. The key is fingerprinted to a game
|
|
36
|
+
* on the server side via `pk_<gameSlug>_<base62>`. */
|
|
37
|
+
type PublicKeyConfig = BaseConfig & {
|
|
38
|
+
publicKey: string;
|
|
39
|
+
secretKey?: never;
|
|
40
|
+
};
|
|
41
|
+
/** Secret-key auth: server-side HMAC. The pair `{ id, secret }` is what
|
|
42
|
+
* the operator dashboard issues; the SDK signs requests with `secret` and
|
|
43
|
+
* identifies them via `id`. */
|
|
44
|
+
type SecretKeyConfig = BaseConfig & {
|
|
45
|
+
secretKey: {
|
|
46
|
+
id: string;
|
|
47
|
+
secret: string;
|
|
48
|
+
};
|
|
49
|
+
publicKey?: never;
|
|
50
|
+
};
|
|
51
|
+
/** The top-level config type. The union is open for additional auth modes
|
|
52
|
+
* in future major releases. */
|
|
53
|
+
type ScorezillaConfig = PublicKeyConfig | SecretKeyConfig;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Wire types for the Scorezilla API at /v1.
|
|
57
|
+
*
|
|
58
|
+
* Mirrors the documented response shapes. TypeScript's structural typing
|
|
59
|
+
* means additional fields the server adds in a minor release won't break
|
|
60
|
+
* consumers — see VERSIONING.md for the full SemVer contract.
|
|
61
|
+
*
|
|
62
|
+
* No Zod or other runtime validators in v0.1.0 — keeps the bundle small.
|
|
63
|
+
* Narrow untrusted input with the {@link isApiSuccess} / {@link isApiError}
|
|
64
|
+
* type guards exported below.
|
|
65
|
+
*/
|
|
66
|
+
/**
|
|
67
|
+
* Machine-stable error codes returned by the API.
|
|
68
|
+
*
|
|
69
|
+
* Consumers MUST branch on this `code` rather than the human-readable
|
|
70
|
+
* `message` — message text is English-only and explicitly NOT part of the
|
|
71
|
+
* SemVer contract.
|
|
72
|
+
*
|
|
73
|
+
* The union is intentionally open (`| (string & {})`) so unknown future
|
|
74
|
+
* codes from a server-side minor release don't compile-error against the
|
|
75
|
+
* SDK. The trick preserves autocomplete on the known set while permitting
|
|
76
|
+
* arbitrary strings at runtime — see
|
|
77
|
+
* https://github.com/microsoft/TypeScript/issues/29729 for the pattern.
|
|
78
|
+
*
|
|
79
|
+
* @stable v0.1.0
|
|
80
|
+
*/
|
|
81
|
+
type ScorezillaErrorCode = 'unauthorized' | 'forbidden' | 'not_found' | 'invalid_input' | 'invalid_json' | 'out_of_bounds' | 'rate_limited' | 'conflict' | 'internal_error' | (string & {});
|
|
82
|
+
/** Reason sub-classifier on `out_of_bounds` errors. Open union — see {@link ScorezillaErrorCode}. */
|
|
83
|
+
type OutOfBoundsReason = 'below_min' | 'above_max' | (string & {});
|
|
84
|
+
/** Successful API response envelope. The `T` is the per-route payload. */
|
|
85
|
+
type ApiSuccess<T> = {
|
|
86
|
+
ok: true;
|
|
87
|
+
} & T;
|
|
88
|
+
/** Failure response envelope. The server returns this on every non-2xx response. */
|
|
89
|
+
interface ApiError {
|
|
90
|
+
ok: false;
|
|
91
|
+
error: ScorezillaErrorCode;
|
|
92
|
+
/** Human-readable, English only. Not machine-stable — branch on `error` and `reason`. */
|
|
93
|
+
message?: string;
|
|
94
|
+
/** Sub-classifier — used by `out_of_bounds` (`'below_min' | 'above_max'`). */
|
|
95
|
+
reason?: string;
|
|
96
|
+
/** Seconds — present on `rate_limited`. Also mirrored in the HTTP `Retry-After` header. */
|
|
97
|
+
retryAfter?: number;
|
|
98
|
+
/** Which rate-limit layer fired — present on `rate_limited`. */
|
|
99
|
+
layer?: string;
|
|
100
|
+
/** The limit value that was crossed — present on `out_of_bounds`. */
|
|
101
|
+
bound?: number;
|
|
102
|
+
}
|
|
103
|
+
/** Discriminated envelope: every API response is one of these two shapes. */
|
|
104
|
+
type ApiResponse<T> = ApiSuccess<T> | ApiError;
|
|
105
|
+
/**
|
|
106
|
+
* A single ranked entry on a leaderboard.
|
|
107
|
+
*
|
|
108
|
+
* Returned as an array on `leaderboard` and `window-around` responses, and
|
|
109
|
+
* inline on `playerRank` (without the `rank` wrapper — see
|
|
110
|
+
* {@link PlayerRankResponse}).
|
|
111
|
+
*/
|
|
112
|
+
interface RankedEntry {
|
|
113
|
+
/** 1-based rank. */
|
|
114
|
+
rank: number;
|
|
115
|
+
playerId: string;
|
|
116
|
+
score: number;
|
|
117
|
+
/** Milliseconds since epoch. */
|
|
118
|
+
submittedAt: number;
|
|
119
|
+
metadata?: Record<string, unknown>;
|
|
120
|
+
}
|
|
121
|
+
/** Payload from `POST /v1/boards/:boardId/scores`. */
|
|
122
|
+
interface SubmitScoreResponse {
|
|
123
|
+
boardId: string;
|
|
124
|
+
/** The key ID that authorized the submission. Useful for consumer-side audit. */
|
|
125
|
+
keyId: string;
|
|
126
|
+
/** 1-based rank after the submit settled. */
|
|
127
|
+
rank: number;
|
|
128
|
+
totalEntries: number;
|
|
129
|
+
isPersonalBest: boolean;
|
|
130
|
+
}
|
|
131
|
+
/** Payload from `GET /v1/boards/:boardId/leaderboard`. */
|
|
132
|
+
interface LeaderboardResponse {
|
|
133
|
+
boardId: string;
|
|
134
|
+
offset: number;
|
|
135
|
+
limit: number;
|
|
136
|
+
entries: RankedEntry[];
|
|
137
|
+
}
|
|
138
|
+
/** Payload from `GET /v1/boards/:boardId/players/:playerId/rank`. */
|
|
139
|
+
interface PlayerRankResponse {
|
|
140
|
+
boardId: string;
|
|
141
|
+
playerId: string;
|
|
142
|
+
rank: number;
|
|
143
|
+
score: number;
|
|
144
|
+
submittedAt: number;
|
|
145
|
+
totalEntries: number;
|
|
146
|
+
}
|
|
147
|
+
/** Payload from `GET /v1/boards/:boardId/players/:playerId/window`. */
|
|
148
|
+
interface WindowAroundResponse {
|
|
149
|
+
boardId: string;
|
|
150
|
+
playerId: string;
|
|
151
|
+
before: number;
|
|
152
|
+
after: number;
|
|
153
|
+
entries: RankedEntry[];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* SDK error type.
|
|
158
|
+
*
|
|
159
|
+
* Every non-2xx API response is normalized into a `ScorezillaError` instance
|
|
160
|
+
* by the transport layer. Network failures and timeouts surface as the same
|
|
161
|
+
* class (with `status: 0`) so callers have a single error type to catch.
|
|
162
|
+
*
|
|
163
|
+
* **Invariant — consumers MUST branch on `code` (and optionally `reason`),
|
|
164
|
+
* never on `message`.** The English-language `message` is for operator
|
|
165
|
+
* logging only and is explicitly **not** part of the SemVer contract; a
|
|
166
|
+
* minor release MAY reword any message. Machine logic that depends on
|
|
167
|
+
* message text will break silently across upgrades.
|
|
168
|
+
*/
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Options for {@link ScorezillaError.from}.
|
|
172
|
+
*
|
|
173
|
+
* The fields mirror what's available after a fetch round-trip: the HTTP
|
|
174
|
+
* status, the parsed JSON body (if any), the request ID from
|
|
175
|
+
* `X-Request-Id`, and an optional `cause` for the underlying
|
|
176
|
+
* network/abort error.
|
|
177
|
+
*/
|
|
178
|
+
interface ScorezillaErrorFromInit {
|
|
179
|
+
status: number;
|
|
180
|
+
body?: ApiError | undefined;
|
|
181
|
+
requestId?: string | undefined;
|
|
182
|
+
cause?: unknown;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Thrown by the SDK for every failure path — non-2xx responses, network
|
|
186
|
+
* errors, aborts, and timeouts.
|
|
187
|
+
*
|
|
188
|
+
* Cross-realm `instanceof` is guaranteed: the class sets `Error.prototype`
|
|
189
|
+
* explicitly so checks survive iframe / worker boundaries.
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```ts
|
|
193
|
+
* try {
|
|
194
|
+
* await sz.submitScore({ boardId, playerId, score });
|
|
195
|
+
* } catch (e) {
|
|
196
|
+
* if (!(e instanceof ScorezillaError)) throw e;
|
|
197
|
+
*
|
|
198
|
+
* if (e.isRateLimited()) {
|
|
199
|
+
* await sleep((e.retryAfter ?? 30) * 1000);
|
|
200
|
+
* return retry();
|
|
201
|
+
* }
|
|
202
|
+
* if (e.code === 'out_of_bounds') {
|
|
203
|
+
* console.warn(`Score crosses ${e.reason} bound (limit ${e.bound})`);
|
|
204
|
+
* return;
|
|
205
|
+
* }
|
|
206
|
+
* if (e.isAuth()) throw new Error('SDK misconfigured — bad publicKey');
|
|
207
|
+
*
|
|
208
|
+
* // Anything else: surface to your reporter with requestId for support.
|
|
209
|
+
* console.error(`Scorezilla ${e.code} (${e.status}) — request ${e.requestId}`);
|
|
210
|
+
* throw e;
|
|
211
|
+
* }
|
|
212
|
+
* ```
|
|
213
|
+
*
|
|
214
|
+
* @since 0.1.0
|
|
215
|
+
* @stability stable
|
|
216
|
+
*/
|
|
217
|
+
declare class ScorezillaError extends Error {
|
|
218
|
+
/** HTTP status of the response, or {@link STATUS_NETWORK_ERROR} (0) for
|
|
219
|
+
* network / abort / timeout. */
|
|
220
|
+
readonly status: number;
|
|
221
|
+
/** Machine-stable error code from the API. Open union — see
|
|
222
|
+
* {@link ScorezillaErrorCode}. For network errors, this is `'network_error'`;
|
|
223
|
+
* for aborts, `'aborted'`; for timeouts, `'timeout'`. */
|
|
224
|
+
readonly code: ScorezillaErrorCode;
|
|
225
|
+
/** Sub-classifier — present on `out_of_bounds` (`'below_min' | 'above_max'`)
|
|
226
|
+
* and possibly other codes in future minor releases. */
|
|
227
|
+
readonly reason: OutOfBoundsReason | string | undefined;
|
|
228
|
+
/** Seconds — present on `rate_limited`. Honored by the transport's retry
|
|
229
|
+
* policy (Step 2.4). */
|
|
230
|
+
readonly retryAfter: number | undefined;
|
|
231
|
+
/** Server-issued request ID, lifted from the `X-Request-Id` response
|
|
232
|
+
* header. Pass this to support when filing bugs. */
|
|
233
|
+
readonly requestId: string | undefined;
|
|
234
|
+
/** The bound value crossed on `out_of_bounds`. */
|
|
235
|
+
readonly bound: number | undefined;
|
|
236
|
+
/** Which rate-limit layer fired on `rate_limited`. */
|
|
237
|
+
readonly layer: string | undefined;
|
|
238
|
+
/** The underlying cause (e.g., a `TypeError: fetch failed`) for
|
|
239
|
+
* network/abort/timeout paths. `undefined` when the error came from a
|
|
240
|
+
* successfully-parsed API error body. */
|
|
241
|
+
readonly cause: unknown;
|
|
242
|
+
constructor(message: string, init: {
|
|
243
|
+
status: number;
|
|
244
|
+
code: ScorezillaErrorCode;
|
|
245
|
+
reason?: string | undefined;
|
|
246
|
+
retryAfter?: number | undefined;
|
|
247
|
+
requestId?: string | undefined;
|
|
248
|
+
bound?: number | undefined;
|
|
249
|
+
layer?: string | undefined;
|
|
250
|
+
cause?: unknown;
|
|
251
|
+
});
|
|
252
|
+
/** `true` when this error is a 429 / `rate_limited`. */
|
|
253
|
+
isRateLimited(): boolean;
|
|
254
|
+
/** `true` when this error is a 401 / `unauthorized` (or 403 / `forbidden`). */
|
|
255
|
+
isAuth(): boolean;
|
|
256
|
+
/** `true` when this error is a 404 / `not_found`. */
|
|
257
|
+
isNotFound(): boolean;
|
|
258
|
+
/** `true` when this error is a 422 / `out_of_bounds` (score below/above board limit). */
|
|
259
|
+
isOutOfBounds(): boolean;
|
|
260
|
+
/** `true` for transient / retryable conditions: network errors, timeouts,
|
|
261
|
+
* 5xx, and 429. The transport layer relies on this for its retry policy. */
|
|
262
|
+
isTransient(): boolean;
|
|
263
|
+
/**
|
|
264
|
+
* Build a `ScorezillaError` from a fetch round-trip outcome.
|
|
265
|
+
*
|
|
266
|
+
* Prefer this over `new ScorezillaError(...)` from the transport layer —
|
|
267
|
+
* it does the mapping from API response shape to error fields in one
|
|
268
|
+
* place, so future fields like `correlationId` get added once here.
|
|
269
|
+
*
|
|
270
|
+
* @param init - status, optional parsed body, optional requestId, optional cause
|
|
271
|
+
*/
|
|
272
|
+
static from(init: ScorezillaErrorFromInit): ScorezillaError;
|
|
273
|
+
/**
|
|
274
|
+
* Build a `ScorezillaError` for a transport-level failure (no HTTP
|
|
275
|
+
* response received): network error, abort, or timeout.
|
|
276
|
+
*/
|
|
277
|
+
static network(message: string, cause: unknown): ScorezillaError;
|
|
278
|
+
/** Build a `ScorezillaError` for an `AbortSignal`-triggered cancellation. */
|
|
279
|
+
static aborted(cause: unknown): ScorezillaError;
|
|
280
|
+
/** Build a `ScorezillaError` for a request that exceeded its timeout budget. */
|
|
281
|
+
static timeout(timeoutMs: number): ScorezillaError;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* `Scorezilla` — the public-key client for v0.1.0.
|
|
286
|
+
*
|
|
287
|
+
* Composes the substrate modules (transport, retry, errors, config,
|
|
288
|
+
* paths, user-agent) into the four documented public methods:
|
|
289
|
+
*
|
|
290
|
+
* • {@link Scorezilla.submitScore}
|
|
291
|
+
* • {@link Scorezilla.getLeaderboard}
|
|
292
|
+
* • {@link Scorezilla.getPlayerRank}
|
|
293
|
+
* • {@link Scorezilla.getWindowAround}
|
|
294
|
+
*
|
|
295
|
+
* Every method returns a parsed, typed response on the success path and
|
|
296
|
+
* throws {@link ScorezillaError} on every failure path (HTTP non-2xx,
|
|
297
|
+
* network failures, aborts, timeouts, invalid JSON).
|
|
298
|
+
*
|
|
299
|
+
* **Input contract — `playerId` only.** The SDK does NOT accept a `player`
|
|
300
|
+
* alias. TypeScript discriminated unions that differ only in key name
|
|
301
|
+
* produce no narrowing benefit and create friction for callers spreading
|
|
302
|
+
* existing types. v0.1.0 is the moment to be strict.
|
|
303
|
+
*
|
|
304
|
+
* **Auth — v0.1.0 is public-key only.** A `ScorezillaConfig` with
|
|
305
|
+
* `secretKey` is accepted by `validateConfig` (since the type union covers
|
|
306
|
+
* both kinds) but the constructor throws a clear `Error` pointing to the
|
|
307
|
+
* future `scorezilla/server` adapter (v0.2.0). This prevents accidental
|
|
308
|
+
* browser leakage of secret keys via the public-key surface.
|
|
309
|
+
*/
|
|
310
|
+
|
|
311
|
+
/** Input for {@link Scorezilla.submitScore}. */
|
|
312
|
+
interface SubmitScoreInput {
|
|
313
|
+
/** UUID-typed board identifier — issued by the operator dashboard. */
|
|
314
|
+
boardId: string;
|
|
315
|
+
/** The SDK accepts ONLY `playerId`. Pass your stable per-player identifier
|
|
316
|
+
* (UUID, account id, anonymous-session id). Avoid PII. */
|
|
317
|
+
playerId: string;
|
|
318
|
+
/** Finite number. The API rejects NaN, Infinity, and values outside the
|
|
319
|
+
* board's configured `[minScore, maxScore]` range with `out_of_bounds`. */
|
|
320
|
+
score: number;
|
|
321
|
+
/** Optional structured context attached to the submission. Validated
|
|
322
|
+
* locally before send: no functions, no symbols, no circular refs;
|
|
323
|
+
* ≤ 4 KB UTF-8 bytes when JSON-stringified. */
|
|
324
|
+
metadata?: Record<string, unknown> | undefined;
|
|
325
|
+
}
|
|
326
|
+
/** Input for {@link Scorezilla.getLeaderboard}. */
|
|
327
|
+
interface GetLeaderboardInput {
|
|
328
|
+
boardId: string;
|
|
329
|
+
/** Number of entries to return. API caps at 1000; default 100. */
|
|
330
|
+
top?: number | undefined;
|
|
331
|
+
/** Offset into the sorted board. API caps at 1_000_000; default 0. */
|
|
332
|
+
offset?: number | undefined;
|
|
333
|
+
}
|
|
334
|
+
/** Input for {@link Scorezilla.getPlayerRank}. */
|
|
335
|
+
interface GetPlayerRankInput {
|
|
336
|
+
boardId: string;
|
|
337
|
+
playerId: string;
|
|
338
|
+
}
|
|
339
|
+
/** Input for {@link Scorezilla.getWindowAround}. */
|
|
340
|
+
interface GetWindowAroundInput {
|
|
341
|
+
boardId: string;
|
|
342
|
+
playerId: string;
|
|
343
|
+
/** Entries strictly above the player. API caps at 100; default 5. */
|
|
344
|
+
before?: number | undefined;
|
|
345
|
+
/** Entries strictly below the player. API caps at 100; default 5. */
|
|
346
|
+
after?: number | undefined;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Public-key client for the Scorezilla API.
|
|
350
|
+
*
|
|
351
|
+
* ```ts
|
|
352
|
+
* const sz = new Scorezilla({ publicKey: 'pk_mygame_aBcDeF…' });
|
|
353
|
+
* const { rank, isPersonalBest } = await sz.submitScore({
|
|
354
|
+
* boardId: 'board-uuid',
|
|
355
|
+
* playerId: 'player-uuid',
|
|
356
|
+
* score: 9001,
|
|
357
|
+
* });
|
|
358
|
+
* ```
|
|
359
|
+
*
|
|
360
|
+
* @since 0.1.0
|
|
361
|
+
* @stability stable
|
|
362
|
+
*/
|
|
363
|
+
declare class Scorezilla {
|
|
364
|
+
#private;
|
|
365
|
+
/** The package version, injected at build time from `package.json`. */
|
|
366
|
+
static readonly version: string;
|
|
367
|
+
/**
|
|
368
|
+
* @param config - public-key configuration. Passing `secretKey` throws —
|
|
369
|
+
* use the `scorezilla/server` adapter (v0.2.0) for HMAC.
|
|
370
|
+
*/
|
|
371
|
+
constructor(config: ScorezillaConfig);
|
|
372
|
+
/**
|
|
373
|
+
* Submit a score to a board.
|
|
374
|
+
*
|
|
375
|
+
* Maps to `POST /v1/boards/:boardId/scores`. See [API.md](../API.md#submitscore)
|
|
376
|
+
* for the full contract.
|
|
377
|
+
*
|
|
378
|
+
* @example
|
|
379
|
+
* ```ts
|
|
380
|
+
* try {
|
|
381
|
+
* const r = await sz.submitScore({ boardId, playerId: 'alice', score: 9001 });
|
|
382
|
+
* if (r.isPersonalBest) console.log(`PB! Rank ${r.rank} of ${r.totalEntries}`);
|
|
383
|
+
* } catch (e) {
|
|
384
|
+
* if (e instanceof ScorezillaError && e.code === 'out_of_bounds') {
|
|
385
|
+
* console.warn(`Score outside board bounds (${e.reason}, limit ${e.bound})`);
|
|
386
|
+
* } else throw e;
|
|
387
|
+
* }
|
|
388
|
+
* ```
|
|
389
|
+
*
|
|
390
|
+
* @throws {ScorezillaError} `unauthorized` (bad publicKey), `forbidden`
|
|
391
|
+
* (key not bound to this board), `not_found` (board doesn't exist),
|
|
392
|
+
* `out_of_bounds` (score outside board's min/max), `rate_limited`
|
|
393
|
+
* (Layer 2/3 throttle hit), `invalid_input`, `network_error`, `timeout`.
|
|
394
|
+
* @since 0.1.0
|
|
395
|
+
* @stability stable
|
|
396
|
+
*/
|
|
397
|
+
submitScore(input: SubmitScoreInput): Promise<ApiSuccess<SubmitScoreResponse>>;
|
|
398
|
+
/**
|
|
399
|
+
* Fetch the top-N leaderboard for a board.
|
|
400
|
+
*
|
|
401
|
+
* Maps to `GET /v1/boards/:boardId/leaderboard`.
|
|
402
|
+
*
|
|
403
|
+
* @example
|
|
404
|
+
* ```ts
|
|
405
|
+
* const { entries } = await sz.getLeaderboard({ boardId, top: 25 });
|
|
406
|
+
* for (const e of entries) console.log(`${e.rank}. ${e.playerId}: ${e.score}`);
|
|
407
|
+
* ```
|
|
408
|
+
*
|
|
409
|
+
* @throws {ScorezillaError} `not_found`, `network_error`, `timeout`.
|
|
410
|
+
* @since 0.1.0
|
|
411
|
+
* @stability stable
|
|
412
|
+
*/
|
|
413
|
+
getLeaderboard(input: GetLeaderboardInput): Promise<ApiSuccess<LeaderboardResponse>>;
|
|
414
|
+
/**
|
|
415
|
+
* Fetch a single player's rank on a board.
|
|
416
|
+
*
|
|
417
|
+
* Maps to `GET /v1/boards/:boardId/players/:playerId/rank`. Returns 404
|
|
418
|
+
* (`not_found`) if the player has no entry yet.
|
|
419
|
+
*
|
|
420
|
+
* @example
|
|
421
|
+
* ```ts
|
|
422
|
+
* try {
|
|
423
|
+
* const { rank, score } = await sz.getPlayerRank({ boardId, playerId: 'alice' });
|
|
424
|
+
* console.log(`Alice is rank ${rank} with score ${score}`);
|
|
425
|
+
* } catch (e) {
|
|
426
|
+
* if (e instanceof ScorezillaError && e.isNotFound()) {
|
|
427
|
+
* console.log('Alice has no submission on this board yet.');
|
|
428
|
+
* } else throw e;
|
|
429
|
+
* }
|
|
430
|
+
* ```
|
|
431
|
+
*
|
|
432
|
+
* @throws {ScorezillaError} `not_found` (player has no submission),
|
|
433
|
+
* `network_error`, `timeout`.
|
|
434
|
+
* @since 0.1.0
|
|
435
|
+
* @stability stable
|
|
436
|
+
*/
|
|
437
|
+
getPlayerRank(input: GetPlayerRankInput): Promise<ApiSuccess<PlayerRankResponse>>;
|
|
438
|
+
/**
|
|
439
|
+
* Fetch the slice of entries surrounding a player.
|
|
440
|
+
*
|
|
441
|
+
* Maps to `GET /v1/boards/:boardId/players/:playerId/window?before=&after=`.
|
|
442
|
+
*
|
|
443
|
+
* @example
|
|
444
|
+
* ```ts
|
|
445
|
+
* const { entries } = await sz.getWindowAround({
|
|
446
|
+
* boardId, playerId: 'alice', before: 2, after: 2,
|
|
447
|
+
* });
|
|
448
|
+
* ```
|
|
449
|
+
*
|
|
450
|
+
* @throws {ScorezillaError} `network_error`, `timeout`.
|
|
451
|
+
* @since 0.1.0
|
|
452
|
+
* @stability stable
|
|
453
|
+
*/
|
|
454
|
+
getWindowAround(input: GetWindowAroundInput): Promise<ApiSuccess<WindowAroundResponse>>;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Convenience factory for users who prefer a functional API.
|
|
458
|
+
*
|
|
459
|
+
* Functionally equivalent to `new Scorezilla(config)` — same auth rules,
|
|
460
|
+
* same validation, same instance type. The only reason to prefer one over
|
|
461
|
+
* the other is code style.
|
|
462
|
+
*
|
|
463
|
+
* @example
|
|
464
|
+
* ```ts
|
|
465
|
+
* import { createClient, ScorezillaError } from 'scorezilla';
|
|
466
|
+
*
|
|
467
|
+
* const sz = createClient({ publicKey: 'pk_mygame_…' });
|
|
468
|
+
* try {
|
|
469
|
+
* await sz.submitScore({ boardId, playerId: 'alice', score: 9001 });
|
|
470
|
+
* } catch (e) {
|
|
471
|
+
* if (e instanceof ScorezillaError && e.isRateLimited()) {
|
|
472
|
+
* await new Promise((r) => setTimeout(r, (e.retryAfter ?? 30) * 1000));
|
|
473
|
+
* } else throw e;
|
|
474
|
+
* }
|
|
475
|
+
* ```
|
|
476
|
+
*
|
|
477
|
+
* @throws Plain `Error` if the config is malformed (e.g. invalid
|
|
478
|
+
* `publicKey` format, or `secretKey` passed to the public-key client).
|
|
479
|
+
* See `validateConfig`.
|
|
480
|
+
* @since 0.1.0
|
|
481
|
+
* @stability stable
|
|
482
|
+
*/
|
|
483
|
+
declare function createClient(config: ScorezillaConfig): Scorezilla;
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Runtime detection + `User-Agent` builder.
|
|
487
|
+
*
|
|
488
|
+
* **Privacy invariant:** detection happens via cheap probes on
|
|
489
|
+
* `globalThis` only. The SDK never reads `navigator.userAgent` for
|
|
490
|
+
* fingerprinting — only for the narrow case of distinguishing Cloudflare
|
|
491
|
+
* Workers from a generic worker scope. No other DOM, hardware, or
|
|
492
|
+
* timezone signals are touched. This is documented in COMPATIBILITY.md.
|
|
493
|
+
*
|
|
494
|
+
* Note on browsers: per the Fetch spec, browsers silently ignore the
|
|
495
|
+
* `User-Agent` request header. Setting it has no effect there — but it
|
|
496
|
+
* remains meaningful in Node, Bun, Deno, and Workers, where we want
|
|
497
|
+
* server-side observability of which SDK version + runtime issued each
|
|
498
|
+
* request. The client layer also sends a separate `X-Scorezilla-Client`
|
|
499
|
+
* header that browsers do honor, so we get browser-side telemetry too.
|
|
500
|
+
*/
|
|
501
|
+
/** Runtimes the SDK explicitly identifies. `unknown` is the fallback when
|
|
502
|
+
* no signature matches (e.g., a new runtime, or one with all the right
|
|
503
|
+
* globals stripped). */
|
|
504
|
+
type Runtime = 'browser' | 'node' | 'bun' | 'deno' | 'workers' | 'unknown';
|
|
505
|
+
interface GlobalProbe {
|
|
506
|
+
readonly Bun?: unknown;
|
|
507
|
+
readonly Deno?: unknown;
|
|
508
|
+
readonly process?: {
|
|
509
|
+
readonly versions?: {
|
|
510
|
+
readonly node?: string;
|
|
511
|
+
};
|
|
512
|
+
};
|
|
513
|
+
readonly document?: unknown;
|
|
514
|
+
readonly WorkerGlobalScope?: unknown;
|
|
515
|
+
readonly navigator?: {
|
|
516
|
+
readonly userAgent?: string;
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Detect the current runtime by probing well-known globals. Pure function
|
|
521
|
+
* — accepts an optional `g` override so tests can simulate any runtime.
|
|
522
|
+
*
|
|
523
|
+
* Order matters: Bun and Deno expose `process.versions.node`-like shims
|
|
524
|
+
* for compat, so we check their own identifiers first.
|
|
525
|
+
*
|
|
526
|
+
* @example
|
|
527
|
+
* ```ts
|
|
528
|
+
* import { detectRuntime } from 'scorezilla';
|
|
529
|
+
* const runtime = detectRuntime(); // → 'browser' | 'node' | 'bun' | 'deno' | 'workers' | 'unknown'
|
|
530
|
+
* myTelemetry.track('app.start', { sdk_runtime: runtime });
|
|
531
|
+
* ```
|
|
532
|
+
*
|
|
533
|
+
* @since 0.1.0
|
|
534
|
+
* @stability stable
|
|
535
|
+
*/
|
|
536
|
+
declare function detectRuntime(g?: GlobalProbe): Runtime;
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Scorezilla SDK — public entry.
|
|
540
|
+
*
|
|
541
|
+
* Surface (v0.1.0):
|
|
542
|
+
* • `Scorezilla` — public-key client (browser + Node ≥ 20 + Workers + Bun + Deno)
|
|
543
|
+
* • `createClient` — convenience factory
|
|
544
|
+
* • `ScorezillaError` — the single error type the SDK throws
|
|
545
|
+
* • `SDK_VERSION` — exported for support / bug-reporting
|
|
546
|
+
* • All wire types — `SubmitScoreResponse`, `LeaderboardResponse`, etc.
|
|
547
|
+
* • All input types — `SubmitScoreInput`, `GetLeaderboardInput`, etc.
|
|
548
|
+
* • Config types — `ScorezillaConfig`, `PublicKeyConfig`, `SecretKeyConfig`
|
|
549
|
+
*
|
|
550
|
+
* The HMAC server-side client (`scorezilla/server`), React adapter
|
|
551
|
+
* (`scorezilla/react`), and Phaser plugin (`scorezilla/phaser`) ship in
|
|
552
|
+
* later minor releases — see CHANGELOG.md.
|
|
553
|
+
*/
|
|
554
|
+
|
|
555
|
+
declare const SDK_VERSION: string;
|
|
556
|
+
|
|
557
|
+
export { type ApiError, type ApiResponse, type ApiSuccess, type BaseConfig, type GetLeaderboardInput, type GetPlayerRankInput, type GetWindowAroundInput, type LeaderboardResponse, type OutOfBoundsReason, type PlayerRankResponse, type PublicKeyConfig, type RankedEntry, type Runtime, SDK_VERSION, Scorezilla, type ScorezillaConfig, ScorezillaError, type ScorezillaErrorCode, type SecretKeyConfig, type SubmitScoreInput, type SubmitScoreResponse, type WindowAroundResponse, createClient, detectRuntime };
|