@blaxel/core 0.2.82-preview.146 → 0.2.82
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/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/common/h2fetch.js +93 -13
- package/dist/cjs/common/h2pool.js +109 -14
- package/dist/cjs/common/lazyInit.js +1 -1
- package/dist/cjs/common/settings.js +11 -2
- package/dist/cjs/sandbox/action.js +12 -8
- package/dist/cjs/sandbox/interpreter.js +6 -3
- package/dist/cjs/sandbox/sandbox.js +22 -11
- package/dist/cjs/types/common/h2fetch.d.ts +6 -6
- package/dist/cjs/types/common/h2pool.d.ts +20 -0
- package/dist/cjs/types/common/settings.d.ts +2 -0
- package/dist/cjs/types/sandbox/action.d.ts +1 -0
- package/dist/cjs/types/sandbox/sandbox.d.ts +4 -1
- package/dist/cjs/types/sandbox/types.d.ts +1 -0
- package/dist/cjs-browser/.tsbuildinfo +1 -1
- package/dist/cjs-browser/common/h2fetch.js +1 -0
- package/dist/cjs-browser/common/lazyInit.js +1 -1
- package/dist/cjs-browser/common/settings.js +11 -2
- package/dist/cjs-browser/sandbox/action.js +12 -8
- package/dist/cjs-browser/sandbox/interpreter.js +6 -3
- package/dist/cjs-browser/sandbox/sandbox.js +22 -11
- package/dist/cjs-browser/types/common/h2fetch.d.ts +6 -6
- package/dist/cjs-browser/types/common/h2pool.d.ts +20 -0
- package/dist/cjs-browser/types/common/settings.d.ts +2 -0
- package/dist/cjs-browser/types/sandbox/action.d.ts +1 -0
- package/dist/cjs-browser/types/sandbox/sandbox.d.ts +4 -1
- package/dist/cjs-browser/types/sandbox/types.d.ts +1 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/common/h2fetch.js +92 -13
- package/dist/esm/common/h2pool.js +109 -14
- package/dist/esm/common/lazyInit.js +1 -1
- package/dist/esm/common/settings.js +11 -2
- package/dist/esm/sandbox/action.js +13 -9
- package/dist/esm/sandbox/interpreter.js +7 -4
- package/dist/esm/sandbox/sandbox.js +22 -11
- package/dist/esm-browser/.tsbuildinfo +1 -1
- package/dist/esm-browser/common/h2fetch.js +1 -0
- package/dist/esm-browser/common/lazyInit.js +1 -1
- package/dist/esm-browser/common/settings.js +11 -2
- package/dist/esm-browser/sandbox/action.js +13 -9
- package/dist/esm-browser/sandbox/interpreter.js +7 -4
- package/dist/esm-browser/sandbox/sandbox.js +22 -11
- package/package.json +1 -1
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createH2Fetch = createH2Fetch;
|
|
4
4
|
exports.createPoolBackedH2Fetch = createPoolBackedH2Fetch;
|
|
5
|
+
exports.h2RequestDirectFromPool = h2RequestDirectFromPool;
|
|
5
6
|
exports.h2RequestDirect = h2RequestDirect;
|
|
7
|
+
const MIN_H2_SESSION_MAX_LISTENERS = 64;
|
|
8
|
+
const sessionsWithListenerBudget = new WeakSet();
|
|
6
9
|
/**
|
|
7
10
|
* Creates a fetch()-compatible function that sends requests over an existing
|
|
8
11
|
* HTTP/2 session. Falls back to globalThis.fetch() only when the session is
|
|
@@ -22,25 +25,60 @@ function createH2Fetch(session) {
|
|
|
22
25
|
/**
|
|
23
26
|
* Creates a fetch()-compatible function backed by the H2 session pool.
|
|
24
27
|
*
|
|
25
|
-
*
|
|
26
|
-
* available
|
|
27
|
-
*
|
|
28
|
-
* so subsequent calls get H2).
|
|
28
|
+
* The pool validates idle sessions before reuse. If no usable H2 session is
|
|
29
|
+
* available, the request falls back to regular fetch before any H2 frames
|
|
30
|
+
* are sent.
|
|
29
31
|
*/
|
|
30
32
|
function createPoolBackedH2Fetch(pool, domain) {
|
|
31
|
-
return (input) => {
|
|
32
|
-
const session = pool.
|
|
33
|
+
return async (input) => {
|
|
34
|
+
const session = await pool.get(domain);
|
|
33
35
|
if (session) {
|
|
34
|
-
|
|
36
|
+
let h2RequestCreated = false;
|
|
37
|
+
try {
|
|
38
|
+
return await _h2Request(session, input, {
|
|
39
|
+
onH2RequestCreated: () => {
|
|
40
|
+
h2RequestCreated = true;
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
if (h2RequestCreated) {
|
|
46
|
+
pool.evictSession(domain, session);
|
|
47
|
+
}
|
|
48
|
+
throw err;
|
|
49
|
+
}
|
|
35
50
|
}
|
|
36
51
|
return globalThis.fetch(input);
|
|
37
52
|
};
|
|
38
53
|
}
|
|
54
|
+
async function h2RequestDirectFromPool(pool, domain, url, init) {
|
|
55
|
+
const session = await pool.get(domain);
|
|
56
|
+
if (session) {
|
|
57
|
+
let h2RequestCreated = false;
|
|
58
|
+
try {
|
|
59
|
+
return await h2RequestDirectInternal(session, url, init, {
|
|
60
|
+
onH2RequestCreated: () => {
|
|
61
|
+
h2RequestCreated = true;
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
if (h2RequestCreated) {
|
|
67
|
+
pool.evictSession(domain, session);
|
|
68
|
+
}
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return globalThis.fetch(url, init);
|
|
73
|
+
}
|
|
39
74
|
/**
|
|
40
75
|
* Low-level H2 request that takes raw URL + init, skipping Request construction.
|
|
41
76
|
* Used by SandboxAction.h2Fetch() for direct calls from subsystems.
|
|
42
77
|
*/
|
|
43
78
|
function h2RequestDirect(session, url, init) {
|
|
79
|
+
return h2RequestDirectInternal(session, url, init);
|
|
80
|
+
}
|
|
81
|
+
function h2RequestDirectInternal(session, url, init, options) {
|
|
44
82
|
if (session.closed || session.destroyed) {
|
|
45
83
|
return globalThis.fetch(url, init);
|
|
46
84
|
}
|
|
@@ -88,9 +126,9 @@ function h2RequestDirect(session, url, init) {
|
|
|
88
126
|
h2Headers["content-length"] = body.byteLength;
|
|
89
127
|
}
|
|
90
128
|
}
|
|
91
|
-
return _h2Send(session, h2Headers, body, init?.signal ?? null, url, init);
|
|
129
|
+
return _h2Send(session, h2Headers, body, init?.signal ?? null, url, init, options);
|
|
92
130
|
}
|
|
93
|
-
async function _h2Request(session, input) {
|
|
131
|
+
async function _h2Request(session, input, options) {
|
|
94
132
|
const url = new URL(input.url);
|
|
95
133
|
const method = input.method || "GET";
|
|
96
134
|
const h2Headers = {
|
|
@@ -115,15 +153,15 @@ async function _h2Request(session, input) {
|
|
|
115
153
|
headers: input.headers,
|
|
116
154
|
body,
|
|
117
155
|
signal: input.signal,
|
|
118
|
-
});
|
|
156
|
+
}, options);
|
|
119
157
|
}
|
|
120
|
-
function _h2Send(session, h2Headers, body, signal, fallbackUrl, fallbackInit) {
|
|
158
|
+
function _h2Send(session, h2Headers, body, signal, fallbackUrl, fallbackInit, options) {
|
|
121
159
|
return new Promise((resolve, reject) => {
|
|
122
160
|
let settled = false;
|
|
123
161
|
let responded = false;
|
|
124
162
|
let streamController = null;
|
|
125
163
|
let streamClosed = false;
|
|
126
|
-
let req;
|
|
164
|
+
let req = null;
|
|
127
165
|
try {
|
|
128
166
|
req = session.request(h2Headers);
|
|
129
167
|
}
|
|
@@ -133,12 +171,40 @@ function _h2Send(session, h2Headers, body, signal, fallbackUrl, fallbackInit) {
|
|
|
133
171
|
globalThis.fetch(fallbackUrl, fallbackInit).then(resolve, reject);
|
|
134
172
|
return;
|
|
135
173
|
}
|
|
174
|
+
options?.onH2RequestCreated?.();
|
|
175
|
+
ensureH2SessionListenerBudget(session);
|
|
176
|
+
const cleanupBeforeResponseListeners = () => {
|
|
177
|
+
session.off("close", onSessionClose);
|
|
178
|
+
session.off("goaway", onSessionGoaway);
|
|
179
|
+
session.off("error", onSessionError);
|
|
180
|
+
};
|
|
181
|
+
const rejectBeforeResponse = (err) => {
|
|
182
|
+
if (settled)
|
|
183
|
+
return;
|
|
184
|
+
settled = true;
|
|
185
|
+
cleanupBeforeResponseListeners();
|
|
186
|
+
req?.close();
|
|
187
|
+
reject(err);
|
|
188
|
+
};
|
|
189
|
+
const onSessionClose = () => {
|
|
190
|
+
rejectBeforeResponse(new Error("HTTP/2 session closed before response"));
|
|
191
|
+
};
|
|
192
|
+
const onSessionGoaway = () => {
|
|
193
|
+
rejectBeforeResponse(new Error("HTTP/2 session sent GOAWAY before response"));
|
|
194
|
+
};
|
|
195
|
+
const onSessionError = (err) => {
|
|
196
|
+
rejectBeforeResponse(err);
|
|
197
|
+
};
|
|
198
|
+
session.once("close", onSessionClose);
|
|
199
|
+
session.once("goaway", onSessionGoaway);
|
|
200
|
+
session.once("error", onSessionError);
|
|
136
201
|
const abort = () => {
|
|
137
|
-
req
|
|
202
|
+
req?.close();
|
|
138
203
|
const abortError = new DOMException("The operation was aborted.", "AbortError");
|
|
139
204
|
if (!responded) {
|
|
140
205
|
if (!settled) {
|
|
141
206
|
settled = true;
|
|
207
|
+
cleanupBeforeResponseListeners();
|
|
142
208
|
reject(abortError);
|
|
143
209
|
}
|
|
144
210
|
return;
|
|
@@ -151,6 +217,7 @@ function _h2Send(session, h2Headers, body, signal, fallbackUrl, fallbackInit) {
|
|
|
151
217
|
if (signal) {
|
|
152
218
|
if (signal.aborted) {
|
|
153
219
|
req.close();
|
|
220
|
+
cleanupBeforeResponseListeners();
|
|
154
221
|
settled = true;
|
|
155
222
|
reject(new DOMException("The operation was aborted.", "AbortError"));
|
|
156
223
|
return;
|
|
@@ -162,6 +229,7 @@ function _h2Send(session, h2Headers, body, signal, fallbackUrl, fallbackInit) {
|
|
|
162
229
|
return;
|
|
163
230
|
settled = true;
|
|
164
231
|
responded = true;
|
|
232
|
+
cleanupBeforeResponseListeners();
|
|
165
233
|
const status = headers[":status"] ?? 200;
|
|
166
234
|
const resHeaders = new Headers();
|
|
167
235
|
for (const [k, v] of Object.entries(headers)) {
|
|
@@ -181,12 +249,14 @@ function _h2Send(session, h2Headers, body, signal, fallbackUrl, fallbackInit) {
|
|
|
181
249
|
req.on("end", () => {
|
|
182
250
|
if (!streamClosed) {
|
|
183
251
|
streamClosed = true;
|
|
252
|
+
signal?.removeEventListener("abort", abort);
|
|
184
253
|
controller.close();
|
|
185
254
|
}
|
|
186
255
|
});
|
|
187
256
|
req.on("error", (err) => {
|
|
188
257
|
if (!streamClosed) {
|
|
189
258
|
streamClosed = true;
|
|
259
|
+
signal?.removeEventListener("abort", abort);
|
|
190
260
|
controller.error(err);
|
|
191
261
|
}
|
|
192
262
|
});
|
|
@@ -198,6 +268,7 @@ function _h2Send(session, h2Headers, body, signal, fallbackUrl, fallbackInit) {
|
|
|
198
268
|
if (settled)
|
|
199
269
|
return;
|
|
200
270
|
settled = true;
|
|
271
|
+
cleanupBeforeResponseListeners();
|
|
201
272
|
reject(err);
|
|
202
273
|
});
|
|
203
274
|
if (body) {
|
|
@@ -208,3 +279,12 @@ function _h2Send(session, h2Headers, body, signal, fallbackUrl, fallbackInit) {
|
|
|
208
279
|
}
|
|
209
280
|
});
|
|
210
281
|
}
|
|
282
|
+
function ensureH2SessionListenerBudget(session) {
|
|
283
|
+
if (sessionsWithListenerBudget.has(session))
|
|
284
|
+
return;
|
|
285
|
+
sessionsWithListenerBudget.add(session);
|
|
286
|
+
const currentMax = session.getMaxListeners();
|
|
287
|
+
if (currentMax > 0 && currentMax < MIN_H2_SESSION_MAX_LISTENERS) {
|
|
288
|
+
session.setMaxListeners(MIN_H2_SESSION_MAX_LISTENERS);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -34,6 +34,8 @@ 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 DEFAULT_MAX_IDLE_MS = 5_000;
|
|
38
|
+
const DEFAULT_PING_TIMEOUT_MS = 500;
|
|
37
39
|
/**
|
|
38
40
|
* Singleton H2 session pool keyed by edge domain.
|
|
39
41
|
*
|
|
@@ -46,6 +48,14 @@ class H2Pool {
|
|
|
46
48
|
sessions = new Map();
|
|
47
49
|
inflight = new Map();
|
|
48
50
|
_establish = null;
|
|
51
|
+
maxIdleMs;
|
|
52
|
+
pingTimeoutMs;
|
|
53
|
+
now;
|
|
54
|
+
constructor(options = {}) {
|
|
55
|
+
this.maxIdleMs = options.maxIdleMs ?? DEFAULT_MAX_IDLE_MS;
|
|
56
|
+
this.pingTimeoutMs = options.pingTimeoutMs ?? DEFAULT_PING_TIMEOUT_MS;
|
|
57
|
+
this.now = options.now ?? Date.now;
|
|
58
|
+
}
|
|
49
59
|
/**
|
|
50
60
|
* Lazily resolve the establish function so the http2 / tls / dns modules
|
|
51
61
|
* are only imported in Node.js environments.
|
|
@@ -68,7 +78,7 @@ class H2Pool {
|
|
|
68
78
|
const evict = () => {
|
|
69
79
|
// Only evict if this specific session is still the cached one.
|
|
70
80
|
// A newer session may have taken its place after reconnect.
|
|
71
|
-
if (this.sessions.get(domain) === session) {
|
|
81
|
+
if (this.sessions.get(domain)?.session === session) {
|
|
72
82
|
this.sessions.delete(domain);
|
|
73
83
|
}
|
|
74
84
|
};
|
|
@@ -76,19 +86,92 @@ class H2Pool {
|
|
|
76
86
|
session.on("error", evict);
|
|
77
87
|
session.on("close", evict);
|
|
78
88
|
}
|
|
89
|
+
isClosed(session) {
|
|
90
|
+
return session.closed || session.destroyed;
|
|
91
|
+
}
|
|
92
|
+
isIdle(entry) {
|
|
93
|
+
return this.now() - entry.lastUsedAt > this.maxIdleMs;
|
|
94
|
+
}
|
|
95
|
+
cache(domain, session) {
|
|
96
|
+
this.sessions.set(domain, {
|
|
97
|
+
session,
|
|
98
|
+
lastUsedAt: this.now(),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
markUsed(domain, session) {
|
|
102
|
+
const entry = this.sessions.get(domain);
|
|
103
|
+
if (entry?.session === session) {
|
|
104
|
+
entry.lastUsedAt = this.now();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
evict(domain, session) {
|
|
108
|
+
const entry = this.sessions.get(domain);
|
|
109
|
+
if (!entry)
|
|
110
|
+
return;
|
|
111
|
+
if (session && entry.session !== session)
|
|
112
|
+
return;
|
|
113
|
+
this.sessions.delete(domain);
|
|
114
|
+
if (!entry.session.closed && !entry.session.destroyed) {
|
|
115
|
+
entry.session.close();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
ping(session) {
|
|
119
|
+
if (this.isClosed(session))
|
|
120
|
+
return Promise.resolve(false);
|
|
121
|
+
return new Promise((resolve) => {
|
|
122
|
+
let settled = false;
|
|
123
|
+
const finish = (ok) => {
|
|
124
|
+
if (settled)
|
|
125
|
+
return;
|
|
126
|
+
settled = true;
|
|
127
|
+
clearTimeout(timer);
|
|
128
|
+
resolve(ok);
|
|
129
|
+
};
|
|
130
|
+
const timer = setTimeout(() => finish(false), this.pingTimeoutMs);
|
|
131
|
+
try {
|
|
132
|
+
const sent = session.ping((err) => {
|
|
133
|
+
finish(!err && !this.isClosed(session));
|
|
134
|
+
});
|
|
135
|
+
if (!sent)
|
|
136
|
+
finish(false);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
finish(false);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
async validateEntry(domain, entry) {
|
|
144
|
+
if (!entry)
|
|
145
|
+
return null;
|
|
146
|
+
const { session } = entry;
|
|
147
|
+
if (this.isClosed(session)) {
|
|
148
|
+
this.evict(domain, session);
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
if (!this.isIdle(entry)) {
|
|
152
|
+
this.markUsed(domain, session);
|
|
153
|
+
return session;
|
|
154
|
+
}
|
|
155
|
+
if (await this.ping(session)) {
|
|
156
|
+
this.markUsed(domain, session);
|
|
157
|
+
return session;
|
|
158
|
+
}
|
|
159
|
+
this.evict(domain, session);
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
79
162
|
/**
|
|
80
163
|
* Fire-and-forget background warming. Safe to call multiple times for
|
|
81
164
|
* the same domain — only one connection attempt per domain at a time.
|
|
82
165
|
*/
|
|
83
166
|
warm(domain) {
|
|
84
|
-
const existing = this.
|
|
85
|
-
if (existing
|
|
167
|
+
const existing = this.tryGet(domain);
|
|
168
|
+
if (existing)
|
|
86
169
|
return;
|
|
87
170
|
if (this.inflight.has(domain))
|
|
88
171
|
return;
|
|
89
172
|
const p = this.establish(domain)
|
|
90
173
|
.then((session) => {
|
|
91
|
-
this.
|
|
174
|
+
this.cache(domain, session);
|
|
92
175
|
return session;
|
|
93
176
|
})
|
|
94
177
|
.catch(() => null)
|
|
@@ -103,25 +186,37 @@ class H2Pool {
|
|
|
103
186
|
*/
|
|
104
187
|
tryGet(domain) {
|
|
105
188
|
const cached = this.sessions.get(domain);
|
|
106
|
-
if (
|
|
107
|
-
return
|
|
108
|
-
this.
|
|
109
|
-
|
|
189
|
+
if (!cached)
|
|
190
|
+
return null;
|
|
191
|
+
if (this.isClosed(cached.session) || this.isIdle(cached)) {
|
|
192
|
+
this.evict(domain, cached.session);
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
this.markUsed(domain, cached.session);
|
|
196
|
+
return cached.session;
|
|
197
|
+
}
|
|
198
|
+
isUsable(session) {
|
|
199
|
+
return !this.isClosed(session);
|
|
200
|
+
}
|
|
201
|
+
evictSession(domain, session) {
|
|
202
|
+
this.evict(domain, session);
|
|
110
203
|
}
|
|
111
204
|
/**
|
|
112
205
|
* Get a live H2 session for `domain`. Returns immediately from cache,
|
|
113
206
|
* joins an in-flight warming, or starts a new one.
|
|
114
207
|
*/
|
|
115
208
|
async get(domain) {
|
|
116
|
-
const fast = this.
|
|
209
|
+
const fast = await this.validateEntry(domain, this.sessions.get(domain));
|
|
117
210
|
if (fast)
|
|
118
211
|
return fast;
|
|
119
212
|
// Join in-flight warming if one exists
|
|
120
213
|
const pending = this.inflight.get(domain);
|
|
121
214
|
if (pending) {
|
|
122
215
|
const session = await pending;
|
|
123
|
-
if (session &&
|
|
216
|
+
if (session && this.isUsable(session)) {
|
|
217
|
+
this.markUsed(domain, session);
|
|
124
218
|
return session;
|
|
219
|
+
}
|
|
125
220
|
}
|
|
126
221
|
// Start fresh, deduplicating concurrent callers via inflight
|
|
127
222
|
// Re-check: another caller may have started a fresh one while we awaited
|
|
@@ -133,7 +228,7 @@ class H2Pool {
|
|
|
133
228
|
return freshCached;
|
|
134
229
|
const p = this.establish(domain)
|
|
135
230
|
.then((session) => {
|
|
136
|
-
this.
|
|
231
|
+
this.cache(domain, session);
|
|
137
232
|
return session;
|
|
138
233
|
})
|
|
139
234
|
.catch(() => null)
|
|
@@ -145,9 +240,9 @@ class H2Pool {
|
|
|
145
240
|
}
|
|
146
241
|
/** Close all sessions (for cleanup). */
|
|
147
242
|
closeAll() {
|
|
148
|
-
for (const [,
|
|
149
|
-
if (!session.closed && !session.destroyed)
|
|
150
|
-
session.close();
|
|
243
|
+
for (const [, entry] of this.sessions) {
|
|
244
|
+
if (!entry.session.closed && !entry.session.destroyed)
|
|
245
|
+
entry.session.close();
|
|
151
246
|
}
|
|
152
247
|
this.sessions.clear();
|
|
153
248
|
this.inflight.clear();
|
|
@@ -77,7 +77,7 @@ function ensureAutoloaded() {
|
|
|
77
77
|
const isNode = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
|
|
78
78
|
/* eslint-disable */
|
|
79
79
|
const isBrowser = typeof globalThis !== "undefined" && globalThis?.window !== undefined;
|
|
80
|
-
if (isNode && !isBrowser) {
|
|
80
|
+
if (isNode && !isBrowser && !settings_js_1.settings.disableH2) {
|
|
81
81
|
try {
|
|
82
82
|
// Pre-warm edge H2 for the configured region so the first
|
|
83
83
|
// SandboxInstance.create() gets an instant session via the pool.
|
|
@@ -11,8 +11,8 @@ const index_js_1 = require("../authentication/index.js");
|
|
|
11
11
|
const env_js_1 = require("../common/env.js");
|
|
12
12
|
const node_js_1 = require("../common/node.js");
|
|
13
13
|
// Build info - these placeholders are replaced at build time by build:replace-imports
|
|
14
|
-
const BUILD_VERSION = "0.2.82
|
|
15
|
-
const BUILD_COMMIT = "
|
|
14
|
+
const BUILD_VERSION = "0.2.82";
|
|
15
|
+
const BUILD_COMMIT = "4d2b3531bdb9d8a065f6d83822343840ae0b8d65";
|
|
16
16
|
const BUILD_SENTRY_DSN = "https://fd5e60e1c9820e1eef5ccebb84a07127@o4508714045276160.ingest.us.sentry.io/4510465864564736";
|
|
17
17
|
const BLAXEL_API_VERSION = "2026-04-16";
|
|
18
18
|
// Cache for config.yaml tracking value
|
|
@@ -218,6 +218,15 @@ class Settings {
|
|
|
218
218
|
get region() {
|
|
219
219
|
return env_js_1.env.BL_REGION || undefined;
|
|
220
220
|
}
|
|
221
|
+
get disableH2() {
|
|
222
|
+
if (typeof this.config.disableH2 === "boolean") {
|
|
223
|
+
return this.config.disableH2;
|
|
224
|
+
}
|
|
225
|
+
const value = env_js_1.env.BL_DISABLE_H2;
|
|
226
|
+
if (!value)
|
|
227
|
+
return false;
|
|
228
|
+
return ["1", "true", "yes", "on"].includes(value.toLowerCase());
|
|
229
|
+
}
|
|
221
230
|
async authenticate() {
|
|
222
231
|
await this.credentials.authenticate();
|
|
223
232
|
}
|
|
@@ -5,6 +5,7 @@ const client_fetch_1 = require("@hey-api/client-fetch");
|
|
|
5
5
|
const interceptors_js_1 = require("../client/interceptors.js");
|
|
6
6
|
const responseInterceptor_js_1 = require("../client/responseInterceptor.js");
|
|
7
7
|
const h2fetch_js_1 = require("../common/h2fetch.js");
|
|
8
|
+
const h2pool_js_1 = require("../common/h2pool.js");
|
|
8
9
|
const internal_js_1 = require("../common/internal.js");
|
|
9
10
|
const settings_js_1 = require("../common/settings.js");
|
|
10
11
|
const client_gen_js_1 = require("./client/client.gen.js");
|
|
@@ -36,6 +37,7 @@ exports.ResponseError = ResponseError;
|
|
|
36
37
|
class SandboxAction {
|
|
37
38
|
sandbox;
|
|
38
39
|
_h2Client = null;
|
|
40
|
+
_h2ClientDomain = null;
|
|
39
41
|
constructor(sandbox) {
|
|
40
42
|
this.sandbox = sandbox;
|
|
41
43
|
}
|
|
@@ -62,12 +64,13 @@ class SandboxAction {
|
|
|
62
64
|
headers: this.sandbox.headers,
|
|
63
65
|
});
|
|
64
66
|
}
|
|
65
|
-
const
|
|
66
|
-
if (
|
|
67
|
-
if (!this._h2Client) {
|
|
67
|
+
const h2Domain = this.sandbox.h2Domain;
|
|
68
|
+
if (h2Domain) {
|
|
69
|
+
if (!this._h2Client || this._h2ClientDomain !== h2Domain) {
|
|
68
70
|
this._h2Client = (0, client_fetch_1.createClient)({
|
|
69
|
-
fetch: (0, h2fetch_js_1.
|
|
71
|
+
fetch: (0, h2fetch_js_1.createPoolBackedH2Fetch)(h2pool_js_1.h2Pool, h2Domain),
|
|
70
72
|
});
|
|
73
|
+
this._h2ClientDomain = h2Domain;
|
|
71
74
|
for (const interceptor of interceptors_js_1.interceptors) {
|
|
72
75
|
// @ts-expect-error - Interceptor is not typed
|
|
73
76
|
this._h2Client.interceptors.request.use(interceptor);
|
|
@@ -78,8 +81,9 @@ class SandboxAction {
|
|
|
78
81
|
}
|
|
79
82
|
return this._h2Client;
|
|
80
83
|
}
|
|
81
|
-
// Invalidate cached H2 client when
|
|
84
|
+
// Invalidate cached H2 client when the sandbox no longer has an H2 domain.
|
|
82
85
|
this._h2Client = null;
|
|
86
|
+
this._h2ClientDomain = null;
|
|
83
87
|
return client_gen_js_1.client;
|
|
84
88
|
}
|
|
85
89
|
/**
|
|
@@ -87,9 +91,9 @@ class SandboxAction {
|
|
|
87
91
|
* globalThis.fetch. Uses a direct H2 path that avoids Request allocation.
|
|
88
92
|
*/
|
|
89
93
|
h2Fetch(input, init) {
|
|
90
|
-
const
|
|
91
|
-
if (
|
|
92
|
-
return (0, h2fetch_js_1.
|
|
94
|
+
const h2Domain = this.sandbox.h2Domain;
|
|
95
|
+
if (h2Domain) {
|
|
96
|
+
return (0, h2fetch_js_1.h2RequestDirectFromPool)(h2pool_js_1.h2Pool, h2Domain, input.toString(), init);
|
|
93
97
|
}
|
|
94
98
|
return globalThis.fetch(input, init);
|
|
95
99
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CodeInterpreter = void 0;
|
|
4
4
|
const h2fetch_js_1 = require("../common/h2fetch.js");
|
|
5
|
+
const h2pool_js_1 = require("../common/h2pool.js");
|
|
5
6
|
const logger_js_1 = require("../common/logger.js");
|
|
6
7
|
const settings_js_1 = require("../common/settings.js");
|
|
7
8
|
const sandbox_js_1 = require("./sandbox.js");
|
|
@@ -28,6 +29,7 @@ class CodeInterpreter extends sandbox_js_1.SandboxInstance {
|
|
|
28
29
|
status: base.status,
|
|
29
30
|
events: base.events,
|
|
30
31
|
h2Session: base.h2Session,
|
|
32
|
+
h2Domain: base.h2Domain,
|
|
31
33
|
};
|
|
32
34
|
return new CodeInterpreter(config);
|
|
33
35
|
}
|
|
@@ -72,6 +74,7 @@ class CodeInterpreter extends sandbox_js_1.SandboxInstance {
|
|
|
72
74
|
status: baseInstance.status,
|
|
73
75
|
events: baseInstance.events,
|
|
74
76
|
h2Session: baseInstance.h2Session,
|
|
77
|
+
h2Domain: baseInstance.h2Domain,
|
|
75
78
|
};
|
|
76
79
|
// Preserve forceUrl, headers, and params from input if provided
|
|
77
80
|
if (sandbox && typeof sandbox === "object" && !Array.isArray(sandbox)) {
|
|
@@ -94,9 +97,9 @@ class CodeInterpreter extends sandbox_js_1.SandboxInstance {
|
|
|
94
97
|
return this.process.url;
|
|
95
98
|
}
|
|
96
99
|
_fetch(input, init) {
|
|
97
|
-
const
|
|
98
|
-
if (
|
|
99
|
-
return (0, h2fetch_js_1.
|
|
100
|
+
const h2Domain = this._sandboxConfig.h2Domain;
|
|
101
|
+
if (h2Domain) {
|
|
102
|
+
return (0, h2fetch_js_1.h2RequestDirectFromPool)(h2pool_js_1.h2Pool, h2Domain, input.toString(), init);
|
|
100
103
|
}
|
|
101
104
|
return globalThis.fetch(input, init);
|
|
102
105
|
}
|
|
@@ -57,7 +57,6 @@ class SandboxInstance {
|
|
|
57
57
|
codegen;
|
|
58
58
|
system;
|
|
59
59
|
drives;
|
|
60
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
61
60
|
h2Session;
|
|
62
61
|
constructor(sandbox) {
|
|
63
62
|
this.sandbox = sandbox;
|
|
@@ -86,27 +85,35 @@ class SandboxInstance {
|
|
|
86
85
|
get lastUsedAt() {
|
|
87
86
|
return this.sandbox.lastUsedAt;
|
|
88
87
|
}
|
|
88
|
+
get h2Domain() {
|
|
89
|
+
return this.sandbox.h2Domain ?? null;
|
|
90
|
+
}
|
|
89
91
|
/**
|
|
90
92
|
* Warm and attach an H2 session based on the sandbox's region.
|
|
91
93
|
* Shared by create(), get(), list(), and update helpers.
|
|
92
94
|
*/
|
|
93
95
|
static async attachH2Session(instance) {
|
|
94
|
-
const
|
|
95
|
-
if (!
|
|
96
|
+
const edgeDomain = SandboxInstance.edgeDomainForRegion(instance.spec?.region);
|
|
97
|
+
if (!edgeDomain || settings_js_1.settings.disableH2)
|
|
96
98
|
return instance;
|
|
97
|
-
const edgeSuffix = settings_js_1.settings.env === "prod" ? "bl.run" : "runv2.blaxel.dev";
|
|
98
|
-
const edgeDomain = `any.${region}.${edgeSuffix}`;
|
|
99
99
|
try {
|
|
100
100
|
const { h2Pool } = await Promise.resolve().then(() => __importStar(require("../common/h2pool.js")));
|
|
101
101
|
const h2Session = await h2Pool.get(edgeDomain);
|
|
102
102
|
instance.h2Session = h2Session;
|
|
103
103
|
instance.sandbox.h2Session = h2Session;
|
|
104
|
+
instance.sandbox.h2Domain = edgeDomain;
|
|
104
105
|
}
|
|
105
106
|
catch {
|
|
106
107
|
// H2 warming is best-effort; fall back to regular fetch
|
|
107
108
|
}
|
|
108
109
|
return instance;
|
|
109
110
|
}
|
|
111
|
+
static edgeDomainForRegion(region) {
|
|
112
|
+
if (!region)
|
|
113
|
+
return null;
|
|
114
|
+
const edgeSuffix = settings_js_1.settings.env === "prod" ? "bl.run" : "runv2.blaxel.dev";
|
|
115
|
+
return `any.${region}.${edgeSuffix}`;
|
|
116
|
+
}
|
|
110
117
|
get expiresIn() {
|
|
111
118
|
return this.sandbox.expiresIn;
|
|
112
119
|
}
|
|
@@ -201,10 +208,9 @@ class SandboxInstance {
|
|
|
201
208
|
}
|
|
202
209
|
sandbox.spec.runtime.image = sandbox.spec.runtime.image || defaultImage;
|
|
203
210
|
sandbox.spec.runtime.memory = sandbox.spec.runtime.memory || defaultMemory;
|
|
204
|
-
const
|
|
205
|
-
const edgeDomain = sandbox.spec?.region ? `any.${sandbox.spec.region}.${edgeSuffix}` : null;
|
|
211
|
+
const edgeDomain = SandboxInstance.edgeDomainForRegion(sandbox.spec?.region);
|
|
206
212
|
// Kick off warming so h2Pool.get() can join it during the API call
|
|
207
|
-
if (edgeDomain) {
|
|
213
|
+
if (edgeDomain && !settings_js_1.settings.disableH2) {
|
|
208
214
|
Promise.resolve().then(() => __importStar(require("../common/h2pool.js"))).then(({ h2Pool }) => h2Pool.warm(edgeDomain)).catch(() => { });
|
|
209
215
|
}
|
|
210
216
|
const [{ data }, h2Session] = await Promise.all([
|
|
@@ -212,10 +218,10 @@ class SandboxInstance {
|
|
|
212
218
|
body: sandbox,
|
|
213
219
|
throwOnError: true,
|
|
214
220
|
}),
|
|
215
|
-
edgeDomain ? Promise.resolve().then(() => __importStar(require("../common/h2pool.js"))).then(({ h2Pool }) => h2Pool.get(edgeDomain)).catch(() => null) : Promise.resolve(null),
|
|
221
|
+
edgeDomain && !settings_js_1.settings.disableH2 ? Promise.resolve().then(() => __importStar(require("../common/h2pool.js"))).then(({ h2Pool }) => h2Pool.get(edgeDomain)).catch(() => null) : Promise.resolve(null),
|
|
216
222
|
]);
|
|
217
223
|
// Inject the H2 session into the config so subsystems can use it
|
|
218
|
-
const config = { ...data, h2Session };
|
|
224
|
+
const config = { ...data, h2Session, h2Domain: settings_js_1.settings.disableH2 ? null : edgeDomain };
|
|
219
225
|
const instance = new SandboxInstance(config);
|
|
220
226
|
instance.h2Session = h2Session;
|
|
221
227
|
// Note: H2 session already attached via Promise.all above, no need for attachH2Session()
|
|
@@ -224,7 +230,10 @@ class SandboxInstance {
|
|
|
224
230
|
try {
|
|
225
231
|
await instance.fs.ls('/');
|
|
226
232
|
}
|
|
227
|
-
catch {
|
|
233
|
+
catch (err) {
|
|
234
|
+
await SandboxInstance.delete(instance.metadata.name).catch(() => { });
|
|
235
|
+
throw err;
|
|
236
|
+
}
|
|
228
237
|
}
|
|
229
238
|
return instance;
|
|
230
239
|
}
|
|
@@ -255,6 +264,8 @@ class SandboxInstance {
|
|
|
255
264
|
async delete() {
|
|
256
265
|
// Don't close the H2 session — it's shared via h2Pool
|
|
257
266
|
this.h2Session = null;
|
|
267
|
+
this.sandbox.h2Session = null;
|
|
268
|
+
this.sandbox.h2Domain = null;
|
|
258
269
|
return await SandboxInstance.delete(this.metadata.name);
|
|
259
270
|
}
|
|
260
271
|
static async updateMetadata(sandboxName, metadata) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import http2 from "http2";
|
|
2
|
-
import type {
|
|
2
|
+
import type { H2Pool } from "./h2pool.js";
|
|
3
3
|
/**
|
|
4
4
|
* Creates a fetch()-compatible function that sends requests over an existing
|
|
5
5
|
* HTTP/2 session. Falls back to globalThis.fetch() only when the session is
|
|
@@ -12,12 +12,12 @@ export declare function createH2Fetch(session: http2.ClientHttp2Session): (input
|
|
|
12
12
|
/**
|
|
13
13
|
* Creates a fetch()-compatible function backed by the H2 session pool.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
* available
|
|
17
|
-
*
|
|
18
|
-
* so subsequent calls get H2).
|
|
15
|
+
* The pool validates idle sessions before reuse. If no usable H2 session is
|
|
16
|
+
* available, the request falls back to regular fetch before any H2 frames
|
|
17
|
+
* are sent.
|
|
19
18
|
*/
|
|
20
|
-
export declare function createPoolBackedH2Fetch(pool:
|
|
19
|
+
export declare function createPoolBackedH2Fetch(pool: H2Pool, domain: string): (input: Request) => Promise<Response>;
|
|
20
|
+
export declare function h2RequestDirectFromPool(pool: H2Pool, domain: string, url: string, init?: RequestInit): Promise<Response>;
|
|
21
21
|
/**
|
|
22
22
|
* Low-level H2 request that takes raw URL + init, skipping Request construction.
|
|
23
23
|
* Used by SandboxAction.h2Fetch() for direct calls from subsystems.
|