scorezilla 0.1.0-next.3 → 0.2.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/CHANGELOG.md +153 -0
- package/README.md +13 -0
- package/dist/{errors-CUAQsaVS.d.cts → errors-B7hyC-C5.d.cts} +79 -5
- package/dist/{errors-CUAQsaVS.d.ts → errors-B7hyC-C5.d.ts} +79 -5
- package/dist/index.cjs +96 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -6
- package/dist/index.d.ts +18 -6
- package/dist/index.js +96 -10
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +95 -9
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +19 -13
- package/dist/server.d.ts +19 -13
- package/dist/server.js +95 -9
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/dist/server.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as SecretKeyConfig, A as ApiSuccess, a as SubmitScoreResponse, L as LeaderboardResponse, P as PlayerRankResponse, W as WindowAroundResponse } from './errors-
|
|
2
|
-
export { R as RankedEntry, b as ScorezillaError, c as ScorezillaErrorCode } from './errors-
|
|
1
|
+
import { S as SecretKeyConfig, A as ApiSuccess, a as SubmitScoreResponse, L as LeaderboardResponse, P as PlayerRankResponse, W as WindowAroundResponse } from './errors-B7hyC-C5.js';
|
|
2
|
+
export { R as RankedEntry, b as ScorezillaError, c as ScorezillaErrorCode } from './errors-B7hyC-C5.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* `scorezilla/server` — HMAC-signed adapter for game backends (#17, v0.2.0).
|
|
@@ -20,11 +20,14 @@ export { R as RankedEntry, b as ScorezillaError, c as ScorezillaErrorCode } from
|
|
|
20
20
|
* -----------------------------------------
|
|
21
21
|
* The method shape is intentionally identical — `submitScore`,
|
|
22
22
|
* `getLeaderboard`, `getPlayerRank`, `getWindowAround`. The only
|
|
23
|
-
* difference at the call site is the constructor argument:
|
|
23
|
+
* difference at the call site is the constructor argument: a single
|
|
24
|
+
* `sk_live_<keyId>_<random>` token replaces the public key (Stripe-
|
|
25
|
+
* style single-token format; the keyId is embedded in the plaintext
|
|
26
|
+
* so consumers manage one value, not two):
|
|
24
27
|
*
|
|
25
28
|
* import { Scorezilla } from 'scorezilla/server';
|
|
26
29
|
* const sz = new Scorezilla({
|
|
27
|
-
* secretKey:
|
|
30
|
+
* secretKey: process.env.SCOREZILLA_SECRET_KEY!, // sk_live_<keyId>_<random>
|
|
28
31
|
* });
|
|
29
32
|
*
|
|
30
33
|
* Behind the scenes:
|
|
@@ -43,8 +46,14 @@ export { R as RankedEntry, b as ScorezillaError, c as ScorezillaErrorCode } from
|
|
|
43
46
|
* secret key MUST NEVER leave the server.
|
|
44
47
|
*/
|
|
45
48
|
|
|
49
|
+
/** Caller-cancellable common shape — server-side too. */
|
|
50
|
+
interface CancellableInput {
|
|
51
|
+
/** Optional `AbortSignal` to cancel mid-flight. The transport composes
|
|
52
|
+
* it with the per-attempt timeout, so aborting always wins. */
|
|
53
|
+
signal?: AbortSignal | undefined;
|
|
54
|
+
}
|
|
46
55
|
/** Input for {@link Scorezilla.submitScore}. */
|
|
47
|
-
interface SubmitScoreInput {
|
|
56
|
+
interface SubmitScoreInput extends CancellableInput {
|
|
48
57
|
/** UUID-typed board identifier — issued by the operator dashboard. */
|
|
49
58
|
boardId: string;
|
|
50
59
|
/** Stable per-player identifier (UUID, your account ID, etc.). Avoid PII. */
|
|
@@ -56,7 +65,7 @@ interface SubmitScoreInput {
|
|
|
56
65
|
metadata?: Record<string, unknown> | undefined;
|
|
57
66
|
}
|
|
58
67
|
/** Input for {@link Scorezilla.getLeaderboard}. */
|
|
59
|
-
interface GetLeaderboardInput {
|
|
68
|
+
interface GetLeaderboardInput extends CancellableInput {
|
|
60
69
|
boardId: string;
|
|
61
70
|
/** Number of entries (API caps at 1000; default 100). */
|
|
62
71
|
top?: number | undefined;
|
|
@@ -64,12 +73,12 @@ interface GetLeaderboardInput {
|
|
|
64
73
|
offset?: number | undefined;
|
|
65
74
|
}
|
|
66
75
|
/** Input for {@link Scorezilla.getPlayerRank}. */
|
|
67
|
-
interface GetPlayerRankInput {
|
|
76
|
+
interface GetPlayerRankInput extends CancellableInput {
|
|
68
77
|
boardId: string;
|
|
69
78
|
playerId: string;
|
|
70
79
|
}
|
|
71
80
|
/** Input for {@link Scorezilla.getWindowAround}. */
|
|
72
|
-
interface GetWindowAroundInput {
|
|
81
|
+
interface GetWindowAroundInput extends CancellableInput {
|
|
73
82
|
boardId: string;
|
|
74
83
|
playerId: string;
|
|
75
84
|
/** Entries strictly above the player (API caps at 100; default 5). */
|
|
@@ -90,10 +99,7 @@ interface GetWindowAroundInput {
|
|
|
90
99
|
* import { Scorezilla, ScorezillaError } from 'scorezilla/server';
|
|
91
100
|
*
|
|
92
101
|
* const sz = new Scorezilla({
|
|
93
|
-
* secretKey:
|
|
94
|
-
* id: process.env.SCOREZILLA_KEY_ID!,
|
|
95
|
-
* secret: process.env.SCOREZILLA_KEY_SECRET!,
|
|
96
|
-
* },
|
|
102
|
+
* secretKey: process.env.SCOREZILLA_SECRET_KEY!, // sk_live_<keyId>_<random>
|
|
97
103
|
* });
|
|
98
104
|
*
|
|
99
105
|
* try {
|
|
@@ -139,4 +145,4 @@ declare class Scorezilla {
|
|
|
139
145
|
getWindowAround(input: GetWindowAroundInput): Promise<ApiSuccess<WindowAroundResponse>>;
|
|
140
146
|
}
|
|
141
147
|
|
|
142
|
-
export { ApiSuccess, type GetLeaderboardInput, type GetPlayerRankInput, type GetWindowAroundInput, LeaderboardResponse, PlayerRankResponse, Scorezilla, type SubmitScoreInput, SubmitScoreResponse, WindowAroundResponse };
|
|
148
|
+
export { ApiSuccess, type CancellableInput, type GetLeaderboardInput, type GetPlayerRankInput, type GetWindowAroundInput, LeaderboardResponse, PlayerRankResponse, Scorezilla, type SubmitScoreInput, SubmitScoreResponse, WindowAroundResponse };
|
package/dist/server.js
CHANGED
|
@@ -150,7 +150,9 @@ var ScorezillaError = class _ScorezillaError extends Error {
|
|
|
150
150
|
* {@link ScorezillaErrorCode}. For network errors, this is `'network_error'`;
|
|
151
151
|
* for aborts, `'aborted'`; for timeouts, `'timeout'`. */
|
|
152
152
|
code;
|
|
153
|
-
/** Sub-classifier — present on
|
|
153
|
+
/** Sub-classifier — present on:
|
|
154
|
+
* - `out_of_bounds`: `'below_min' | 'above_max'`
|
|
155
|
+
* - `usage_cap_exceeded`: `'over_cap' | 'suspended'`
|
|
154
156
|
* and possibly other codes in future minor releases. */
|
|
155
157
|
reason;
|
|
156
158
|
/** Seconds — present on `rate_limited`. Honored by the transport's retry
|
|
@@ -163,6 +165,20 @@ var ScorezillaError = class _ScorezillaError extends Error {
|
|
|
163
165
|
bound;
|
|
164
166
|
/** Which rate-limit layer fired on `rate_limited`. */
|
|
165
167
|
layer;
|
|
168
|
+
/** Tenant's billing tier — present on `usage_cap_exceeded`. */
|
|
169
|
+
tier;
|
|
170
|
+
/** The cap value crossed on `usage_cap_exceeded`. `0` indicates a
|
|
171
|
+
* suspended tenant. `undefined` on all other error codes. */
|
|
172
|
+
cap;
|
|
173
|
+
/** The post-increment submit count on `usage_cap_exceeded`. Always
|
|
174
|
+
* `> cap` when `reason === 'over_cap'`. */
|
|
175
|
+
count;
|
|
176
|
+
/** The period the count belongs to on `usage_cap_exceeded`, in `YYYY-MM`
|
|
177
|
+
* UTC form. */
|
|
178
|
+
period;
|
|
179
|
+
/** ISO-8601 timestamp of midnight UTC on the 1st of the next month —
|
|
180
|
+
* the counter's natural reset point on `usage_cap_exceeded`. */
|
|
181
|
+
resetsAt;
|
|
166
182
|
/** The underlying cause (e.g., a `TypeError: fetch failed`) for
|
|
167
183
|
* network/abort/timeout paths. `undefined` when the error came from a
|
|
168
184
|
* successfully-parsed API error body. */
|
|
@@ -177,6 +193,11 @@ var ScorezillaError = class _ScorezillaError extends Error {
|
|
|
177
193
|
this.requestId = truncateField(init.requestId);
|
|
178
194
|
this.bound = init.bound;
|
|
179
195
|
this.layer = truncateField(init.layer);
|
|
196
|
+
this.tier = truncateField(init.tier);
|
|
197
|
+
this.cap = init.cap;
|
|
198
|
+
this.count = init.count;
|
|
199
|
+
this.period = truncateField(init.period);
|
|
200
|
+
this.resetsAt = truncateField(init.resetsAt);
|
|
180
201
|
this.cause = init.cause;
|
|
181
202
|
Object.setPrototypeOf(this, _ScorezillaError.prototype);
|
|
182
203
|
if (typeof Error.captureStackTrace === "function") {
|
|
@@ -190,6 +211,22 @@ var ScorezillaError = class _ScorezillaError extends Error {
|
|
|
190
211
|
isRateLimited() {
|
|
191
212
|
return this.code === "rate_limited";
|
|
192
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* `true` when this error is a 402 / `usage_cap_exceeded`. The tenant
|
|
216
|
+
* has either hit their tier's monthly submit cap (`reason ===
|
|
217
|
+
* 'over_cap'`) or is suspended (`reason === 'suspended'`).
|
|
218
|
+
*
|
|
219
|
+
* Consumers SHOULD NOT auto-retry on this error — the cap doesn't lift
|
|
220
|
+
* until `resetsAt`. Surface to the developer with an upgrade prompt
|
|
221
|
+
* (over_cap) or contact-support message (suspended).
|
|
222
|
+
*/
|
|
223
|
+
isUsageCapExceeded() {
|
|
224
|
+
return this.code === "usage_cap_exceeded";
|
|
225
|
+
}
|
|
226
|
+
/** `true` when this error is a 402 + reason 'suspended' (vs over-cap). */
|
|
227
|
+
isSuspended() {
|
|
228
|
+
return this.code === "usage_cap_exceeded" && this.reason === "suspended";
|
|
229
|
+
}
|
|
193
230
|
/** `true` when this error is a 401 / `unauthorized` (or 403 / `forbidden`). */
|
|
194
231
|
isAuth() {
|
|
195
232
|
return this.code === "unauthorized" || this.code === "forbidden";
|
|
@@ -202,13 +239,21 @@ var ScorezillaError = class _ScorezillaError extends Error {
|
|
|
202
239
|
isOutOfBounds() {
|
|
203
240
|
return this.code === "out_of_bounds";
|
|
204
241
|
}
|
|
205
|
-
/** `true` for
|
|
206
|
-
*
|
|
242
|
+
/** `true` for the SDK's retryable conditions: pure network errors, 5xx, and
|
|
243
|
+
* 429. Deliberately excludes `timeout` and `aborted` — those are caller-
|
|
244
|
+
* observable terminal states, not transient. Aligned with `shouldRetryError`
|
|
245
|
+
* in `retry.ts` so a consumer mirroring the SDK's retry policy gets the
|
|
246
|
+
* same answer the transport does. */
|
|
207
247
|
isTransient() {
|
|
208
|
-
if (this.
|
|
248
|
+
if (this.code === "network_error") return true;
|
|
209
249
|
if (this.status >= 500 && this.status < 600) return true;
|
|
210
250
|
return this.isRateLimited();
|
|
211
251
|
}
|
|
252
|
+
/** `true` when this error is a 409 / `conflict` (idempotency-key conflict
|
|
253
|
+
* on retry). */
|
|
254
|
+
isConflict() {
|
|
255
|
+
return this.code === "conflict";
|
|
256
|
+
}
|
|
212
257
|
// ─── Factory ─────────────────────────────────────────────────────────
|
|
213
258
|
/**
|
|
214
259
|
* Build a `ScorezillaError` from a fetch round-trip outcome.
|
|
@@ -229,6 +274,13 @@ var ScorezillaError = class _ScorezillaError extends Error {
|
|
|
229
274
|
retryAfter: body.retryAfter,
|
|
230
275
|
bound: body.bound,
|
|
231
276
|
layer: body.layer,
|
|
277
|
+
// Usage-cap fields from `ApiError` (populated by the server on
|
|
278
|
+
// 402 responses; undefined on other errors).
|
|
279
|
+
tier: body.tier,
|
|
280
|
+
cap: body.cap,
|
|
281
|
+
count: body.count,
|
|
282
|
+
period: body.period,
|
|
283
|
+
resetsAt: body.resetsAt,
|
|
232
284
|
requestId,
|
|
233
285
|
cause
|
|
234
286
|
});
|
|
@@ -270,8 +322,10 @@ var ScorezillaError = class _ScorezillaError extends Error {
|
|
|
270
322
|
};
|
|
271
323
|
function codeForStatus(status) {
|
|
272
324
|
if (status === 401) return "unauthorized";
|
|
325
|
+
if (status === 402) return "usage_cap_exceeded";
|
|
273
326
|
if (status === 403) return "forbidden";
|
|
274
327
|
if (status === 404) return "not_found";
|
|
328
|
+
if (status === 409) return "conflict";
|
|
275
329
|
if (status === 422) return "out_of_bounds";
|
|
276
330
|
if (status === 429) return "rate_limited";
|
|
277
331
|
if (status >= 500) return "internal_error";
|
|
@@ -382,6 +436,7 @@ async function request(opts) {
|
|
|
382
436
|
}
|
|
383
437
|
const response = await fetchImpl(url, init);
|
|
384
438
|
if (response.ok) {
|
|
439
|
+
warnOnDeprecationOnce(response, opts.warnImpl);
|
|
385
440
|
return await parseJson(response);
|
|
386
441
|
}
|
|
387
442
|
const body = await safelyParseErrorBody(response);
|
|
@@ -493,6 +548,29 @@ function readRetryAfter(response) {
|
|
|
493
548
|
const n = Number(raw);
|
|
494
549
|
return Number.isFinite(n) && n >= 0 ? n : void 0;
|
|
495
550
|
}
|
|
551
|
+
var seenDeprecations = /* @__PURE__ */ new Set();
|
|
552
|
+
function warnOnDeprecationOnce(response, warnImpl) {
|
|
553
|
+
const deprecation = response.headers.get("Deprecation");
|
|
554
|
+
const sunset = response.headers.get("Sunset");
|
|
555
|
+
if (!deprecation && !sunset) return;
|
|
556
|
+
const link = response.headers.get("Link") ?? "";
|
|
557
|
+
const key = `${deprecation ?? ""}|${sunset ?? ""}|${link}`;
|
|
558
|
+
if (seenDeprecations.has(key)) return;
|
|
559
|
+
seenDeprecations.add(key);
|
|
560
|
+
const detail = [];
|
|
561
|
+
if (deprecation === "true" || deprecation) detail.push(`Deprecation: ${deprecation}`);
|
|
562
|
+
if (sunset) detail.push(`Sunset: ${sunset}`);
|
|
563
|
+
if (link) {
|
|
564
|
+
const m = link.match(/<([^>]+)>/);
|
|
565
|
+
if (m) detail.push(`Docs: ${m[1]}`);
|
|
566
|
+
}
|
|
567
|
+
const message = `[scorezilla-sdk] API responded with deprecation signal: ${detail.join(" \xB7 ")}. Upgrade your SDK before the sunset date.`;
|
|
568
|
+
if (warnImpl) {
|
|
569
|
+
warnImpl(message);
|
|
570
|
+
} else {
|
|
571
|
+
console.warn(message);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
496
574
|
function combineSignalsWithTimeout(caller, timeoutMs) {
|
|
497
575
|
const ctrl = new AbortController();
|
|
498
576
|
let didTimeOut = false;
|
|
@@ -550,7 +628,7 @@ var Scorezilla = class _Scorezilla {
|
|
|
550
628
|
* Mirrors the static on the public-key client so consumers can log
|
|
551
629
|
* the running SDK build the same way regardless of which surface
|
|
552
630
|
* they imported. */
|
|
553
|
-
static version = "0.
|
|
631
|
+
static version = "0.2.0";
|
|
554
632
|
#keyId;
|
|
555
633
|
#secret;
|
|
556
634
|
#baseUrl;
|
|
@@ -562,6 +640,7 @@ var Scorezilla = class _Scorezilla {
|
|
|
562
640
|
#timeoutMs;
|
|
563
641
|
#maxRetries;
|
|
564
642
|
#sleepImpl;
|
|
643
|
+
#warnImpl;
|
|
565
644
|
#userAgent;
|
|
566
645
|
constructor(config) {
|
|
567
646
|
if (isRealBrowserEnvironment()) {
|
|
@@ -584,6 +663,7 @@ var Scorezilla = class _Scorezilla {
|
|
|
584
663
|
this.#timeoutMs = config.timeoutMs;
|
|
585
664
|
this.#maxRetries = config.maxRetries;
|
|
586
665
|
this.#sleepImpl = config.sleepImpl;
|
|
666
|
+
this.#warnImpl = config.warn;
|
|
587
667
|
this.#userAgent = config.userAgent ?? defaultUserAgent(_Scorezilla.version);
|
|
588
668
|
}
|
|
589
669
|
/**
|
|
@@ -608,7 +688,8 @@ var Scorezilla = class _Scorezilla {
|
|
|
608
688
|
playerId: input.playerId,
|
|
609
689
|
score: input.score,
|
|
610
690
|
...input.metadata !== void 0 ? { metadata: input.metadata } : {}
|
|
611
|
-
}
|
|
691
|
+
},
|
|
692
|
+
signal: input.signal
|
|
612
693
|
});
|
|
613
694
|
}
|
|
614
695
|
/** Fetch the top-N entries on a board. */
|
|
@@ -618,14 +699,16 @@ var Scorezilla = class _Scorezilla {
|
|
|
618
699
|
...input.top !== void 0 ? { top: input.top } : {},
|
|
619
700
|
...input.offset !== void 0 ? { offset: input.offset } : {}
|
|
620
701
|
}),
|
|
621
|
-
method: "GET"
|
|
702
|
+
method: "GET",
|
|
703
|
+
signal: input.signal
|
|
622
704
|
});
|
|
623
705
|
}
|
|
624
706
|
/** Look up a single player's rank on a board. */
|
|
625
707
|
async getPlayerRank(input) {
|
|
626
708
|
return this.#request({
|
|
627
709
|
path: getPlayerRankPath(input.boardId, input.playerId),
|
|
628
|
-
method: "GET"
|
|
710
|
+
method: "GET",
|
|
711
|
+
signal: input.signal
|
|
629
712
|
});
|
|
630
713
|
}
|
|
631
714
|
/** Fetch the slice of entries surrounding a player. */
|
|
@@ -635,7 +718,8 @@ var Scorezilla = class _Scorezilla {
|
|
|
635
718
|
...input.before !== void 0 ? { before: input.before } : {},
|
|
636
719
|
...input.after !== void 0 ? { after: input.after } : {}
|
|
637
720
|
}),
|
|
638
|
-
method: "GET"
|
|
721
|
+
method: "GET",
|
|
722
|
+
signal: input.signal
|
|
639
723
|
});
|
|
640
724
|
}
|
|
641
725
|
/**
|
|
@@ -666,7 +750,9 @@ var Scorezilla = class _Scorezilla {
|
|
|
666
750
|
})
|
|
667
751
|
};
|
|
668
752
|
if (opts.body !== void 0) requestOpts.body = opts.body;
|
|
753
|
+
if (opts.signal !== void 0) requestOpts.signal = opts.signal;
|
|
669
754
|
if (this.#fetchImpl !== void 0) requestOpts.fetchImpl = this.#fetchImpl;
|
|
755
|
+
if (this.#warnImpl !== void 0) requestOpts.warnImpl = this.#warnImpl;
|
|
670
756
|
if (this.#timeoutMs !== void 0) requestOpts.timeoutMs = this.#timeoutMs;
|
|
671
757
|
if (this.#maxRetries !== void 0 || this.#sleepImpl !== void 0) {
|
|
672
758
|
requestOpts.retry = {
|