scorezilla 0.4.0 → 0.5.1
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 +22 -11
- package/CHANGELOG.md +21 -0
- package/README.md +26 -0
- package/dist/errors-8qzE40-2.d.ts +170 -0
- package/dist/errors-Bu084BD9.d.cts +170 -0
- package/dist/headless.cjs +886 -0
- package/dist/headless.cjs.map +1 -0
- package/dist/headless.d.cts +59 -0
- package/dist/headless.d.ts +59 -0
- package/dist/headless.js +883 -0
- package/dist/headless.js.map +1 -0
- package/dist/index.cjs +13 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -3
- package/dist/index.d.ts +17 -3
- package/dist/index.js +13 -3
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +1 -1
- package/dist/server.d.cts +3 -2
- package/dist/server.d.ts +3 -2
- package/dist/server.js +1 -1
- package/dist/{errors-BhPRMQiV.d.cts → types-BxxzS_pF.d.cts} +23 -169
- package/dist/{errors-BhPRMQiV.d.ts → types-BxxzS_pF.d.ts} +23 -169
- package/package.json +14 -1
package/API.md
CHANGED
|
@@ -111,17 +111,22 @@ Violations throw a plain `Error` (not `ScorezillaError`) before any network call
|
|
|
111
111
|
|
|
112
112
|
#### Errors
|
|
113
113
|
|
|
114
|
-
| Code | Status | Meaning
|
|
115
|
-
| -------------------------------- | ------ |
|
|
116
|
-
| `unauthorized` | 401 | Invalid `publicKey`.
|
|
117
|
-
| `forbidden` | 403 | Key is not bound to this `boardId`.
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
122
|
-
| `
|
|
123
|
-
| `
|
|
124
|
-
| `
|
|
114
|
+
| Code | Status | Meaning |
|
|
115
|
+
| -------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
116
|
+
| `unauthorized` | 401 | Invalid `publicKey`. |
|
|
117
|
+
| `forbidden` | 403 | Key is not bound to this `boardId`. |
|
|
118
|
+
| `origin_not_allowed` | 403 | Request `Origin` is not in the board's allowlist (configured in the dashboard). Gates browser submits only — server-to-server calls send no `Origin`. |
|
|
119
|
+
| `player_banned` | 403 | The player is on this board's denylist (banned by the game owner). |
|
|
120
|
+
| `turnstile_required` | 403 | The board requires a Cloudflare Turnstile token and none was sent. |
|
|
121
|
+
| `turnstile_failed` | 403 | The supplied Turnstile token failed verification. |
|
|
122
|
+
| `turnstile_hostname_mismatch` | 403 | The Turnstile token was solved on an origin not allowed for this game. |
|
|
123
|
+
| `not_found` | 404 | Board doesn't exist. |
|
|
124
|
+
| `out_of_bounds` | 422 | Score outside the board's `[minScore, maxScore]`. `error.reason` is `'below_min'` or `'above_max'`; `error.bound` is the limit. |
|
|
125
|
+
| `rate_limited` | 429 | Throttled. `error.retryAfter` (seconds), `error.layer`. |
|
|
126
|
+
| `invalid_input` / `invalid_json` | 400 | Malformed body. |
|
|
127
|
+
| `network_error` | 0 | Could not reach the API. |
|
|
128
|
+
| `timeout` | 0 | Request exceeded `timeoutMs`. |
|
|
129
|
+
| `aborted` | 0 | Caller-provided `AbortSignal` fired. |
|
|
125
130
|
|
|
126
131
|
### `getLeaderboard`
|
|
127
132
|
|
|
@@ -154,6 +159,12 @@ const { entries } = await sz.getLeaderboard({ boardId, top: 25 });
|
|
|
154
159
|
for (const e of entries) console.log(`${e.rank}. ${e.playerId}: ${e.score}`);
|
|
155
160
|
```
|
|
156
161
|
|
|
162
|
+
> **Read-path errors.** The read methods (`getLeaderboard`, `getPlayerRank`,
|
|
163
|
+
> `getWindowAround`) share the codes in the [`submitScore` table](#errors)
|
|
164
|
+
> (minus the submit-only ones), and add one: **`tenant_suspended`** (`402`) when
|
|
165
|
+
> the board's tenant is suspended. (On the submit path that same condition
|
|
166
|
+
> surfaces as `usage_cap_exceeded` with `reason: 'suspended'`.)
|
|
167
|
+
|
|
157
168
|
### `getPlayerRank`
|
|
158
169
|
|
|
159
170
|
`GET /v1/boards/:boardId/players/:playerId/rank`. "No entry yet" is a normal
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#66](https://github.com/isco-tec/scorezilla-js/pull/66) [`746d599`](https://github.com/isco-tec/scorezilla-js/commit/746d599880a347ca656b233a9241dc6b8cc68926) Thanks [@isco-tec](https://github.com/isco-tec)! - Add `turnstile_hostname_mismatch` to the `ScorezillaErrorCode` union — a 403 the API can return when a Turnstile token is solved on an origin not allowed for the game. It previously fell through the open union as an untyped string; now it's a first-class code (with JSDoc) and documented in the API reference alongside the other 403 codes.
|
|
8
|
+
|
|
9
|
+
- [#67](https://github.com/isco-tec/scorezilla-js/pull/67) [`2966433`](https://github.com/isco-tec/scorezilla-js/commit/2966433d66883fb3eee04596aefbba5d1677c436) Thanks [@isco-tec](https://github.com/isco-tec)! - Add `tenant_suspended` to the `ScorezillaErrorCode` union — a `402` the read paths (`getLeaderboard` / `getPlayerRank` / `getWindowAround`) return when the board's tenant is suspended. It previously fell through the open union as an untyped string; now it's typed (with JSDoc) and documented in the API reference. (On submit, the same condition still surfaces as `usage_cap_exceeded` with `reason: 'suspended'`.)
|
|
10
|
+
|
|
11
|
+
- [#62](https://github.com/isco-tec/scorezilla-js/pull/62) [`90b277a`](https://github.com/isco-tec/scorezilla-js/commit/90b277afb745c29fbdcca46cd6dbd14479229c2f) Thanks [@isco-tec](https://github.com/isco-tec)! - Add the newer server error codes to the `ScorezillaErrorCode` union so consumers get autocomplete + type-checking when branching on them: `player_banned`, `name_taken`, `board_archived`, `turnstile_required`, `turnstile_failed`, `origin_not_allowed`. (Runtime behavior is unchanged — the union's open tail already carried these strings at runtime.) The `submitScore` `@throws` JSDoc now lists `player_banned` and `name_taken` as possible outcomes.
|
|
12
|
+
|
|
13
|
+
## 0.5.0
|
|
14
|
+
|
|
15
|
+
### Minor Changes
|
|
16
|
+
|
|
17
|
+
- [#60](https://github.com/isco-tec/scorezilla-js/pull/60) [`20ccc50`](https://github.com/isco-tec/scorezilla-js/commit/20ccc50d0f37a491c309b2f47555c698c3ceaec2) Thanks [@isco-tec](https://github.com/isco-tec)! - Add the headless, never-throws client and cross-platform submit fields.
|
|
18
|
+
- **`scorezilla/headless`** — a new subpath exposing `createHeadlessClient(config)` with `submit(...) → { ok, rank, totalEntries, isPersonalBest } | null` and `getLeaderboard(...) → RankedEntry[]` that **never throw**: failures collapse to `null` / `[]`. This is the identical headless surface a host wraps so embedded game code never changes between platforms. Plus `isCrossOrigin(homeOrigin)` to detect cross-site embedding and decide whether the token path is needed.
|
|
19
|
+
- **`submitScore` / headless `submit`** now accept an optional **`name`** (public display name) and **`turnstileToken`** (the cross-origin token path); both are forwarded on the wire.
|
|
20
|
+
- **`RankedEntry`** now carries an optional **`name`** — the display name returned by the leaderboard read.
|
|
21
|
+
|
|
22
|
+
The throwing `Scorezilla` class is unchanged; reach for it when you want typed `ScorezillaError`s to branch on.
|
|
23
|
+
|
|
3
24
|
## 0.4.0
|
|
4
25
|
|
|
5
26
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -92,6 +92,32 @@ await sz.getWindowAround({ boardId, playerId, before?, after? });
|
|
|
92
92
|
See [**API.md**](./API.md) for the full reference, including every response
|
|
93
93
|
field, every error code, and advanced patterns.
|
|
94
94
|
|
|
95
|
+
## Headless, never-throws client (`scorezilla/headless`)
|
|
96
|
+
|
|
97
|
+
For embedded games that want the **same API on every host** and never want a
|
|
98
|
+
failed call to throw, wrap the client in the headless facade. Failures collapse
|
|
99
|
+
to `null` (submit) or `[]` (leaderboard), so a dropped network call can't crash
|
|
100
|
+
a game loop:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import { createHeadlessClient, isCrossOrigin } from 'scorezilla/headless';
|
|
104
|
+
|
|
105
|
+
const sz = createHeadlessClient({ publicKey: 'pk_mygame_…' });
|
|
106
|
+
|
|
107
|
+
// Never throws — `null` means "didn't land", any result means it did.
|
|
108
|
+
const result = await sz.submit({ boardId, playerId, score: 4200, name: 'Ada' });
|
|
109
|
+
if (result) console.log(`rank ${result.rank} of ${result.totalEntries}`);
|
|
110
|
+
|
|
111
|
+
// Never throws — `[]` on any failure. Each entry has at least
|
|
112
|
+
// { rank, playerId, name?, score }.
|
|
113
|
+
const top = await sz.getLeaderboard({ boardId, top: 10 });
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
When a game is embedded **cross-site** (itch.io, a portal) and the board has
|
|
117
|
+
Turnstile gating on, pass a `turnstileToken` your host obtains (e.g. via a
|
|
118
|
+
hidden broker iframe on a trusted origin). Use `isCrossOrigin(homeOrigin)` to
|
|
119
|
+
decide whether that path is needed; same-origin submits need nothing extra.
|
|
120
|
+
|
|
95
121
|
## Player identity (`scorezilla/identity`)
|
|
96
122
|
|
|
97
123
|
Browser-side helpers that produce the `playerId` you pass to `submitScore`:
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { S as ScorezillaErrorCode, O as OutOfBoundsReason, U as UsageCapReason, B as BillingTier, A as ApiError } from './types-BxxzS_pF.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SDK error type.
|
|
5
|
+
*
|
|
6
|
+
* Every non-2xx API response is normalized into a `ScorezillaError` instance
|
|
7
|
+
* by the transport layer. Network failures and timeouts surface as the same
|
|
8
|
+
* class (with `status: 0`) so callers have a single error type to catch.
|
|
9
|
+
*
|
|
10
|
+
* **Invariant — consumers MUST branch on `code` (and optionally `reason`),
|
|
11
|
+
* never on `message`.** The English-language `message` is for operator
|
|
12
|
+
* logging only and is explicitly **not** part of the SemVer contract; a
|
|
13
|
+
* minor release MAY reword any message. Machine logic that depends on
|
|
14
|
+
* message text will break silently across upgrades.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Options for {@link ScorezillaError.from}.
|
|
19
|
+
*
|
|
20
|
+
* The fields mirror what's available after a fetch round-trip: the HTTP
|
|
21
|
+
* status, the parsed JSON body (if any), the request ID from
|
|
22
|
+
* `X-Request-Id`, and an optional `cause` for the underlying
|
|
23
|
+
* network/abort error.
|
|
24
|
+
*/
|
|
25
|
+
interface ScorezillaErrorFromInit {
|
|
26
|
+
status: number;
|
|
27
|
+
body?: ApiError | undefined;
|
|
28
|
+
requestId?: string | undefined;
|
|
29
|
+
cause?: unknown;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Thrown by the SDK for every failure path — non-2xx responses, network
|
|
33
|
+
* errors, aborts, and timeouts.
|
|
34
|
+
*
|
|
35
|
+
* Cross-realm `instanceof` is guaranteed: the class sets `Error.prototype`
|
|
36
|
+
* explicitly so checks survive iframe / worker boundaries.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* try {
|
|
41
|
+
* await sz.submitScore({ boardId, playerId, score });
|
|
42
|
+
* } catch (e) {
|
|
43
|
+
* if (!(e instanceof ScorezillaError)) throw e;
|
|
44
|
+
*
|
|
45
|
+
* if (e.isRateLimited()) {
|
|
46
|
+
* await sleep((e.retryAfter ?? 30) * 1000);
|
|
47
|
+
* return retry();
|
|
48
|
+
* }
|
|
49
|
+
* if (e.code === 'out_of_bounds') {
|
|
50
|
+
* console.warn(`Score crosses ${e.reason} bound (limit ${e.bound})`);
|
|
51
|
+
* return;
|
|
52
|
+
* }
|
|
53
|
+
* if (e.isAuth()) throw new Error('SDK misconfigured — bad publicKey');
|
|
54
|
+
*
|
|
55
|
+
* // Anything else: surface to your reporter with requestId for support.
|
|
56
|
+
* console.error(`Scorezilla ${e.code} (${e.status}) — request ${e.requestId}`);
|
|
57
|
+
* throw e;
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* @since 0.1.0
|
|
62
|
+
* @stability stable
|
|
63
|
+
*/
|
|
64
|
+
declare class ScorezillaError extends Error {
|
|
65
|
+
/** HTTP status of the response, or {@link STATUS_NETWORK_ERROR} (0) for
|
|
66
|
+
* network / abort / timeout. */
|
|
67
|
+
readonly status: number;
|
|
68
|
+
/** Machine-stable error code from the API. Open union — see
|
|
69
|
+
* {@link ScorezillaErrorCode}. For network errors, this is `'network_error'`;
|
|
70
|
+
* for aborts, `'aborted'`; for timeouts, `'timeout'`. */
|
|
71
|
+
readonly code: ScorezillaErrorCode;
|
|
72
|
+
/** Sub-classifier — present on:
|
|
73
|
+
* - `out_of_bounds`: `'below_min' | 'above_max'`
|
|
74
|
+
* - `usage_cap_exceeded`: `'over_cap' | 'suspended'`
|
|
75
|
+
* and possibly other codes in future minor releases. */
|
|
76
|
+
readonly reason: OutOfBoundsReason | UsageCapReason | string | undefined;
|
|
77
|
+
/** Seconds — present on `rate_limited`. Honored by the transport's retry
|
|
78
|
+
* policy (Step 2.4). */
|
|
79
|
+
readonly retryAfter: number | undefined;
|
|
80
|
+
/** Server-issued request ID, lifted from the `X-Request-Id` response
|
|
81
|
+
* header. Pass this to support when filing bugs. */
|
|
82
|
+
readonly requestId: string | undefined;
|
|
83
|
+
/** The bound value crossed on `out_of_bounds`. */
|
|
84
|
+
readonly bound: number | undefined;
|
|
85
|
+
/** Which rate-limit layer fired on `rate_limited`. */
|
|
86
|
+
readonly layer: string | undefined;
|
|
87
|
+
/** Tenant's billing tier — present on `usage_cap_exceeded`. */
|
|
88
|
+
readonly tier: BillingTier | undefined;
|
|
89
|
+
/** The cap value crossed on `usage_cap_exceeded`. `0` indicates a
|
|
90
|
+
* suspended tenant. `undefined` on all other error codes. */
|
|
91
|
+
readonly cap: number | undefined;
|
|
92
|
+
/** The post-increment submit count on `usage_cap_exceeded`. Always
|
|
93
|
+
* `> cap` when `reason === 'over_cap'`. */
|
|
94
|
+
readonly count: number | undefined;
|
|
95
|
+
/** The period the count belongs to on `usage_cap_exceeded`, in `YYYY-MM`
|
|
96
|
+
* UTC form. */
|
|
97
|
+
readonly period: string | undefined;
|
|
98
|
+
/** ISO-8601 timestamp of midnight UTC on the 1st of the next month —
|
|
99
|
+
* the counter's natural reset point on `usage_cap_exceeded`. */
|
|
100
|
+
readonly resetsAt: string | undefined;
|
|
101
|
+
/** The underlying cause (e.g., a `TypeError: fetch failed`) for
|
|
102
|
+
* network/abort/timeout paths. `undefined` when the error came from a
|
|
103
|
+
* successfully-parsed API error body. */
|
|
104
|
+
readonly cause: unknown;
|
|
105
|
+
constructor(message: string, init: {
|
|
106
|
+
status: number;
|
|
107
|
+
code: ScorezillaErrorCode;
|
|
108
|
+
reason?: string | undefined;
|
|
109
|
+
retryAfter?: number | undefined;
|
|
110
|
+
requestId?: string | undefined;
|
|
111
|
+
bound?: number | undefined;
|
|
112
|
+
layer?: string | undefined;
|
|
113
|
+
tier?: BillingTier | undefined;
|
|
114
|
+
cap?: number | undefined;
|
|
115
|
+
count?: number | undefined;
|
|
116
|
+
period?: string | undefined;
|
|
117
|
+
resetsAt?: string | undefined;
|
|
118
|
+
cause?: unknown;
|
|
119
|
+
});
|
|
120
|
+
/** `true` when this error is a 429 / `rate_limited`. */
|
|
121
|
+
isRateLimited(): boolean;
|
|
122
|
+
/**
|
|
123
|
+
* `true` when this error is a 402 / `usage_cap_exceeded`. The tenant
|
|
124
|
+
* has either hit their tier's monthly submit cap (`reason ===
|
|
125
|
+
* 'over_cap'`) or is suspended (`reason === 'suspended'`).
|
|
126
|
+
*
|
|
127
|
+
* Consumers SHOULD NOT auto-retry on this error — the cap doesn't lift
|
|
128
|
+
* until `resetsAt`. Surface to the developer with an upgrade prompt
|
|
129
|
+
* (over_cap) or contact-support message (suspended).
|
|
130
|
+
*/
|
|
131
|
+
isUsageCapExceeded(): boolean;
|
|
132
|
+
/** `true` when this error is a 402 + reason 'suspended' (vs over-cap). */
|
|
133
|
+
isSuspended(): boolean;
|
|
134
|
+
/** `true` when this error is a 401 / `unauthorized` (or 403 / `forbidden`). */
|
|
135
|
+
isAuth(): boolean;
|
|
136
|
+
/** `true` when this error is a 404 / `not_found`. */
|
|
137
|
+
isNotFound(): boolean;
|
|
138
|
+
/** `true` when this error is a 422 / `out_of_bounds` (score below/above board limit). */
|
|
139
|
+
isOutOfBounds(): boolean;
|
|
140
|
+
/** `true` for the SDK's retryable conditions: pure network errors, 5xx, and
|
|
141
|
+
* 429. Deliberately excludes `timeout` and `aborted` — those are caller-
|
|
142
|
+
* observable terminal states, not transient. Aligned with `shouldRetryError`
|
|
143
|
+
* in `retry.ts` so a consumer mirroring the SDK's retry policy gets the
|
|
144
|
+
* same answer the transport does. */
|
|
145
|
+
isTransient(): boolean;
|
|
146
|
+
/** `true` when this error is a 409 / `conflict` (idempotency-key conflict
|
|
147
|
+
* on retry). */
|
|
148
|
+
isConflict(): boolean;
|
|
149
|
+
/**
|
|
150
|
+
* Build a `ScorezillaError` from a fetch round-trip outcome.
|
|
151
|
+
*
|
|
152
|
+
* Prefer this over `new ScorezillaError(...)` from the transport layer —
|
|
153
|
+
* it does the mapping from API response shape to error fields in one
|
|
154
|
+
* place, so future fields like `correlationId` get added once here.
|
|
155
|
+
*
|
|
156
|
+
* @param init - status, optional parsed body, optional requestId, optional cause
|
|
157
|
+
*/
|
|
158
|
+
static from(init: ScorezillaErrorFromInit): ScorezillaError;
|
|
159
|
+
/**
|
|
160
|
+
* Build a `ScorezillaError` for a transport-level failure (no HTTP
|
|
161
|
+
* response received): network error, abort, or timeout.
|
|
162
|
+
*/
|
|
163
|
+
static network(message: string, cause: unknown): ScorezillaError;
|
|
164
|
+
/** Build a `ScorezillaError` for an `AbortSignal`-triggered cancellation. */
|
|
165
|
+
static aborted(cause: unknown): ScorezillaError;
|
|
166
|
+
/** Build a `ScorezillaError` for a request that exceeded its timeout budget. */
|
|
167
|
+
static timeout(timeoutMs: number): ScorezillaError;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export { ScorezillaError as S };
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { S as ScorezillaErrorCode, O as OutOfBoundsReason, U as UsageCapReason, B as BillingTier, A as ApiError } from './types-BxxzS_pF.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SDK error type.
|
|
5
|
+
*
|
|
6
|
+
* Every non-2xx API response is normalized into a `ScorezillaError` instance
|
|
7
|
+
* by the transport layer. Network failures and timeouts surface as the same
|
|
8
|
+
* class (with `status: 0`) so callers have a single error type to catch.
|
|
9
|
+
*
|
|
10
|
+
* **Invariant — consumers MUST branch on `code` (and optionally `reason`),
|
|
11
|
+
* never on `message`.** The English-language `message` is for operator
|
|
12
|
+
* logging only and is explicitly **not** part of the SemVer contract; a
|
|
13
|
+
* minor release MAY reword any message. Machine logic that depends on
|
|
14
|
+
* message text will break silently across upgrades.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Options for {@link ScorezillaError.from}.
|
|
19
|
+
*
|
|
20
|
+
* The fields mirror what's available after a fetch round-trip: the HTTP
|
|
21
|
+
* status, the parsed JSON body (if any), the request ID from
|
|
22
|
+
* `X-Request-Id`, and an optional `cause` for the underlying
|
|
23
|
+
* network/abort error.
|
|
24
|
+
*/
|
|
25
|
+
interface ScorezillaErrorFromInit {
|
|
26
|
+
status: number;
|
|
27
|
+
body?: ApiError | undefined;
|
|
28
|
+
requestId?: string | undefined;
|
|
29
|
+
cause?: unknown;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Thrown by the SDK for every failure path — non-2xx responses, network
|
|
33
|
+
* errors, aborts, and timeouts.
|
|
34
|
+
*
|
|
35
|
+
* Cross-realm `instanceof` is guaranteed: the class sets `Error.prototype`
|
|
36
|
+
* explicitly so checks survive iframe / worker boundaries.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* try {
|
|
41
|
+
* await sz.submitScore({ boardId, playerId, score });
|
|
42
|
+
* } catch (e) {
|
|
43
|
+
* if (!(e instanceof ScorezillaError)) throw e;
|
|
44
|
+
*
|
|
45
|
+
* if (e.isRateLimited()) {
|
|
46
|
+
* await sleep((e.retryAfter ?? 30) * 1000);
|
|
47
|
+
* return retry();
|
|
48
|
+
* }
|
|
49
|
+
* if (e.code === 'out_of_bounds') {
|
|
50
|
+
* console.warn(`Score crosses ${e.reason} bound (limit ${e.bound})`);
|
|
51
|
+
* return;
|
|
52
|
+
* }
|
|
53
|
+
* if (e.isAuth()) throw new Error('SDK misconfigured — bad publicKey');
|
|
54
|
+
*
|
|
55
|
+
* // Anything else: surface to your reporter with requestId for support.
|
|
56
|
+
* console.error(`Scorezilla ${e.code} (${e.status}) — request ${e.requestId}`);
|
|
57
|
+
* throw e;
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* @since 0.1.0
|
|
62
|
+
* @stability stable
|
|
63
|
+
*/
|
|
64
|
+
declare class ScorezillaError extends Error {
|
|
65
|
+
/** HTTP status of the response, or {@link STATUS_NETWORK_ERROR} (0) for
|
|
66
|
+
* network / abort / timeout. */
|
|
67
|
+
readonly status: number;
|
|
68
|
+
/** Machine-stable error code from the API. Open union — see
|
|
69
|
+
* {@link ScorezillaErrorCode}. For network errors, this is `'network_error'`;
|
|
70
|
+
* for aborts, `'aborted'`; for timeouts, `'timeout'`. */
|
|
71
|
+
readonly code: ScorezillaErrorCode;
|
|
72
|
+
/** Sub-classifier — present on:
|
|
73
|
+
* - `out_of_bounds`: `'below_min' | 'above_max'`
|
|
74
|
+
* - `usage_cap_exceeded`: `'over_cap' | 'suspended'`
|
|
75
|
+
* and possibly other codes in future minor releases. */
|
|
76
|
+
readonly reason: OutOfBoundsReason | UsageCapReason | string | undefined;
|
|
77
|
+
/** Seconds — present on `rate_limited`. Honored by the transport's retry
|
|
78
|
+
* policy (Step 2.4). */
|
|
79
|
+
readonly retryAfter: number | undefined;
|
|
80
|
+
/** Server-issued request ID, lifted from the `X-Request-Id` response
|
|
81
|
+
* header. Pass this to support when filing bugs. */
|
|
82
|
+
readonly requestId: string | undefined;
|
|
83
|
+
/** The bound value crossed on `out_of_bounds`. */
|
|
84
|
+
readonly bound: number | undefined;
|
|
85
|
+
/** Which rate-limit layer fired on `rate_limited`. */
|
|
86
|
+
readonly layer: string | undefined;
|
|
87
|
+
/** Tenant's billing tier — present on `usage_cap_exceeded`. */
|
|
88
|
+
readonly tier: BillingTier | undefined;
|
|
89
|
+
/** The cap value crossed on `usage_cap_exceeded`. `0` indicates a
|
|
90
|
+
* suspended tenant. `undefined` on all other error codes. */
|
|
91
|
+
readonly cap: number | undefined;
|
|
92
|
+
/** The post-increment submit count on `usage_cap_exceeded`. Always
|
|
93
|
+
* `> cap` when `reason === 'over_cap'`. */
|
|
94
|
+
readonly count: number | undefined;
|
|
95
|
+
/** The period the count belongs to on `usage_cap_exceeded`, in `YYYY-MM`
|
|
96
|
+
* UTC form. */
|
|
97
|
+
readonly period: string | undefined;
|
|
98
|
+
/** ISO-8601 timestamp of midnight UTC on the 1st of the next month —
|
|
99
|
+
* the counter's natural reset point on `usage_cap_exceeded`. */
|
|
100
|
+
readonly resetsAt: string | undefined;
|
|
101
|
+
/** The underlying cause (e.g., a `TypeError: fetch failed`) for
|
|
102
|
+
* network/abort/timeout paths. `undefined` when the error came from a
|
|
103
|
+
* successfully-parsed API error body. */
|
|
104
|
+
readonly cause: unknown;
|
|
105
|
+
constructor(message: string, init: {
|
|
106
|
+
status: number;
|
|
107
|
+
code: ScorezillaErrorCode;
|
|
108
|
+
reason?: string | undefined;
|
|
109
|
+
retryAfter?: number | undefined;
|
|
110
|
+
requestId?: string | undefined;
|
|
111
|
+
bound?: number | undefined;
|
|
112
|
+
layer?: string | undefined;
|
|
113
|
+
tier?: BillingTier | undefined;
|
|
114
|
+
cap?: number | undefined;
|
|
115
|
+
count?: number | undefined;
|
|
116
|
+
period?: string | undefined;
|
|
117
|
+
resetsAt?: string | undefined;
|
|
118
|
+
cause?: unknown;
|
|
119
|
+
});
|
|
120
|
+
/** `true` when this error is a 429 / `rate_limited`. */
|
|
121
|
+
isRateLimited(): boolean;
|
|
122
|
+
/**
|
|
123
|
+
* `true` when this error is a 402 / `usage_cap_exceeded`. The tenant
|
|
124
|
+
* has either hit their tier's monthly submit cap (`reason ===
|
|
125
|
+
* 'over_cap'`) or is suspended (`reason === 'suspended'`).
|
|
126
|
+
*
|
|
127
|
+
* Consumers SHOULD NOT auto-retry on this error — the cap doesn't lift
|
|
128
|
+
* until `resetsAt`. Surface to the developer with an upgrade prompt
|
|
129
|
+
* (over_cap) or contact-support message (suspended).
|
|
130
|
+
*/
|
|
131
|
+
isUsageCapExceeded(): boolean;
|
|
132
|
+
/** `true` when this error is a 402 + reason 'suspended' (vs over-cap). */
|
|
133
|
+
isSuspended(): boolean;
|
|
134
|
+
/** `true` when this error is a 401 / `unauthorized` (or 403 / `forbidden`). */
|
|
135
|
+
isAuth(): boolean;
|
|
136
|
+
/** `true` when this error is a 404 / `not_found`. */
|
|
137
|
+
isNotFound(): boolean;
|
|
138
|
+
/** `true` when this error is a 422 / `out_of_bounds` (score below/above board limit). */
|
|
139
|
+
isOutOfBounds(): boolean;
|
|
140
|
+
/** `true` for the SDK's retryable conditions: pure network errors, 5xx, and
|
|
141
|
+
* 429. Deliberately excludes `timeout` and `aborted` — those are caller-
|
|
142
|
+
* observable terminal states, not transient. Aligned with `shouldRetryError`
|
|
143
|
+
* in `retry.ts` so a consumer mirroring the SDK's retry policy gets the
|
|
144
|
+
* same answer the transport does. */
|
|
145
|
+
isTransient(): boolean;
|
|
146
|
+
/** `true` when this error is a 409 / `conflict` (idempotency-key conflict
|
|
147
|
+
* on retry). */
|
|
148
|
+
isConflict(): boolean;
|
|
149
|
+
/**
|
|
150
|
+
* Build a `ScorezillaError` from a fetch round-trip outcome.
|
|
151
|
+
*
|
|
152
|
+
* Prefer this over `new ScorezillaError(...)` from the transport layer —
|
|
153
|
+
* it does the mapping from API response shape to error fields in one
|
|
154
|
+
* place, so future fields like `correlationId` get added once here.
|
|
155
|
+
*
|
|
156
|
+
* @param init - status, optional parsed body, optional requestId, optional cause
|
|
157
|
+
*/
|
|
158
|
+
static from(init: ScorezillaErrorFromInit): ScorezillaError;
|
|
159
|
+
/**
|
|
160
|
+
* Build a `ScorezillaError` for a transport-level failure (no HTTP
|
|
161
|
+
* response received): network error, abort, or timeout.
|
|
162
|
+
*/
|
|
163
|
+
static network(message: string, cause: unknown): ScorezillaError;
|
|
164
|
+
/** Build a `ScorezillaError` for an `AbortSignal`-triggered cancellation. */
|
|
165
|
+
static aborted(cause: unknown): ScorezillaError;
|
|
166
|
+
/** Build a `ScorezillaError` for a request that exceeded its timeout budget. */
|
|
167
|
+
static timeout(timeoutMs: number): ScorezillaError;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export { ScorezillaError as S };
|