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/index.cjs
CHANGED
|
@@ -33,6 +33,7 @@ function validateConfig(cfg) {
|
|
|
33
33
|
timeoutMs: cfg.timeoutMs,
|
|
34
34
|
maxRetries: cfg.maxRetries,
|
|
35
35
|
sleepImpl: cfg.sleepImpl,
|
|
36
|
+
warn: cfg.warn,
|
|
36
37
|
userAgent: cfg.userAgent,
|
|
37
38
|
auth
|
|
38
39
|
};
|
|
@@ -119,7 +120,9 @@ var ScorezillaError = class _ScorezillaError extends Error {
|
|
|
119
120
|
* {@link ScorezillaErrorCode}. For network errors, this is `'network_error'`;
|
|
120
121
|
* for aborts, `'aborted'`; for timeouts, `'timeout'`. */
|
|
121
122
|
code;
|
|
122
|
-
/** Sub-classifier — present on
|
|
123
|
+
/** Sub-classifier — present on:
|
|
124
|
+
* - `out_of_bounds`: `'below_min' | 'above_max'`
|
|
125
|
+
* - `usage_cap_exceeded`: `'over_cap' | 'suspended'`
|
|
123
126
|
* and possibly other codes in future minor releases. */
|
|
124
127
|
reason;
|
|
125
128
|
/** Seconds — present on `rate_limited`. Honored by the transport's retry
|
|
@@ -132,6 +135,20 @@ var ScorezillaError = class _ScorezillaError extends Error {
|
|
|
132
135
|
bound;
|
|
133
136
|
/** Which rate-limit layer fired on `rate_limited`. */
|
|
134
137
|
layer;
|
|
138
|
+
/** Tenant's billing tier — present on `usage_cap_exceeded`. */
|
|
139
|
+
tier;
|
|
140
|
+
/** The cap value crossed on `usage_cap_exceeded`. `0` indicates a
|
|
141
|
+
* suspended tenant. `undefined` on all other error codes. */
|
|
142
|
+
cap;
|
|
143
|
+
/** The post-increment submit count on `usage_cap_exceeded`. Always
|
|
144
|
+
* `> cap` when `reason === 'over_cap'`. */
|
|
145
|
+
count;
|
|
146
|
+
/** The period the count belongs to on `usage_cap_exceeded`, in `YYYY-MM`
|
|
147
|
+
* UTC form. */
|
|
148
|
+
period;
|
|
149
|
+
/** ISO-8601 timestamp of midnight UTC on the 1st of the next month —
|
|
150
|
+
* the counter's natural reset point on `usage_cap_exceeded`. */
|
|
151
|
+
resetsAt;
|
|
135
152
|
/** The underlying cause (e.g., a `TypeError: fetch failed`) for
|
|
136
153
|
* network/abort/timeout paths. `undefined` when the error came from a
|
|
137
154
|
* successfully-parsed API error body. */
|
|
@@ -146,6 +163,11 @@ var ScorezillaError = class _ScorezillaError extends Error {
|
|
|
146
163
|
this.requestId = truncateField(init.requestId);
|
|
147
164
|
this.bound = init.bound;
|
|
148
165
|
this.layer = truncateField(init.layer);
|
|
166
|
+
this.tier = truncateField(init.tier);
|
|
167
|
+
this.cap = init.cap;
|
|
168
|
+
this.count = init.count;
|
|
169
|
+
this.period = truncateField(init.period);
|
|
170
|
+
this.resetsAt = truncateField(init.resetsAt);
|
|
149
171
|
this.cause = init.cause;
|
|
150
172
|
Object.setPrototypeOf(this, _ScorezillaError.prototype);
|
|
151
173
|
if (typeof Error.captureStackTrace === "function") {
|
|
@@ -159,6 +181,22 @@ var ScorezillaError = class _ScorezillaError extends Error {
|
|
|
159
181
|
isRateLimited() {
|
|
160
182
|
return this.code === "rate_limited";
|
|
161
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* `true` when this error is a 402 / `usage_cap_exceeded`. The tenant
|
|
186
|
+
* has either hit their tier's monthly submit cap (`reason ===
|
|
187
|
+
* 'over_cap'`) or is suspended (`reason === 'suspended'`).
|
|
188
|
+
*
|
|
189
|
+
* Consumers SHOULD NOT auto-retry on this error — the cap doesn't lift
|
|
190
|
+
* until `resetsAt`. Surface to the developer with an upgrade prompt
|
|
191
|
+
* (over_cap) or contact-support message (suspended).
|
|
192
|
+
*/
|
|
193
|
+
isUsageCapExceeded() {
|
|
194
|
+
return this.code === "usage_cap_exceeded";
|
|
195
|
+
}
|
|
196
|
+
/** `true` when this error is a 402 + reason 'suspended' (vs over-cap). */
|
|
197
|
+
isSuspended() {
|
|
198
|
+
return this.code === "usage_cap_exceeded" && this.reason === "suspended";
|
|
199
|
+
}
|
|
162
200
|
/** `true` when this error is a 401 / `unauthorized` (or 403 / `forbidden`). */
|
|
163
201
|
isAuth() {
|
|
164
202
|
return this.code === "unauthorized" || this.code === "forbidden";
|
|
@@ -171,13 +209,21 @@ var ScorezillaError = class _ScorezillaError extends Error {
|
|
|
171
209
|
isOutOfBounds() {
|
|
172
210
|
return this.code === "out_of_bounds";
|
|
173
211
|
}
|
|
174
|
-
/** `true` for
|
|
175
|
-
*
|
|
212
|
+
/** `true` for the SDK's retryable conditions: pure network errors, 5xx, and
|
|
213
|
+
* 429. Deliberately excludes `timeout` and `aborted` — those are caller-
|
|
214
|
+
* observable terminal states, not transient. Aligned with `shouldRetryError`
|
|
215
|
+
* in `retry.ts` so a consumer mirroring the SDK's retry policy gets the
|
|
216
|
+
* same answer the transport does. */
|
|
176
217
|
isTransient() {
|
|
177
|
-
if (this.
|
|
218
|
+
if (this.code === "network_error") return true;
|
|
178
219
|
if (this.status >= 500 && this.status < 600) return true;
|
|
179
220
|
return this.isRateLimited();
|
|
180
221
|
}
|
|
222
|
+
/** `true` when this error is a 409 / `conflict` (idempotency-key conflict
|
|
223
|
+
* on retry). */
|
|
224
|
+
isConflict() {
|
|
225
|
+
return this.code === "conflict";
|
|
226
|
+
}
|
|
181
227
|
// ─── Factory ─────────────────────────────────────────────────────────
|
|
182
228
|
/**
|
|
183
229
|
* Build a `ScorezillaError` from a fetch round-trip outcome.
|
|
@@ -198,6 +244,13 @@ var ScorezillaError = class _ScorezillaError extends Error {
|
|
|
198
244
|
retryAfter: body.retryAfter,
|
|
199
245
|
bound: body.bound,
|
|
200
246
|
layer: body.layer,
|
|
247
|
+
// Usage-cap fields from `ApiError` (populated by the server on
|
|
248
|
+
// 402 responses; undefined on other errors).
|
|
249
|
+
tier: body.tier,
|
|
250
|
+
cap: body.cap,
|
|
251
|
+
count: body.count,
|
|
252
|
+
period: body.period,
|
|
253
|
+
resetsAt: body.resetsAt,
|
|
201
254
|
requestId,
|
|
202
255
|
cause
|
|
203
256
|
});
|
|
@@ -239,8 +292,10 @@ var ScorezillaError = class _ScorezillaError extends Error {
|
|
|
239
292
|
};
|
|
240
293
|
function codeForStatus(status) {
|
|
241
294
|
if (status === 401) return "unauthorized";
|
|
295
|
+
if (status === 402) return "usage_cap_exceeded";
|
|
242
296
|
if (status === 403) return "forbidden";
|
|
243
297
|
if (status === 404) return "not_found";
|
|
298
|
+
if (status === 409) return "conflict";
|
|
244
299
|
if (status === 422) return "out_of_bounds";
|
|
245
300
|
if (status === 429) return "rate_limited";
|
|
246
301
|
if (status >= 500) return "internal_error";
|
|
@@ -351,6 +406,7 @@ async function request(opts) {
|
|
|
351
406
|
}
|
|
352
407
|
const response = await fetchImpl(url, init);
|
|
353
408
|
if (response.ok) {
|
|
409
|
+
warnOnDeprecationOnce(response, opts.warnImpl);
|
|
354
410
|
return await parseJson(response);
|
|
355
411
|
}
|
|
356
412
|
const body = await safelyParseErrorBody(response);
|
|
@@ -462,6 +518,29 @@ function readRetryAfter(response) {
|
|
|
462
518
|
const n = Number(raw);
|
|
463
519
|
return Number.isFinite(n) && n >= 0 ? n : void 0;
|
|
464
520
|
}
|
|
521
|
+
var seenDeprecations = /* @__PURE__ */ new Set();
|
|
522
|
+
function warnOnDeprecationOnce(response, warnImpl) {
|
|
523
|
+
const deprecation = response.headers.get("Deprecation");
|
|
524
|
+
const sunset = response.headers.get("Sunset");
|
|
525
|
+
if (!deprecation && !sunset) return;
|
|
526
|
+
const link = response.headers.get("Link") ?? "";
|
|
527
|
+
const key = `${deprecation ?? ""}|${sunset ?? ""}|${link}`;
|
|
528
|
+
if (seenDeprecations.has(key)) return;
|
|
529
|
+
seenDeprecations.add(key);
|
|
530
|
+
const detail = [];
|
|
531
|
+
if (deprecation === "true" || deprecation) detail.push(`Deprecation: ${deprecation}`);
|
|
532
|
+
if (sunset) detail.push(`Sunset: ${sunset}`);
|
|
533
|
+
if (link) {
|
|
534
|
+
const m = link.match(/<([^>]+)>/);
|
|
535
|
+
if (m) detail.push(`Docs: ${m[1]}`);
|
|
536
|
+
}
|
|
537
|
+
const message = `[scorezilla-sdk] API responded with deprecation signal: ${detail.join(" \xB7 ")}. Upgrade your SDK before the sunset date.`;
|
|
538
|
+
if (warnImpl) {
|
|
539
|
+
warnImpl(message);
|
|
540
|
+
} else {
|
|
541
|
+
console.warn(message);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
465
544
|
function combineSignalsWithTimeout(caller, timeoutMs) {
|
|
466
545
|
const ctrl = new AbortController();
|
|
467
546
|
let didTimeOut = false;
|
|
@@ -550,10 +629,11 @@ function validateMetadata(metadata) {
|
|
|
550
629
|
`scorezilla: metadata exceeds ${METADATA_MAX_BYTES} bytes (got ${byteLength} bytes when JSON-stringified)`
|
|
551
630
|
);
|
|
552
631
|
}
|
|
632
|
+
return serialized;
|
|
553
633
|
}
|
|
554
634
|
var Scorezilla = class _Scorezilla {
|
|
555
635
|
/** The package version, injected at build time from `package.json`. */
|
|
556
|
-
static version = "0.
|
|
636
|
+
static version = "0.2.0";
|
|
557
637
|
#config;
|
|
558
638
|
#userAgent;
|
|
559
639
|
#authHeader;
|
|
@@ -611,7 +691,8 @@ var Scorezilla = class _Scorezilla {
|
|
|
611
691
|
return this.#request({
|
|
612
692
|
path: submitScorePath(input.boardId),
|
|
613
693
|
method: "POST",
|
|
614
|
-
body
|
|
694
|
+
body,
|
|
695
|
+
signal: input.signal
|
|
615
696
|
});
|
|
616
697
|
}
|
|
617
698
|
/**
|
|
@@ -635,7 +716,8 @@ var Scorezilla = class _Scorezilla {
|
|
|
635
716
|
if (input.offset !== void 0) q.offset = input.offset;
|
|
636
717
|
return this.#request({
|
|
637
718
|
path: getLeaderboardPath(input.boardId, q),
|
|
638
|
-
method: "GET"
|
|
719
|
+
method: "GET",
|
|
720
|
+
signal: input.signal
|
|
639
721
|
});
|
|
640
722
|
}
|
|
641
723
|
/**
|
|
@@ -664,7 +746,8 @@ var Scorezilla = class _Scorezilla {
|
|
|
664
746
|
async getPlayerRank(input) {
|
|
665
747
|
return this.#request({
|
|
666
748
|
path: getPlayerRankPath(input.boardId, input.playerId),
|
|
667
|
-
method: "GET"
|
|
749
|
+
method: "GET",
|
|
750
|
+
signal: input.signal
|
|
668
751
|
});
|
|
669
752
|
}
|
|
670
753
|
/**
|
|
@@ -689,7 +772,8 @@ var Scorezilla = class _Scorezilla {
|
|
|
689
772
|
if (input.after !== void 0) q.after = input.after;
|
|
690
773
|
return this.#request({
|
|
691
774
|
path: getWindowAroundPath(input.boardId, input.playerId, q),
|
|
692
|
-
method: "GET"
|
|
775
|
+
method: "GET",
|
|
776
|
+
signal: input.signal
|
|
693
777
|
});
|
|
694
778
|
}
|
|
695
779
|
// ─── Internal ────────────────────────────────────────────────────────
|
|
@@ -716,7 +800,9 @@ var Scorezilla = class _Scorezilla {
|
|
|
716
800
|
headers
|
|
717
801
|
};
|
|
718
802
|
if (opts.body !== void 0) requestOpts.body = opts.body;
|
|
803
|
+
if (opts.signal !== void 0) requestOpts.signal = opts.signal;
|
|
719
804
|
if (this.#config.fetch !== void 0) requestOpts.fetchImpl = this.#config.fetch;
|
|
805
|
+
if (this.#config.warn !== void 0) requestOpts.warnImpl = this.#config.warn;
|
|
720
806
|
if (this.#config.timeoutMs !== void 0) requestOpts.timeoutMs = this.#config.timeoutMs;
|
|
721
807
|
if (this.#config.maxRetries !== void 0 || this.#config.sleepImpl !== void 0) {
|
|
722
808
|
requestOpts.retry = {
|
|
@@ -732,7 +818,7 @@ function createClient(config) {
|
|
|
732
818
|
}
|
|
733
819
|
|
|
734
820
|
// src/index.ts
|
|
735
|
-
var SDK_VERSION = "0.
|
|
821
|
+
var SDK_VERSION = "0.2.0";
|
|
736
822
|
|
|
737
823
|
exports.SDK_VERSION = SDK_VERSION;
|
|
738
824
|
exports.Scorezilla = Scorezilla;
|