@whittlelabs/sifter 0.27.4 → 0.27.5

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.
Files changed (3) hide show
  1. package/bin.js +124 -4
  2. package/bin.js.map +4 -4
  3. package/package.json +1 -1
package/bin.js CHANGED
@@ -15318,12 +15318,92 @@ ${issues}`);
15318
15318
  }
15319
15319
  });
15320
15320
 
15321
+ // ../../packages/jobs/dist/response.js
15322
+ var require_response = __commonJS({
15323
+ "../../packages/jobs/dist/response.js"(exports2) {
15324
+ "use strict";
15325
+ Object.defineProperty(exports2, "__esModule", { value: true });
15326
+ exports2.NonJsonResponseError = exports2.ServiceUnavailableError = void 0;
15327
+ exports2.readJsonResponse = readJsonResponse;
15328
+ var EXCERPT_LIMIT = 120;
15329
+ var UNAVAILABLE_STATUSES = /* @__PURE__ */ new Set([502, 503, 504]);
15330
+ var UnreadableResponseError = class extends Error {
15331
+ host;
15332
+ statusCode;
15333
+ statusText;
15334
+ contentType;
15335
+ bodyExcerpt;
15336
+ constructor(message, detail) {
15337
+ super(message);
15338
+ this.host = detail.host;
15339
+ this.statusCode = detail.statusCode;
15340
+ this.statusText = detail.statusText;
15341
+ this.contentType = detail.contentType;
15342
+ this.bodyExcerpt = detail.bodyExcerpt;
15343
+ }
15344
+ };
15345
+ var ServiceUnavailableError = class extends UnreadableResponseError {
15346
+ code = "SERVICE_UNAVAILABLE";
15347
+ constructor(detail) {
15348
+ super(`${detail.host} answered ${formatStatus(detail)} (content-type: ${detail.contentType ?? "none"})${detail.bodyExcerpt ? `: ${detail.bodyExcerpt}` : ""}`, detail);
15349
+ this.name = "ServiceUnavailableError";
15350
+ }
15351
+ };
15352
+ exports2.ServiceUnavailableError = ServiceUnavailableError;
15353
+ var NonJsonResponseError = class extends UnreadableResponseError {
15354
+ code = "RESPONSE_NOT_JSON";
15355
+ constructor(detail) {
15356
+ super(`${detail.host} answered ${formatStatus(detail)} with content-type ${detail.contentType ?? "none"}, not JSON${detail.bodyExcerpt ? `: ${detail.bodyExcerpt}` : ""}`, detail);
15357
+ this.name = "NonJsonResponseError";
15358
+ }
15359
+ };
15360
+ exports2.NonJsonResponseError = NonJsonResponseError;
15361
+ function formatStatus(detail) {
15362
+ return detail.statusText ? `${detail.statusCode} ${detail.statusText}` : `${detail.statusCode}`;
15363
+ }
15364
+ function isJson(contentType) {
15365
+ if (!contentType)
15366
+ return false;
15367
+ const essence = contentType.split(";")[0].trim().toLowerCase();
15368
+ return essence === "application/json" || essence.endsWith("+json");
15369
+ }
15370
+ function excerpt(body) {
15371
+ const collapsed = body.replace(/\s+/g, " ").trim();
15372
+ return collapsed.length > EXCERPT_LIMIT ? `${collapsed.slice(0, EXCERPT_LIMIT)}...` : collapsed;
15373
+ }
15374
+ function hostOf(url) {
15375
+ try {
15376
+ return new URL(url).host;
15377
+ } catch {
15378
+ return url;
15379
+ }
15380
+ }
15381
+ async function readJsonResponse(response, url) {
15382
+ const contentType = response.headers.get("content-type");
15383
+ const unavailable = UNAVAILABLE_STATUSES.has(response.status);
15384
+ if (unavailable || !isJson(contentType)) {
15385
+ const body = await response.text().catch(() => "");
15386
+ const detail = {
15387
+ host: hostOf(url),
15388
+ statusCode: response.status,
15389
+ statusText: response.statusText ?? "",
15390
+ contentType,
15391
+ bodyExcerpt: excerpt(body)
15392
+ };
15393
+ throw unavailable ? new ServiceUnavailableError(detail) : new NonJsonResponseError(detail);
15394
+ }
15395
+ return await response.json();
15396
+ }
15397
+ }
15398
+ });
15399
+
15321
15400
  // ../../packages/jobs/dist/client.js
15322
15401
  var require_client = __commonJS({
15323
15402
  "../../packages/jobs/dist/client.js"(exports2) {
15324
15403
  "use strict";
15325
15404
  Object.defineProperty(exports2, "__esModule", { value: true });
15326
15405
  exports2.JobsClient = void 0;
15406
+ var response_1 = require_response();
15327
15407
  var JobsClient = class {
15328
15408
  baseUrl;
15329
15409
  apiKey;
@@ -15534,10 +15614,16 @@ var require_client = __commonJS({
15534
15614
  };
15535
15615
  }
15536
15616
  // ── HTTP plumbing ────────────────────────────────────────────────
15617
+ /**
15618
+ * Every call's one reader. `readJsonResponse` refuses to parse a reply that
15619
+ * is not the API's JSON — an ingress 502/503/504 while a pod rolls, or any
15620
+ * non-JSON body — and throws a named failure instead of a parse error. What
15621
+ * comes back here is the API's own envelope, success or not.
15622
+ */
15537
15623
  async request(method, path, body) {
15538
15624
  const url = `${this.baseUrl}${path}`;
15539
15625
  const response = await this.fetchWithAuthRetry(method, url, body ? JSON.stringify(body) : void 0);
15540
- const json = await response.json();
15626
+ const json = await (0, response_1.readJsonResponse)(response, url);
15541
15627
  if (!response.ok || !json.success) {
15542
15628
  const errorMessage = json.error?.message ?? `Request failed with status ${response.status}`;
15543
15629
  const error = new Error(errorMessage);
@@ -15558,6 +15644,7 @@ var require_subscribe = __commonJS({
15558
15644
  "use strict";
15559
15645
  Object.defineProperty(exports2, "__esModule", { value: true });
15560
15646
  exports2.subscribe = subscribe;
15647
+ var response_1 = require_response();
15561
15648
  var NULL_LOGGER = {
15562
15649
  debug: () => {
15563
15650
  },
@@ -15571,6 +15658,14 @@ var require_subscribe = __commonJS({
15571
15658
  function sleep(ms) {
15572
15659
  return new Promise((resolve) => setTimeout(resolve, ms));
15573
15660
  }
15661
+ function formatDuration(ms) {
15662
+ const seconds = Math.round(ms / 1e3);
15663
+ if (seconds < 60)
15664
+ return `${seconds}s`;
15665
+ const minutes = Math.floor(seconds / 60);
15666
+ const rest = seconds % 60;
15667
+ return rest === 0 ? `${minutes}m` : `${minutes}m ${rest}s`;
15668
+ }
15574
15669
  function subscribe(opts) {
15575
15670
  const logger = opts.logger ?? NULL_LOGGER;
15576
15671
  const pollIntervalMs = opts.pollIntervalMs ?? 2e3;
@@ -15579,6 +15674,7 @@ var require_subscribe = __commonJS({
15579
15674
  let pollTimer = null;
15580
15675
  let inflight = 0;
15581
15676
  let resolvedPoolIds = [];
15677
+ let unavailableSince = null;
15582
15678
  async function start() {
15583
15679
  if (!stopped)
15584
15680
  return;
@@ -15629,6 +15725,10 @@ var require_subscribe = __commonJS({
15629
15725
  limit: maxConcurrency - inflight,
15630
15726
  sort: "priority:desc,queued_at:asc"
15631
15727
  });
15728
+ if (unavailableSince !== null) {
15729
+ logger.info(`Sift is reachable again after ${formatDuration(Date.now() - unavailableSince)}`);
15730
+ unavailableSince = null;
15731
+ }
15632
15732
  if (directives && Object.keys(directives).length > 0 && opts.onDirectives) {
15633
15733
  try {
15634
15734
  await opts.onDirectives(directives);
@@ -15649,7 +15749,20 @@ var require_subscribe = __commonJS({
15649
15749
  });
15650
15750
  }
15651
15751
  } catch (err) {
15652
- logger.error("poll failed", { error: err instanceof Error ? err.message : String(err) });
15752
+ if (err instanceof response_1.ServiceUnavailableError) {
15753
+ if (unavailableSince === null) {
15754
+ unavailableSince = Date.now();
15755
+ logger.warn(`Sift is unavailable (${err.host} answered ${err.statusCode}${err.statusText ? ` ${err.statusText}` : ""}). This is typical during a deployment window; the Sifter will keep trying.`);
15756
+ } else {
15757
+ logger.debug?.("Sift still unavailable", {
15758
+ host: err.host,
15759
+ status: err.statusCode,
15760
+ unavailableFor: formatDuration(Date.now() - unavailableSince)
15761
+ });
15762
+ }
15763
+ } else {
15764
+ logger.error("poll failed", { error: err instanceof Error ? err.message : String(err) });
15765
+ }
15653
15766
  }
15654
15767
  if (!stopped)
15655
15768
  schedulePoll(pollIntervalMs);
@@ -15732,11 +15845,18 @@ var require_dist2 = __commonJS({
15732
15845
  "../../packages/jobs/dist/index.js"(exports2) {
15733
15846
  "use strict";
15734
15847
  Object.defineProperty(exports2, "__esModule", { value: true });
15735
- exports2.subscribe = exports2.JobsClient = void 0;
15848
+ exports2.subscribe = exports2.ServiceUnavailableError = exports2.NonJsonResponseError = exports2.JobsClient = void 0;
15736
15849
  var client_1 = require_client();
15737
15850
  Object.defineProperty(exports2, "JobsClient", { enumerable: true, get: function() {
15738
15851
  return client_1.JobsClient;
15739
15852
  } });
15853
+ var response_1 = require_response();
15854
+ Object.defineProperty(exports2, "NonJsonResponseError", { enumerable: true, get: function() {
15855
+ return response_1.NonJsonResponseError;
15856
+ } });
15857
+ Object.defineProperty(exports2, "ServiceUnavailableError", { enumerable: true, get: function() {
15858
+ return response_1.ServiceUnavailableError;
15859
+ } });
15740
15860
  var subscribe_1 = require_subscribe();
15741
15861
  Object.defineProperty(exports2, "subscribe", { enumerable: true, get: function() {
15742
15862
  return subscribe_1.subscribe;
@@ -22450,7 +22570,7 @@ var import_path = require("path");
22450
22570
  var import_promises = require("fs/promises");
22451
22571
  var import_yaml = __toESM(require_dist());
22452
22572
  var import_shuttle = __toESM(require_dist5());
22453
- var buildVersion = true ? "0.27.4" : pkg.version;
22573
+ var buildVersion = true ? "0.27.5" : pkg.version;
22454
22574
  var sifterBrand = {
22455
22575
  product: {
22456
22576
  id: "sifter",