scorezilla 0.1.0-next.0 → 0.1.0-next.3

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/dist/index.js CHANGED
@@ -16,24 +16,10 @@ function validateConfig(cfg) {
16
16
  }
17
17
  let auth;
18
18
  if (hasPublic) {
19
- const pk = cfg.publicKey;
20
- if (typeof pk !== "string" || !PUBLIC_KEY_PATTERN.test(pk)) {
21
- throw new Error(
22
- `scorezilla: publicKey must match ${PUBLIC_KEY_PATTERN.toString()} (got: ${typeof pk === "string" ? pk.slice(0, 12) + "\u2026" : typeof pk})`
23
- );
24
- }
25
- auth = { kind: "public", key: pk };
19
+ auth = { kind: "public", key: validatePublicKeyValue(cfg.publicKey) };
26
20
  } else {
27
- const sk = cfg.secretKey;
28
- if (!sk || typeof sk !== "object" || typeof sk.id !== "string" || typeof sk.secret !== "string") {
29
- throw new Error("scorezilla: secretKey must be an object with string `id` and `secret`");
30
- }
31
- if (!sk.secret.startsWith(SECRET_KEY_PREFIX)) {
32
- throw new Error(
33
- `scorezilla: secretKey.secret must start with "${SECRET_KEY_PREFIX}" (live keys only)`
34
- );
35
- }
36
- auth = { kind: "secret", keyId: sk.id, secret: sk.secret };
21
+ const resolved = validateSecretKey(cfg);
22
+ auth = { kind: "secret", keyId: resolved.keyId, secret: resolved.secret };
37
23
  }
38
24
  const baseUrlRaw = cfg.baseUrl ?? DEFAULT_BASE_URL;
39
25
  if (typeof baseUrlRaw !== "string" || baseUrlRaw.length === 0) {
@@ -44,10 +30,40 @@ function validateConfig(cfg) {
44
30
  fetch: cfg.fetch,
45
31
  timeoutMs: cfg.timeoutMs,
46
32
  maxRetries: cfg.maxRetries,
33
+ sleepImpl: cfg.sleepImpl,
47
34
  userAgent: cfg.userAgent,
48
35
  auth
49
36
  };
50
37
  }
38
+ function validatePublicKeyValue(pk) {
39
+ if (typeof pk !== "string" || !PUBLIC_KEY_PATTERN.test(pk)) {
40
+ const shape = typeof pk === "string" ? `string of length ${pk.length}` : typeof pk;
41
+ throw new Error(
42
+ `scorezilla: publicKey must match ${PUBLIC_KEY_PATTERN.toString()} (got: ${shape})`
43
+ );
44
+ }
45
+ return pk;
46
+ }
47
+ var SECRET_KEY_PATTERN = /^sk_live_([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})_[A-Za-z0-9]+$/;
48
+ function validateSecretKey(cfg) {
49
+ if (!cfg || typeof cfg !== "object") {
50
+ throw new Error("scorezilla/server: config must be an object with a secretKey field");
51
+ }
52
+ const sk = cfg.secretKey;
53
+ if (typeof sk !== "string") {
54
+ throw new Error(
55
+ `scorezilla/server: secretKey must be a single string of the shape ${SECRET_KEY_PREFIX}<keyId>_<random> (got: ${typeof sk})`
56
+ );
57
+ }
58
+ const match = SECRET_KEY_PATTERN.exec(sk);
59
+ if (!match) {
60
+ const shape = `string of length ${sk.length}`;
61
+ throw new Error(
62
+ `scorezilla/server: secretKey must match ${SECRET_KEY_PATTERN.toString()} (got: ${shape}). v0.1.0-next.2 switched to a single-token format \u2014 if you have a pre-next.2 pair, issue a fresh key in the dashboard to upgrade.`
63
+ );
64
+ }
65
+ return { keyId: match[1], secret: sk };
66
+ }
51
67
 
52
68
  // src/paths.ts
53
69
  function encodeSegment(value, label) {
@@ -87,6 +103,12 @@ function truncateMessage(raw) {
87
103
  const sliceEnd = Math.max(0, MESSAGE_MAX_CHARS - TRUNCATION_SUFFIX.length);
88
104
  return raw.slice(0, sliceEnd) + TRUNCATION_SUFFIX;
89
105
  }
106
+ function truncateField(raw) {
107
+ if (typeof raw !== "string") return void 0;
108
+ if (raw.length <= MESSAGE_MAX_CHARS) return raw;
109
+ const sliceEnd = Math.max(0, MESSAGE_MAX_CHARS - TRUNCATION_SUFFIX.length);
110
+ return raw.slice(0, sliceEnd) + TRUNCATION_SUFFIX;
111
+ }
90
112
  var ScorezillaError = class _ScorezillaError extends Error {
91
113
  /** HTTP status of the response, or {@link STATUS_NETWORK_ERROR} (0) for
92
114
  * network / abort / timeout. */
@@ -117,11 +139,11 @@ var ScorezillaError = class _ScorezillaError extends Error {
117
139
  this.name = "ScorezillaError";
118
140
  this.status = init.status;
119
141
  this.code = init.code;
120
- this.reason = init.reason;
142
+ this.reason = truncateField(init.reason);
121
143
  this.retryAfter = init.retryAfter;
122
- this.requestId = init.requestId;
144
+ this.requestId = truncateField(init.requestId);
123
145
  this.bound = init.bound;
124
- this.layer = init.layer;
146
+ this.layer = truncateField(init.layer);
125
147
  this.cause = init.cause;
126
148
  Object.setPrototypeOf(this, _ScorezillaError.prototype);
127
149
  if (typeof Error.captureStackTrace === "function") {
@@ -251,8 +273,9 @@ function shouldRetryError(err) {
251
273
  function generateIdempotencyKey() {
252
274
  const c = globalThis.crypto;
253
275
  if (!c || typeof c.randomUUID !== "function") {
254
- throw new Error(
255
- "scorezilla: globalThis.crypto.randomUUID is unavailable. The SDK requires Node \u2265 20 or a modern browser. Check your runtime."
276
+ throw new ScorezillaError(
277
+ "scorezilla: globalThis.crypto.randomUUID is unavailable. The SDK requires Node \u2265 20 or a modern browser. Check your runtime.",
278
+ { status: 0, code: "internal_error" }
256
279
  );
257
280
  }
258
281
  return c.randomUUID();
@@ -307,16 +330,24 @@ async function request(opts) {
307
330
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
308
331
  const combined = combineSignalsWithTimeout(opts.signal, timeoutMs);
309
332
  try {
333
+ const bodyString = opts.body !== void 0 ? JSON.stringify(opts.body) : "";
334
+ const perAttemptHeaders = { ...opts.headers ?? {} };
335
+ if (opts.signRequest) {
336
+ perAttemptHeaders.Authorization = await opts.signRequest({
337
+ method: opts.method,
338
+ pathAndQuery: opts.path,
339
+ body: bodyString
340
+ });
341
+ }
310
342
  const init = {
311
343
  method: opts.method,
312
- headers: buildHeaders(opts, idempotencyKey),
344
+ headers: buildHeaders({ ...opts, headers: perAttemptHeaders }, idempotencyKey),
313
345
  signal: combined.signal
314
346
  };
315
347
  if (opts.body !== void 0) {
316
- init.body = JSON.stringify(opts.body);
348
+ init.body = bodyString;
317
349
  }
318
350
  const response = await fetchImpl(url, init);
319
- combined.cleanup();
320
351
  if (response.ok) {
321
352
  return await parseJson(response);
322
353
  }
@@ -335,7 +366,6 @@ async function request(opts) {
335
366
  }
336
367
  throw err;
337
368
  } catch (caught) {
338
- combined.cleanup();
339
369
  if (caught instanceof ScorezillaError) {
340
370
  if (shouldRetryError(caught) && attempt < maxRetries) {
341
371
  const delay = nextDelay(attempt, void 0, random);
@@ -353,6 +383,8 @@ async function request(opts) {
353
383
  continue;
354
384
  }
355
385
  throw mapped;
386
+ } finally {
387
+ combined.cleanup();
356
388
  }
357
389
  }
358
390
  throw lastError ?? new ScorezillaError("Request failed after retries", {
@@ -378,16 +410,38 @@ function buildHeaders(opts, idempotencyKey) {
378
410
  return headers;
379
411
  }
380
412
  async function parseJson(response) {
413
+ const requestId = response.headers.get("X-Request-Id") ?? void 0;
414
+ let parsed;
381
415
  try {
382
- return await response.json();
416
+ parsed = await response.json();
383
417
  } catch (cause) {
384
418
  throw new ScorezillaError("Response body was not valid JSON", {
385
419
  status: response.status,
386
420
  code: "invalid_json",
387
- requestId: response.headers.get("X-Request-Id") ?? void 0,
421
+ requestId,
388
422
  cause
389
423
  });
390
424
  }
425
+ if (parsed === null || typeof parsed !== "object") {
426
+ const observed = parsed === null ? "null" : typeof parsed;
427
+ throw new ScorezillaError(`Response body was not a JSON object (got ${observed})`, {
428
+ status: response.status,
429
+ code: "invalid_json",
430
+ requestId
431
+ });
432
+ }
433
+ const okField = parsed.ok;
434
+ if (okField !== true) {
435
+ throw new ScorezillaError(
436
+ `Response body on a 2xx is missing the \`ok: true\` discriminator (got ok=${String(okField)})`,
437
+ {
438
+ status: response.status,
439
+ code: "invalid_json",
440
+ requestId
441
+ }
442
+ );
443
+ }
444
+ return parsed;
391
445
  }
392
446
  async function safelyParseErrorBody(response) {
393
447
  try {
@@ -497,7 +551,7 @@ function validateMetadata(metadata) {
497
551
  }
498
552
  var Scorezilla = class _Scorezilla {
499
553
  /** The package version, injected at build time from `package.json`. */
500
- static version = "0.1.0-next.0";
554
+ static version = "0.1.0-next.3";
501
555
  #config;
502
556
  #userAgent;
503
557
  #authHeader;
@@ -662,8 +716,11 @@ var Scorezilla = class _Scorezilla {
662
716
  if (opts.body !== void 0) requestOpts.body = opts.body;
663
717
  if (this.#config.fetch !== void 0) requestOpts.fetchImpl = this.#config.fetch;
664
718
  if (this.#config.timeoutMs !== void 0) requestOpts.timeoutMs = this.#config.timeoutMs;
665
- if (this.#config.maxRetries !== void 0) {
666
- requestOpts.retry = { maxRetries: this.#config.maxRetries };
719
+ if (this.#config.maxRetries !== void 0 || this.#config.sleepImpl !== void 0) {
720
+ requestOpts.retry = {
721
+ ...this.#config.maxRetries !== void 0 ? { maxRetries: this.#config.maxRetries } : {},
722
+ ...this.#config.sleepImpl !== void 0 ? { sleepImpl: this.#config.sleepImpl } : {}
723
+ };
667
724
  }
668
725
  return request(requestOpts);
669
726
  }
@@ -673,7 +730,7 @@ function createClient(config) {
673
730
  }
674
731
 
675
732
  // src/index.ts
676
- var SDK_VERSION = "0.1.0-next.0";
733
+ var SDK_VERSION = "0.1.0-next.3";
677
734
 
678
735
  export { SDK_VERSION, Scorezilla, ScorezillaError, createClient, detectRuntime };
679
736
  //# sourceMappingURL=index.js.map