@blaxel/core 0.3.12-preview.254 → 0.3.12-preview.255

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.
@@ -12,6 +12,7 @@ exports.h2RequestDirect = h2RequestDirect;
12
12
  const http2_1 = __importDefault(require("http2"));
13
13
  const settings_js_1 = require("./settings.js");
14
14
  const h2ref_js_1 = require("./h2ref.js");
15
+ const h2stats_js_1 = require("./h2stats.js");
15
16
  // Statuses the Response constructor refuses to pair with a body. Handing it one
16
17
  // throws, so a 204 (e.g. from a DELETE) would blow up instead of resolving.
17
18
  const NULL_BODY_STATUSES = new Set([101, 103, 204, 205, 304]);
@@ -169,6 +170,7 @@ async function withUploadSlot(domain, fn) {
169
170
  function createH2Fetch(session) {
170
171
  return (input) => {
171
172
  if (session.closed || session.destroyed) {
173
+ (0, h2stats_js_1.recordH2Fallback)(new URL(input.url).hostname, "session-unusable");
172
174
  return globalThis.fetch(input);
173
175
  }
174
176
  return _h2Request(session, input);
@@ -234,6 +236,7 @@ async function h2GatewayAttempt(pool, domain, send, fallback) {
234
236
  let h2RequestCreated = false;
235
237
  try {
236
238
  return await send(session, {
239
+ domain,
237
240
  onH2RequestCreated: () => {
238
241
  h2RequestCreated = true;
239
242
  },
@@ -252,6 +255,7 @@ async function h2GatewayAttempt(pool, domain, send, fallback) {
252
255
  // No usable session: free the slot before falling back over a different
253
256
  // connection (no stream opens on the shared session).
254
257
  rel();
258
+ (0, h2stats_js_1.recordH2Fallback)(domain, "no-session");
255
259
  return await fallback();
256
260
  }
257
261
  catch (err) {
@@ -291,6 +295,7 @@ function h2RequestDirectInternal(session, url, init, options) {
291
295
  // Pre-flight fallback (session unusable): no stream opens on the shared
292
296
  // session, so free any held slot before going over globalThis.fetch.
293
297
  options?.releaseSlot?.();
298
+ (0, h2stats_js_1.recordH2Fallback)(options?.domain ?? new URL(url).hostname, "session-unusable");
294
299
  return globalThis.fetch(url, init);
295
300
  }
296
301
  const parsed = new URL(url);
@@ -333,6 +338,7 @@ function h2RequestDirectInternal(session, url, init, options) {
333
338
  // nothing has been sent on the wire yet). No stream opens on the shared
334
339
  // session, so free any held slot before falling back.
335
340
  options?.releaseSlot?.();
341
+ (0, h2stats_js_1.recordH2Fallback)(options?.domain ?? parsed.hostname, "unsupported-body");
336
342
  return globalThis.fetch(url, init);
337
343
  }
338
344
  if (!h2Headers["content-length"]) {
@@ -412,6 +418,7 @@ function _h2Send(session, h2Headers, body, signal, fallbackUrl, fallbackInit, op
412
418
  // H2 frames were sent. No stream opened on the shared session, so free
413
419
  // the slot before retrying over globalThis.fetch.
414
420
  releaseSlot();
421
+ (0, h2stats_js_1.recordH2Fallback)(options?.domain ?? new URL(fallbackUrl).hostname, session.closed || session.destroyed ? "session-unusable" : "request-rejected");
415
422
  globalThis.fetch(fallbackUrl, fallbackInit).then(resolve, reject);
416
423
  return;
417
424
  }
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.h2Pool = exports.H2Pool = void 0;
37
+ const h2stats_js_1 = require("./h2stats.js");
37
38
  const DEFAULT_MAX_IDLE_MS = 5_000;
38
39
  const DEFAULT_PING_TIMEOUT_MS = 500;
39
40
  /**
@@ -75,6 +76,22 @@ class H2Pool {
75
76
  this.attachEvictionListeners(domain, session);
76
77
  return session;
77
78
  }
79
+ startEstablish(domain) {
80
+ const pending = this.establish(domain)
81
+ .then((session) => {
82
+ this.cache(domain, session);
83
+ return session;
84
+ })
85
+ .catch((error) => {
86
+ (0, h2stats_js_1.recordH2EstablishFailure)(domain, error);
87
+ return null;
88
+ })
89
+ .finally(() => {
90
+ this.inflight.delete(domain);
91
+ });
92
+ this.inflight.set(domain, pending);
93
+ return pending;
94
+ }
78
95
  attachEvictionListeners(domain, session) {
79
96
  const evict = () => {
80
97
  // Only evict if this specific session is still the cached one.
@@ -193,16 +210,7 @@ class H2Pool {
193
210
  return;
194
211
  if (this.inflight.has(domain))
195
212
  return;
196
- const p = this.establish(domain)
197
- .then((session) => {
198
- this.cache(domain, session);
199
- return session;
200
- })
201
- .catch(() => null)
202
- .finally(() => {
203
- this.inflight.delete(domain);
204
- });
205
- this.inflight.set(domain, p);
213
+ void this.startEstablish(domain);
206
214
  }
207
215
  /**
208
216
  * Synchronous cache check. Returns a live cached session or `null`.
@@ -250,17 +258,7 @@ class H2Pool {
250
258
  const freshCached = this.tryGet(domain);
251
259
  if (freshCached)
252
260
  return freshCached;
253
- const p = this.establish(domain)
254
- .then((session) => {
255
- this.cache(domain, session);
256
- return session;
257
- })
258
- .catch(() => null)
259
- .finally(() => {
260
- this.inflight.delete(domain);
261
- });
262
- this.inflight.set(domain, p);
263
- return p;
261
+ return this.startEstablish(domain);
264
262
  }
265
263
  /** Close all sessions (for cleanup). */
266
264
  closeAll() {
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.snapshotH2TransportStats = snapshotH2TransportStats;
4
+ exports.resetH2TransportStats = resetH2TransportStats;
5
+ exports.recordH2EstablishFailure = recordH2EstablishFailure;
6
+ exports.recordH2Fallback = recordH2Fallback;
7
+ const logger_js_1 = require("./logger.js");
8
+ const emptyReasons = () => ({
9
+ "no-session": 0,
10
+ "request-rejected": 0,
11
+ "session-unusable": 0,
12
+ "unsupported-body": 0,
13
+ });
14
+ const MAX_TRACKED_DOMAINS = 100;
15
+ const H2_DEBUG_STATS_SYMBOL = Symbol.for("blaxel.h2stats");
16
+ const h2DebugStatsEnabled = typeof process !== "undefined" && process.env?.BL_H2_DEBUG_STATS === "1";
17
+ function publishDebugSnapshot(snapshot) {
18
+ if (!h2DebugStatsEnabled)
19
+ return;
20
+ try {
21
+ globalThis[H2_DEBUG_STATS_SYMBOL] = snapshot;
22
+ }
23
+ catch {
24
+ // Debug diagnostics must never change transport behavior.
25
+ }
26
+ }
27
+ const emptyDomainStats = () => ({
28
+ establishFailures: 0,
29
+ fetchFallbacks: 0,
30
+ fallbacksByReason: emptyReasons(),
31
+ });
32
+ class H2TransportStatsStore {
33
+ totals = emptyDomainStats();
34
+ byDomain = new Map();
35
+ snapshot() {
36
+ return {
37
+ ...this.clone(this.totals),
38
+ byDomain: Object.fromEntries([...this.byDomain].map(([domain, stats]) => [domain, this.clone(stats)])),
39
+ };
40
+ }
41
+ reset() {
42
+ this.totals = emptyDomainStats();
43
+ this.byDomain.clear();
44
+ publishDebugSnapshot(this.snapshot());
45
+ }
46
+ /** @internal */
47
+ recordEstablishFailure(domain, error) {
48
+ this.totals.establishFailures++;
49
+ this.forDomain(domain).establishFailures++;
50
+ try {
51
+ const message = error instanceof Error ? error.message : String(error);
52
+ logger_js_1.logger.debug(`H2 session establishment failed for ${domain}: ${message}`);
53
+ }
54
+ catch {
55
+ // Diagnostics must never change transport behavior.
56
+ }
57
+ publishDebugSnapshot(this.snapshot());
58
+ }
59
+ /** @internal */
60
+ recordFallback(domain, reason) {
61
+ this.totals.fetchFallbacks++;
62
+ this.totals.fallbacksByReason[reason]++;
63
+ const stats = this.forDomain(domain);
64
+ stats.fetchFallbacks++;
65
+ stats.fallbacksByReason[reason]++;
66
+ try {
67
+ logger_js_1.logger.debug(`H2 transport falling back to fetch for ${domain}: ${reason}`);
68
+ }
69
+ catch {
70
+ // Diagnostics must never change transport behavior.
71
+ }
72
+ publishDebugSnapshot(this.snapshot());
73
+ }
74
+ forDomain(domain) {
75
+ let stats = this.byDomain.get(domain);
76
+ if (stats) {
77
+ this.byDomain.delete(domain);
78
+ }
79
+ else {
80
+ if (this.byDomain.size >= MAX_TRACKED_DOMAINS) {
81
+ const oldest = this.byDomain.keys().next().value;
82
+ if (oldest !== undefined)
83
+ this.byDomain.delete(oldest);
84
+ }
85
+ stats = emptyDomainStats();
86
+ }
87
+ this.byDomain.set(domain, stats);
88
+ return stats;
89
+ }
90
+ clone(stats) {
91
+ return {
92
+ establishFailures: stats.establishFailures,
93
+ fetchFallbacks: stats.fetchFallbacks,
94
+ fallbacksByReason: { ...stats.fallbacksByReason },
95
+ };
96
+ }
97
+ }
98
+ const store = new H2TransportStatsStore();
99
+ publishDebugSnapshot(store.snapshot());
100
+ /** @internal */
101
+ function snapshotH2TransportStats() {
102
+ return store.snapshot();
103
+ }
104
+ /** @internal */
105
+ function resetH2TransportStats() {
106
+ store.reset();
107
+ }
108
+ /** @internal */
109
+ function recordH2EstablishFailure(domain, error) {
110
+ store.recordEstablishFailure(domain, error);
111
+ }
112
+ /** @internal */
113
+ function recordH2Fallback(domain, reason) {
114
+ store.recordFallback(domain, reason);
115
+ }
@@ -30,8 +30,8 @@ function missingCredentialsMessage() {
30
30
  return "No Blaxel credentials found. Set the BL_API_KEY and BL_WORKSPACE environment variables, or run `bl login`.";
31
31
  }
32
32
  // Build info - these placeholders are replaced at build time by build:replace-imports
33
- const BUILD_VERSION = "0.3.12-preview.254";
34
- const BUILD_COMMIT = "c57f438aac364b3b4137e7e4ec37d446fec85006";
33
+ const BUILD_VERSION = "0.3.12-preview.255";
34
+ const BUILD_COMMIT = "0a7ecff86c697285b849e61a8cc5219e85b5ba89";
35
35
  const BUILD_SENTRY_DSN = "https://fd5e60e1c9820e1eef5ccebb84a07127@o4508714045276160.ingest.us.sentry.io/4510465864564736";
36
36
  const BLAXEL_API_VERSION = "2026-04-28";
37
37
  // Bun < 1.3.11 never sends connection-level WINDOW_UPDATE: the pooled h2
@@ -32,6 +32,7 @@ export declare class H2Pool {
32
32
  * over session failures at the fetch layer.
33
33
  */
34
34
  private establish;
35
+ private startEstablish;
35
36
  private attachEvictionListeners;
36
37
  private isClosed;
37
38
  private isIdle;
@@ -0,0 +1,17 @@
1
+ export type H2FallbackReason = "no-session" | "request-rejected" | "session-unusable" | "unsupported-body";
2
+ export type H2TransportDomainStats = {
3
+ establishFailures: number;
4
+ fetchFallbacks: number;
5
+ fallbacksByReason: Record<H2FallbackReason, number>;
6
+ };
7
+ export type H2TransportStatsSnapshot = H2TransportDomainStats & {
8
+ byDomain: Record<string, H2TransportDomainStats>;
9
+ };
10
+ /** @internal */
11
+ export declare function snapshotH2TransportStats(): H2TransportStatsSnapshot;
12
+ /** @internal */
13
+ export declare function resetH2TransportStats(): void;
14
+ /** @internal */
15
+ export declare function recordH2EstablishFailure(domain: string, error: unknown): void;
16
+ /** @internal */
17
+ export declare function recordH2Fallback(domain: string, reason: H2FallbackReason): void;