@promigence/sdk 0.1.0-beta.12 → 0.1.0-beta.13
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/caller.js +10 -0
- package/dist/cjs/caller.js +10 -0
- package/dist/cjs/client.d.ts +16 -6
- package/dist/cjs/client.js +1180 -1129
- package/dist/cjs/core.d.ts +1 -1
- package/dist/cjs/core.js +1 -1
- package/dist/cjs/dispatcher.d.ts +2 -0
- package/dist/cjs/dispatcher.js +5 -0
- package/dist/cjs/http2.d.ts +10 -0
- package/dist/cjs/http2.js +143 -51
- package/dist/cjs/keyed.d.ts +6 -0
- package/dist/cjs/keyed.js +59 -0
- package/dist/cjs/proxy.js +11 -0
- package/dist/cjs/sandbox.d.ts +3 -1
- package/dist/cjs/sandbox.js +33 -20
- package/dist/cjs/transport-worker.js +11 -2
- package/dist/client.d.ts +16 -6
- package/dist/client.js +1179 -1127
- package/dist/core.d.ts +1 -1
- package/dist/core.js +1 -1
- package/dist/dispatcher.d.ts +2 -0
- package/dist/dispatcher.js +5 -0
- package/dist/http2.d.ts +10 -0
- package/dist/http2.js +142 -51
- package/dist/keyed.d.ts +6 -0
- package/dist/keyed.js +55 -0
- package/dist/proxy.js +11 -0
- package/dist/sandbox.d.ts +3 -1
- package/dist/sandbox.js +33 -20
- package/dist/transport-worker.js +11 -2
- package/package.json +1 -1
package/dist/core.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Promigence SDK — constants, error hierarchy and shared types. */
|
|
2
|
-
export declare const VERSION = "0.1.0-beta.
|
|
2
|
+
export declare const VERSION = "0.1.0-beta.13";
|
|
3
3
|
export declare const DOCS_URL = "https://promigence.ai";
|
|
4
4
|
/** The error reference; each code is an anchor on it (the API's `docs` link has the same form). */
|
|
5
5
|
export declare const ERRORS_DOCS_URL = "https://www.promigence.ai/docs/errors";
|
package/dist/core.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { hintArg } from "./hint.js";
|
|
2
|
-
export const VERSION = "0.1.0-beta.
|
|
2
|
+
export const VERSION = "0.1.0-beta.13";
|
|
3
3
|
export const DOCS_URL = "https://promigence.ai";
|
|
4
4
|
export const ERRORS_DOCS_URL = "https://www.promigence.ai/docs/errors";
|
|
5
5
|
export const DEFAULT_API_URL = "https://api.promigence.ai/v1";
|
package/dist/dispatcher.d.ts
CHANGED
|
@@ -48,6 +48,8 @@ interface Timeouts {
|
|
|
48
48
|
export declare function longRequestDispatcher(timeoutMs: number, base?: object | undefined): object | undefined;
|
|
49
49
|
/** Test and diagnostics view. Not part of the public API. */
|
|
50
50
|
export declare const __dispatcherInternals: {
|
|
51
|
+
/** How many times `longRequestDispatcher` has been asked (each request on the `fetch` path asks once). */
|
|
52
|
+
lookups(): number;
|
|
51
53
|
/** The timeouts a dispatcher of ours sets, or `undefined` for any other value. */
|
|
52
54
|
timeoutsOf(d: unknown): Timeouts | undefined;
|
|
53
55
|
/** The process's global dispatcher composed with exactly these timeouts (never cached). */
|
package/dist/dispatcher.js
CHANGED
|
@@ -47,7 +47,9 @@ function composeWith(base, ms) {
|
|
|
47
47
|
timeouts.set(d, { headersTimeout: ms, bodyTimeout: ms });
|
|
48
48
|
return d;
|
|
49
49
|
}
|
|
50
|
+
let lookups = 0;
|
|
50
51
|
export function longRequestDispatcher(timeoutMs, base = firstGlobalDispatcher()) {
|
|
52
|
+
lookups++;
|
|
51
53
|
try {
|
|
52
54
|
const needed = timeoutMs + LONG_REQUEST_SLACK_MS;
|
|
53
55
|
if (!(needed > FETCH_DEFAULT_TIMEOUT_MS))
|
|
@@ -73,6 +75,9 @@ export function longRequestDispatcher(timeoutMs, base = firstGlobalDispatcher())
|
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
77
|
export const __dispatcherInternals = {
|
|
78
|
+
lookups() {
|
|
79
|
+
return lookups;
|
|
80
|
+
},
|
|
76
81
|
timeoutsOf(d) {
|
|
77
82
|
return d !== null && typeof d === "object" ? timeouts.get(d) : undefined;
|
|
78
83
|
},
|
package/dist/http2.d.ts
CHANGED
|
@@ -69,6 +69,8 @@ export interface Http2Init {
|
|
|
69
69
|
signal?: AbortSignal;
|
|
70
70
|
/** Read by `fetch` on fallback only. */
|
|
71
71
|
dispatcher?: object;
|
|
72
|
+
/** Worked out on fallback only, when no `dispatcher` was given: the one `fetch` should carry for `timeoutMs`. */
|
|
73
|
+
dispatcherFor?: (timeoutMs: number) => object | undefined;
|
|
72
74
|
/** Phase stamps when `PROMIGENCE_SDK_TRACE=1`. */
|
|
73
75
|
trace?: TraceStamp;
|
|
74
76
|
}
|
|
@@ -142,9 +144,13 @@ export declare class Http2Transport {
|
|
|
142
144
|
*/
|
|
143
145
|
fetch(url: string, init: Http2Init, replayable: boolean): Promise<Response | H2Response>;
|
|
144
146
|
}
|
|
147
|
+
/** The one `Http2Transport` for every client whose fallback is `fallback` (it keeps nothing of a client's). */
|
|
148
|
+
export declare function sharedTransport(fallback: FetchLike): Http2Transport;
|
|
145
149
|
export declare function warmTransport(): void;
|
|
146
150
|
/** Test and diagnostics view of the process-wide pools. Not part of the public API. */
|
|
147
151
|
export declare const __http2Internals: {
|
|
152
|
+
/** How many request URLs have been read (each new URL once; the same URL again is not read again). */
|
|
153
|
+
targetReads(): number;
|
|
148
154
|
pools(): Array<{
|
|
149
155
|
origin: string;
|
|
150
156
|
sessions: number;
|
|
@@ -181,6 +187,8 @@ export declare const __http2Internals: {
|
|
|
181
187
|
answered: number;
|
|
182
188
|
everReady: boolean;
|
|
183
189
|
closedReason: string;
|
|
190
|
+
messages: number;
|
|
191
|
+
posts: number;
|
|
184
192
|
} | undefined;
|
|
185
193
|
/** The transport thread's number of connections and spacing between new requests on one, for an environment (`threadKnobs`). */
|
|
186
194
|
threadKnobs(env: NodeJS.ProcessEnv): {
|
|
@@ -188,6 +196,8 @@ export declare const __http2Internals: {
|
|
|
188
196
|
gapUs: number;
|
|
189
197
|
pace: string;
|
|
190
198
|
};
|
|
199
|
+
/** Tests: make the transport thread stop answering (it reads nothing more), as a stuck one would. */
|
|
200
|
+
wedgeThreadForTest(): void;
|
|
191
201
|
/** Tests: make the transport thread fail as a crash would. */
|
|
192
202
|
crashThreadForTest(): void;
|
|
193
203
|
/** The early lookup, for tests: start one with a platform, DNS servers and hosts file of the test's own. */
|
package/dist/http2.js
CHANGED
|
@@ -845,18 +845,18 @@ const earlies = new Map();
|
|
|
845
845
|
const MAX_EARLIES = 4;
|
|
846
846
|
const EARLY_MAX_AGE_MS = 60_000;
|
|
847
847
|
const EARLY_TIMEOUT_MS = 2_000;
|
|
848
|
-
const
|
|
848
|
+
const DNS_AT_LOAD = (() => {
|
|
849
849
|
try {
|
|
850
|
-
return nodeDns()
|
|
850
|
+
return nodeDns();
|
|
851
851
|
}
|
|
852
852
|
catch {
|
|
853
853
|
return undefined;
|
|
854
854
|
}
|
|
855
855
|
})();
|
|
856
|
+
const LOOKUP_AT_LOAD = DNS_AT_LOAD?.lookup;
|
|
856
857
|
function lookupReplaced() {
|
|
857
858
|
try {
|
|
858
|
-
|
|
859
|
-
return d !== undefined && d.lookup !== LOOKUP_AT_LOAD;
|
|
859
|
+
return DNS_AT_LOAD !== undefined && DNS_AT_LOAD.lookup !== LOOKUP_AT_LOAD;
|
|
860
860
|
}
|
|
861
861
|
catch {
|
|
862
862
|
return true;
|
|
@@ -1290,12 +1290,13 @@ function targetOf(url, known, remember) {
|
|
|
1290
1290
|
}
|
|
1291
1291
|
function fetchInit(init, deadline) {
|
|
1292
1292
|
const timeout = AbortSignal.timeout(Math.max(0, Math.ceil(deadline - performance.now())));
|
|
1293
|
+
const dispatcher = init.dispatcher ?? init.dispatcherFor?.(init.timeoutMs);
|
|
1293
1294
|
return {
|
|
1294
1295
|
method: init.method,
|
|
1295
1296
|
headers: init.headers,
|
|
1296
1297
|
signal: init.signal === undefined ? timeout : AbortSignal.any([timeout, init.signal]),
|
|
1297
1298
|
...(init.body === undefined ? {} : { body: init.body }),
|
|
1298
|
-
...(
|
|
1299
|
+
...(dispatcher === undefined ? {} : { dispatcher }),
|
|
1299
1300
|
};
|
|
1300
1301
|
}
|
|
1301
1302
|
function fetchFailed(cause) {
|
|
@@ -1312,11 +1313,14 @@ class HandBack {
|
|
|
1312
1313
|
const UNSENT = new HandBack(0, 0);
|
|
1313
1314
|
const VIA_FETCH = Symbol("via-fetch");
|
|
1314
1315
|
const THREAD_GRACE_MS = 1_000;
|
|
1316
|
+
const THREAD_SWEEP_MS = 250;
|
|
1317
|
+
const THREAD_MARKS = 4096;
|
|
1315
1318
|
const THREAD_PHASES = ["w.got", "w.sent", "w.head", "w.end"];
|
|
1316
1319
|
class TransportThread {
|
|
1317
1320
|
origin;
|
|
1318
1321
|
worker;
|
|
1319
1322
|
port;
|
|
1323
|
+
acks;
|
|
1320
1324
|
state = "starting";
|
|
1321
1325
|
sessions = 0;
|
|
1322
1326
|
limit = DEFAULT_STREAM_LIMIT;
|
|
@@ -1326,13 +1330,26 @@ class TransportThread {
|
|
|
1326
1330
|
inflight = new Map();
|
|
1327
1331
|
nextId = 1;
|
|
1328
1332
|
listening = false;
|
|
1333
|
+
refd = false;
|
|
1334
|
+
sweep;
|
|
1335
|
+
freeMarks = [];
|
|
1336
|
+
messages = 0;
|
|
1337
|
+
outbox = [];
|
|
1338
|
+
turnOpen = false;
|
|
1339
|
+
posts = 0;
|
|
1329
1340
|
answered = 0;
|
|
1330
1341
|
pace = THREAD_DEFAULTS.pace;
|
|
1331
1342
|
hrOffset = Number(process.hrtime.bigint()) / 1e6 - performance.now();
|
|
1332
|
-
constructor(origin, worker, port) {
|
|
1343
|
+
constructor(origin, worker, port, acks) {
|
|
1333
1344
|
this.origin = origin;
|
|
1334
1345
|
this.worker = worker;
|
|
1335
1346
|
this.port = port;
|
|
1347
|
+
this.acks = acks;
|
|
1348
|
+
for (let i = acks.length - 1; i >= 0; i--)
|
|
1349
|
+
this.freeMarks.push(i);
|
|
1350
|
+
}
|
|
1351
|
+
acked(p) {
|
|
1352
|
+
return Atomics.load(this.acks, p.mark) === p.id;
|
|
1336
1353
|
}
|
|
1337
1354
|
poll() {
|
|
1338
1355
|
for (;;) {
|
|
@@ -1349,12 +1366,13 @@ class TransportThread {
|
|
|
1349
1366
|
}
|
|
1350
1367
|
}
|
|
1351
1368
|
ready() {
|
|
1352
|
-
if (this.state !== "closed")
|
|
1369
|
+
if (!this.listening && this.state !== "closed")
|
|
1353
1370
|
this.poll();
|
|
1354
|
-
return this.state === "ready" && this.sessions > 0 && this.inflight.size < this.sessions * this.limit;
|
|
1371
|
+
return this.state === "ready" && this.sessions > 0 && this.inflight.size < this.sessions * this.limit && this.freeMarks.length > 0;
|
|
1355
1372
|
}
|
|
1356
1373
|
onMessage(m) {
|
|
1357
1374
|
const msg = m;
|
|
1375
|
+
this.messages++;
|
|
1358
1376
|
switch (msg?.t) {
|
|
1359
1377
|
case "ready":
|
|
1360
1378
|
if (this.state === "starting" || this.state === "ready") {
|
|
@@ -1391,12 +1409,6 @@ class TransportThread {
|
|
|
1391
1409
|
case "closed":
|
|
1392
1410
|
this.close(msg.reason ?? "closed");
|
|
1393
1411
|
return;
|
|
1394
|
-
case "ack": {
|
|
1395
|
-
const p = this.inflight.get(msg.id ?? -1);
|
|
1396
|
-
if (p !== undefined)
|
|
1397
|
-
p.acked = true;
|
|
1398
|
-
return;
|
|
1399
|
-
}
|
|
1400
1412
|
case "res":
|
|
1401
1413
|
case "err":
|
|
1402
1414
|
this.settle(m);
|
|
@@ -1480,10 +1492,12 @@ class TransportThread {
|
|
|
1480
1492
|
if (this.closedReason === "")
|
|
1481
1493
|
this.closedReason = reason;
|
|
1482
1494
|
traceEvent("thread:closed");
|
|
1495
|
+
this.outbox = [];
|
|
1483
1496
|
for (const [id, p] of [...this.inflight]) {
|
|
1497
|
+
const sent = this.acked(p);
|
|
1484
1498
|
this.inflight.delete(id);
|
|
1485
1499
|
this.release(p);
|
|
1486
|
-
if (!
|
|
1500
|
+
if (!sent)
|
|
1487
1501
|
p.resolve(UNSENT);
|
|
1488
1502
|
else if (p.replayable)
|
|
1489
1503
|
p.resolve(new HandBack(0, 1));
|
|
@@ -1505,18 +1519,49 @@ class TransportThread {
|
|
|
1505
1519
|
this.poll();
|
|
1506
1520
|
this.port.on("message", (m) => { this.onMessage(m); });
|
|
1507
1521
|
}
|
|
1522
|
+
if (this.refd)
|
|
1523
|
+
return;
|
|
1524
|
+
this.refd = true;
|
|
1508
1525
|
safely(() => { this.port.ref(); });
|
|
1509
1526
|
safely(() => { this.worker.ref(); });
|
|
1527
|
+
this.sweep = setInterval(() => { this.sweepBackstops(); }, THREAD_SWEEP_MS);
|
|
1528
|
+
safely(() => { this.sweep?.unref?.(); });
|
|
1510
1529
|
}
|
|
1511
1530
|
unref() {
|
|
1531
|
+
if (this.sweep !== undefined) {
|
|
1532
|
+
clearInterval(this.sweep);
|
|
1533
|
+
this.sweep = undefined;
|
|
1534
|
+
}
|
|
1535
|
+
if (!this.refd)
|
|
1536
|
+
return;
|
|
1537
|
+
this.refd = false;
|
|
1512
1538
|
safely(() => { this.port.unref(); });
|
|
1513
1539
|
safely(() => { this.worker.unref(); });
|
|
1514
1540
|
}
|
|
1541
|
+
sweepBackstops() {
|
|
1542
|
+
this.poll();
|
|
1543
|
+
const now = performance.now();
|
|
1544
|
+
let late = false;
|
|
1545
|
+
for (const [id, p] of [...this.inflight]) {
|
|
1546
|
+
if (p.backstopAt > now)
|
|
1547
|
+
continue;
|
|
1548
|
+
late = true;
|
|
1549
|
+
this.inflight.delete(id);
|
|
1550
|
+
this.release(p);
|
|
1551
|
+
try {
|
|
1552
|
+
this.port.postMessage({ t: "cancel", id });
|
|
1553
|
+
}
|
|
1554
|
+
catch { }
|
|
1555
|
+
p.reject(timeoutReason());
|
|
1556
|
+
}
|
|
1557
|
+
if (late)
|
|
1558
|
+
this.close("no answer from the transport thread");
|
|
1559
|
+
}
|
|
1515
1560
|
release(p) {
|
|
1516
1561
|
if (p.signal !== undefined && p.onAbort !== undefined)
|
|
1517
1562
|
p.signal.removeEventListener("abort", p.onAbort);
|
|
1518
|
-
|
|
1519
|
-
|
|
1563
|
+
Atomics.store(this.acks, p.mark, 0);
|
|
1564
|
+
this.freeMarks.push(p.mark);
|
|
1520
1565
|
if (this.inflight.size === 0)
|
|
1521
1566
|
this.unref();
|
|
1522
1567
|
}
|
|
@@ -1541,8 +1586,11 @@ class TransportThread {
|
|
|
1541
1586
|
headers["content-length"] = Buffer.byteLength(init.body, "utf8");
|
|
1542
1587
|
const id = this.nextId++;
|
|
1543
1588
|
const tr = init.trace;
|
|
1589
|
+
const place = this.freeMarks.pop();
|
|
1590
|
+
if (place === undefined)
|
|
1591
|
+
return Promise.resolve(UNSENT);
|
|
1544
1592
|
return new Promise((resolve, reject) => {
|
|
1545
|
-
const p = { resolve, reject, method: init.method, replayable,
|
|
1593
|
+
const p = { id, resolve, reject, method: init.method, replayable, mark: place, backstopAt: performance.now() + remaining + THREAD_GRACE_MS, trace: tr };
|
|
1546
1594
|
const signal = init.signal;
|
|
1547
1595
|
if (signal !== undefined) {
|
|
1548
1596
|
p.signal = signal;
|
|
@@ -1551,47 +1599,59 @@ class TransportThread {
|
|
|
1551
1599
|
return;
|
|
1552
1600
|
this.inflight.delete(id);
|
|
1553
1601
|
this.release(p);
|
|
1554
|
-
|
|
1555
|
-
|
|
1602
|
+
const queued = this.outbox.findIndex((b) => b.id === id);
|
|
1603
|
+
if (queued >= 0)
|
|
1604
|
+
this.outbox.splice(queued, 1);
|
|
1605
|
+
else {
|
|
1606
|
+
try {
|
|
1607
|
+
this.port.postMessage({ t: "cancel", id });
|
|
1608
|
+
}
|
|
1609
|
+
catch { }
|
|
1556
1610
|
}
|
|
1557
|
-
catch { }
|
|
1558
1611
|
reject(signal.reason);
|
|
1559
1612
|
};
|
|
1560
1613
|
signal.addEventListener("abort", p.onAbort, { once: true });
|
|
1561
1614
|
}
|
|
1562
|
-
p.backstop = setTimeout(() => {
|
|
1563
|
-
this.poll();
|
|
1564
|
-
if (!this.inflight.has(id))
|
|
1565
|
-
return;
|
|
1566
|
-
this.inflight.delete(id);
|
|
1567
|
-
this.release(p);
|
|
1568
|
-
try {
|
|
1569
|
-
this.port.postMessage({ t: "cancel", id });
|
|
1570
|
-
}
|
|
1571
|
-
catch { }
|
|
1572
|
-
reject(timeoutReason());
|
|
1573
|
-
this.close("no answer from the transport thread");
|
|
1574
|
-
}, remaining + THREAD_GRACE_MS);
|
|
1575
|
-
safely(() => { p.backstop?.unref?.(); });
|
|
1576
1615
|
this.inflight.set(id, p);
|
|
1577
1616
|
this.ref();
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1617
|
+
const paced = this.pace === "all" || (this.pace === "creates" && init.method === "POST" && CREATE_PATH.test(target.path));
|
|
1618
|
+
this.outbox.push({ id, msg: { t: "req", id, mark: place, headers, sensitive: ["authorization"], body: init.body, timeoutMs: remaining, replayable, paced }, p, resolve, trace: tr });
|
|
1619
|
+
if (!this.turnOpen) {
|
|
1620
|
+
this.turnOpen = true;
|
|
1621
|
+
queueMicrotask(() => { this.turnOpen = false; this.handOver(); });
|
|
1622
|
+
this.handOver();
|
|
1581
1623
|
}
|
|
1582
|
-
|
|
1583
|
-
this.
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1624
|
+
else if (this.outbox.length >= OUTBOX_GROUP)
|
|
1625
|
+
this.handOver();
|
|
1626
|
+
});
|
|
1627
|
+
}
|
|
1628
|
+
handOver() {
|
|
1629
|
+
if (this.outbox.length === 0)
|
|
1630
|
+
return;
|
|
1631
|
+
const batch = this.outbox;
|
|
1632
|
+
this.outbox = [];
|
|
1633
|
+
try {
|
|
1634
|
+
this.port.postMessage(batch.length === 1 ? batch[0].msg : { t: "reqs", list: batch.map((b) => b.msg) });
|
|
1635
|
+
this.posts++;
|
|
1636
|
+
}
|
|
1637
|
+
catch {
|
|
1638
|
+
for (const b of batch) {
|
|
1639
|
+
if (!this.inflight.has(b.id))
|
|
1640
|
+
continue;
|
|
1641
|
+
this.inflight.delete(b.id);
|
|
1642
|
+
this.release(b.p);
|
|
1643
|
+
b.resolve(UNSENT);
|
|
1587
1644
|
}
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
for (const b of batch)
|
|
1648
|
+
if (b.trace !== undefined) {
|
|
1649
|
+
mark(b.trace, "sub");
|
|
1650
|
+
b.trace.via = "thread";
|
|
1591
1651
|
}
|
|
1592
|
-
});
|
|
1593
1652
|
}
|
|
1594
1653
|
}
|
|
1654
|
+
const OUTBOX_GROUP = 8;
|
|
1595
1655
|
let transportThread;
|
|
1596
1656
|
function threadFor(origin) {
|
|
1597
1657
|
const t = transportThread;
|
|
@@ -1636,9 +1696,10 @@ function startTransportThread(origin, onFail) {
|
|
|
1636
1696
|
const knobs = threadKnobs();
|
|
1637
1697
|
const file = typeof seam.transportFile === "string" ? seam.transportFile : here.replace(/http2\.js$/, "transport-worker.js");
|
|
1638
1698
|
const { port1, port2 } = new MessageChannel();
|
|
1699
|
+
const acks = new Int32Array(new SharedArrayBuffer(4 * THREAD_MARKS));
|
|
1639
1700
|
const worker = new Worker(file.startsWith("file:") ? new URL(file) : file, {
|
|
1640
1701
|
workerData: {
|
|
1641
|
-
promigenceTransport: true, port: port2, origin: origin.origin, sessions: knobs.sessions, gapUs: knobs.gapUs,
|
|
1702
|
+
promigenceTransport: true, port: port2, acks, origin: origin.origin, sessions: knobs.sessions, gapUs: knobs.gapUs,
|
|
1642
1703
|
firstUseMs: tuning.loadFirstUseMs, idleMs: IDLE_CLOSE_MS, connectTimeoutMs: CONNECT_TIMEOUT_MS,
|
|
1643
1704
|
lookupTimeoutMs: EARLY_TIMEOUT_MS, trace: TRACE_ON,
|
|
1644
1705
|
servers: tuning.earlyServers ?? (Array.isArray(seam.servers) ? seam.servers : undefined),
|
|
@@ -1651,7 +1712,7 @@ function startTransportThread(origin, onFail) {
|
|
|
1651
1712
|
});
|
|
1652
1713
|
worker.unref();
|
|
1653
1714
|
port1.unref();
|
|
1654
|
-
const t = new TransportThread(origin.origin, worker, port1);
|
|
1715
|
+
const t = new TransportThread(origin.origin, worker, port1, acks);
|
|
1655
1716
|
t.pace = knobs.pace;
|
|
1656
1717
|
t.onFail = onFail;
|
|
1657
1718
|
worker.on("error", () => { t.poll(); t.close("error"); });
|
|
@@ -1673,6 +1734,18 @@ function knownPrefix(url) {
|
|
|
1673
1734
|
}
|
|
1674
1735
|
return undefined;
|
|
1675
1736
|
}
|
|
1737
|
+
let lastTargetUrl;
|
|
1738
|
+
let lastTargetRead;
|
|
1739
|
+
let targetReads = 0;
|
|
1740
|
+
function lastTarget(url) {
|
|
1741
|
+
if (url === lastTargetUrl)
|
|
1742
|
+
return lastTargetRead;
|
|
1743
|
+
targetReads++;
|
|
1744
|
+
const target = targetOf(url, knownPrefix(url), remember);
|
|
1745
|
+
lastTargetUrl = url;
|
|
1746
|
+
lastTargetRead = target;
|
|
1747
|
+
return target;
|
|
1748
|
+
}
|
|
1676
1749
|
function remember(p) {
|
|
1677
1750
|
for (let i = 0; i < prefixes.length; i++)
|
|
1678
1751
|
if (prefixes[i].text === p.text)
|
|
@@ -1708,7 +1781,7 @@ export class Http2Transport {
|
|
|
1708
1781
|
const deadline = performance.now() + init.timeoutMs;
|
|
1709
1782
|
let target;
|
|
1710
1783
|
try {
|
|
1711
|
-
target =
|
|
1784
|
+
target = lastTarget(url);
|
|
1712
1785
|
}
|
|
1713
1786
|
catch {
|
|
1714
1787
|
return this.viaFetch(url, init, deadline);
|
|
@@ -1768,6 +1841,15 @@ export class Http2Transport {
|
|
|
1768
1841
|
}
|
|
1769
1842
|
}
|
|
1770
1843
|
}
|
|
1844
|
+
let sharedFallback;
|
|
1845
|
+
let shared;
|
|
1846
|
+
export function sharedTransport(fallback) {
|
|
1847
|
+
if (shared === undefined || sharedFallback !== fallback) {
|
|
1848
|
+
shared = new Http2Transport(fallback);
|
|
1849
|
+
sharedFallback = fallback;
|
|
1850
|
+
}
|
|
1851
|
+
return shared;
|
|
1852
|
+
}
|
|
1771
1853
|
export function warmTransport() {
|
|
1772
1854
|
const res = toResponse(200, { "content-type": "application/json" }, Buffer.from("{}"), "POST");
|
|
1773
1855
|
res.header("server-timing");
|
|
@@ -1780,6 +1862,9 @@ export function warmTransport() {
|
|
|
1780
1862
|
exchange({ end() { } }, undefined, { origin: none, host: none, path: none }, undefined, 0).catch(() => { });
|
|
1781
1863
|
}
|
|
1782
1864
|
export const __http2Internals = {
|
|
1865
|
+
targetReads() {
|
|
1866
|
+
return targetReads;
|
|
1867
|
+
},
|
|
1783
1868
|
pools() {
|
|
1784
1869
|
return [...pools.values()].map((p) => ({
|
|
1785
1870
|
origin: p.origin.origin,
|
|
@@ -1820,11 +1905,17 @@ export const __http2Internals = {
|
|
|
1820
1905
|
if (t === undefined)
|
|
1821
1906
|
return undefined;
|
|
1822
1907
|
t.poll();
|
|
1823
|
-
return { state: t.state, sessions: t.sessions, limit: t.limit, answered: t.answered, everReady: t.everReady, closedReason: t.closedReason };
|
|
1908
|
+
return { state: t.state, sessions: t.sessions, limit: t.limit, answered: t.answered, everReady: t.everReady, closedReason: t.closedReason, messages: t.messages, posts: t.posts };
|
|
1824
1909
|
},
|
|
1825
1910
|
threadKnobs(env) {
|
|
1826
1911
|
return threadKnobs(env);
|
|
1827
1912
|
},
|
|
1913
|
+
wedgeThreadForTest() {
|
|
1914
|
+
try {
|
|
1915
|
+
transportThread?.port.postMessage({ t: "wedge-for-test" });
|
|
1916
|
+
}
|
|
1917
|
+
catch { }
|
|
1918
|
+
},
|
|
1828
1919
|
crashThreadForTest() {
|
|
1829
1920
|
try {
|
|
1830
1921
|
transportThread?.port.postMessage({ t: "crash-for-test" });
|
package/dist/keyed.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Is `method path` a create that takes an `Idempotency-Key`? `path` is relative to `/v1` and may
|
|
3
|
+
* carry a query string. The list is the API reference's (generated from the operations that
|
|
4
|
+
* document the header), so a create the API keys is never sent unkeyed by this client.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isKeyedCreate(method: string, path: string): boolean;
|
package/dist/keyed.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { KEYED_CREATES } from "./keyed.gen.js";
|
|
2
|
+
const KEYED_PATTERNS = KEYED_CREATES.map(([m, p]) => [m, p.split("/").filter(Boolean)]);
|
|
3
|
+
const KEYED_METHODS = new Set(KEYED_CREATES.map(([m]) => m));
|
|
4
|
+
let keyedLastMethod = "";
|
|
5
|
+
let keyedLastPath = "";
|
|
6
|
+
let keyedLastAnswer = false;
|
|
7
|
+
let keyedReads = 0;
|
|
8
|
+
export function isKeyedCreate(method, path) {
|
|
9
|
+
if (!KEYED_METHODS.has(method))
|
|
10
|
+
return false;
|
|
11
|
+
if (path === keyedLastPath && method === keyedLastMethod)
|
|
12
|
+
return keyedLastAnswer;
|
|
13
|
+
const answer = keyedCreateOf(method, path);
|
|
14
|
+
keyedLastMethod = method;
|
|
15
|
+
keyedLastPath = path;
|
|
16
|
+
keyedLastAnswer = answer;
|
|
17
|
+
return answer;
|
|
18
|
+
}
|
|
19
|
+
function keyedCreateOf(method, path) {
|
|
20
|
+
keyedReads++;
|
|
21
|
+
let end = path.indexOf("?");
|
|
22
|
+
if (end < 0)
|
|
23
|
+
end = path.length;
|
|
24
|
+
for (let p = 0; p < KEYED_PATTERNS.length; p++) {
|
|
25
|
+
const entry = KEYED_PATTERNS[p];
|
|
26
|
+
if (entry[0] === method && segmentsMatch(path, end, entry[1]))
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
function segmentsMatch(path, end, pat) {
|
|
32
|
+
let i = 0;
|
|
33
|
+
let k = 0;
|
|
34
|
+
for (;;) {
|
|
35
|
+
while (i < end && path.charCodeAt(i) === 0x2f)
|
|
36
|
+
i++;
|
|
37
|
+
if (i >= end)
|
|
38
|
+
return k === pat.length;
|
|
39
|
+
if (k >= pat.length)
|
|
40
|
+
return false;
|
|
41
|
+
let j = i;
|
|
42
|
+
while (j < end && path.charCodeAt(j) !== 0x2f)
|
|
43
|
+
j++;
|
|
44
|
+
const s = pat[k];
|
|
45
|
+
if (s.charCodeAt(0) !== 0x7b && (j - i !== s.length || !path.startsWith(s, i)))
|
|
46
|
+
return false;
|
|
47
|
+
k++;
|
|
48
|
+
i = j;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export const __keyedInternals = {
|
|
52
|
+
reads() {
|
|
53
|
+
return keyedReads;
|
|
54
|
+
},
|
|
55
|
+
};
|
package/dist/proxy.js
CHANGED
|
@@ -57,12 +57,23 @@ export function noProxy(host, port, env = process.env) {
|
|
|
57
57
|
}
|
|
58
58
|
return false;
|
|
59
59
|
}
|
|
60
|
+
let proxyLastInputs;
|
|
61
|
+
let proxyLast;
|
|
60
62
|
export function proxyFor(url, env = process.env) {
|
|
61
63
|
const https = envOf(env, "HTTPS_PROXY");
|
|
62
64
|
const http = envOf(env, "HTTP_PROXY");
|
|
63
65
|
const all = envOf(env, "ALL_PROXY");
|
|
64
66
|
if (https === undefined && http === undefined && all === undefined)
|
|
65
67
|
return undefined;
|
|
68
|
+
const inputs = `${url}\n${https ?? "\0"}\n${http ?? "\0"}\n${all ?? "\0"}\n${envOf(env, "NO_PROXY") ?? "\0"}`;
|
|
69
|
+
if (inputs === proxyLastInputs)
|
|
70
|
+
return proxyLast;
|
|
71
|
+
const found = proxyForOf(url, env, https, http, all);
|
|
72
|
+
proxyLastInputs = inputs;
|
|
73
|
+
proxyLast = found;
|
|
74
|
+
return found;
|
|
75
|
+
}
|
|
76
|
+
function proxyForOf(url, env, https, http, all) {
|
|
66
77
|
let u;
|
|
67
78
|
try {
|
|
68
79
|
u = new URL(url);
|
package/dist/sandbox.d.ts
CHANGED
|
@@ -396,7 +396,9 @@ export declare class Sandbox {
|
|
|
396
396
|
}>;
|
|
397
397
|
/**
|
|
398
398
|
* `forkHandles`, plus what `create` needs to end a run it was interrupted starting: the key the
|
|
399
|
-
* create is sent under, a signal for it and its waits, and the start options as sent
|
|
399
|
+
* create is sent under, a signal for it and its waits, and the start options as sent (`onStart`
|
|
400
|
+
* is handed the very options sent, the signal included); `client` and `count` stand in for the
|
|
401
|
+
* options' own.
|
|
400
402
|
*/
|
|
401
403
|
private static forkHandlesWith;
|
|
402
404
|
/** Attach to a running sandbox (`GET /sandboxes/:id`). */
|
package/dist/sandbox.js
CHANGED
|
@@ -168,16 +168,19 @@ function onSigint() {
|
|
|
168
168
|
return;
|
|
169
169
|
void Promise.allSettled(cleanups.map((f) => f())).finally(() => { process.kill(process.pid, "SIGINT"); });
|
|
170
170
|
}
|
|
171
|
+
function createAborted() {
|
|
172
|
+
return new TimeoutError("Sandbox.create was aborted by the caller's signal; anything it had started was ended", "Nothing to fix: the caller aborted it.");
|
|
173
|
+
}
|
|
171
174
|
function holdSigint(cleanup) {
|
|
172
175
|
if (typeof process === "undefined" || typeof process.listenerCount !== "function")
|
|
173
|
-
return
|
|
174
|
-
if (process.listenerCount("SIGINT") - (sigintHeld ? 1 : 0) > 0)
|
|
175
|
-
return () => { };
|
|
176
|
-
creatingNow.add(cleanup);
|
|
176
|
+
return undefined;
|
|
177
177
|
if (!sigintHeld) {
|
|
178
|
+
if (process.listenerCount("SIGINT") > 0)
|
|
179
|
+
return undefined;
|
|
178
180
|
process.once("SIGINT", onSigint);
|
|
179
181
|
sigintHeld = true;
|
|
180
182
|
}
|
|
183
|
+
creatingNow.add(cleanup);
|
|
181
184
|
return () => {
|
|
182
185
|
creatingNow.delete(cleanup);
|
|
183
186
|
if (creatingNow.size === 0 && sigintHeld) {
|
|
@@ -455,7 +458,7 @@ export class Sandbox {
|
|
|
455
458
|
...(opts.apiKey === undefined ? {} : { apiKey: opts.apiKey }),
|
|
456
459
|
...(opts.apiUrl === undefined ? {} : { apiUrl: opts.apiUrl }),
|
|
457
460
|
});
|
|
458
|
-
const aborted =
|
|
461
|
+
const aborted = createAborted;
|
|
459
462
|
if (opts.signal?.aborted)
|
|
460
463
|
throw aborted();
|
|
461
464
|
if (opts.image !== undefined) {
|
|
@@ -476,24 +479,31 @@ export class Sandbox {
|
|
|
476
479
|
else if (snapshotIdOrAlias === undefined || snapshotIdOrAlias === "base") {
|
|
477
480
|
snapshotIdOrAlias = DEFAULT_SNAPSHOT;
|
|
478
481
|
}
|
|
479
|
-
const stop = new AbortController();
|
|
480
482
|
const key = newIdempotencyKey();
|
|
481
483
|
let sent;
|
|
482
484
|
let runId;
|
|
483
485
|
let ending;
|
|
484
486
|
const end = () => (ending ??= endInterruptedCreate(client, runId, sent));
|
|
485
|
-
|
|
486
|
-
const
|
|
487
|
-
opts.signal
|
|
487
|
+
let stop;
|
|
488
|
+
const release = holdSigint(() => { stop?.abort(); return end(); });
|
|
489
|
+
const callerSignal = opts.signal;
|
|
490
|
+
const onAbort = callerSignal === undefined ? undefined : () => { stop?.abort(); };
|
|
491
|
+
if (release !== undefined || callerSignal !== undefined)
|
|
492
|
+
stop = new AbortController();
|
|
493
|
+
if (onAbort !== undefined)
|
|
494
|
+
callerSignal.addEventListener("abort", onAbort, { once: true });
|
|
488
495
|
try {
|
|
489
496
|
let handles;
|
|
490
497
|
try {
|
|
491
|
-
|
|
492
|
-
idempotencyKey: key,
|
|
493
|
-
}
|
|
498
|
+
const extra = {
|
|
499
|
+
idempotencyKey: key, onStart: (s) => { sent = s; }, client, count: 1,
|
|
500
|
+
};
|
|
501
|
+
if (stop !== undefined)
|
|
502
|
+
extra.signal = stop.signal;
|
|
503
|
+
({ runId, handles } = await Sandbox.forkHandlesWith(snapshotIdOrAlias, opts, extra));
|
|
494
504
|
}
|
|
495
505
|
catch (e) {
|
|
496
|
-
if (stop
|
|
506
|
+
if (stop?.signal.aborted) {
|
|
497
507
|
await end();
|
|
498
508
|
throw aborted();
|
|
499
509
|
}
|
|
@@ -506,7 +516,7 @@ export class Sandbox {
|
|
|
506
516
|
return await first;
|
|
507
517
|
}
|
|
508
518
|
catch (e) {
|
|
509
|
-
if (stop
|
|
519
|
+
if (stop?.signal.aborted) {
|
|
510
520
|
await end();
|
|
511
521
|
throw aborted();
|
|
512
522
|
}
|
|
@@ -518,8 +528,9 @@ export class Sandbox {
|
|
|
518
528
|
}
|
|
519
529
|
}
|
|
520
530
|
finally {
|
|
521
|
-
release();
|
|
522
|
-
|
|
531
|
+
release?.();
|
|
532
|
+
if (onAbort !== undefined)
|
|
533
|
+
callerSignal.removeEventListener("abort", onAbort);
|
|
523
534
|
}
|
|
524
535
|
}
|
|
525
536
|
static async fork(snapshotIdOrAlias, opts = {}) {
|
|
@@ -530,11 +541,11 @@ export class Sandbox {
|
|
|
530
541
|
return Sandbox.forkHandlesWith(snapshotIdOrAlias, opts, {});
|
|
531
542
|
}
|
|
532
543
|
static async forkHandlesWith(snapshotIdOrAlias, opts, extra) {
|
|
533
|
-
const client = opts.client ?? new PromigenceClient({
|
|
544
|
+
const client = extra.client ?? opts.client ?? new PromigenceClient({
|
|
534
545
|
...(opts.apiKey === undefined ? {} : { apiKey: opts.apiKey }),
|
|
535
546
|
...(opts.apiUrl === undefined ? {} : { apiUrl: opts.apiUrl }),
|
|
536
547
|
});
|
|
537
|
-
const count = opts.count ?? 1;
|
|
548
|
+
const count = extra.count ?? opts.count ?? 1;
|
|
538
549
|
const spendCap = spendCapFromEnv(opts.spendCap);
|
|
539
550
|
const start = { snapshot: snapshotIdOrAlias, count };
|
|
540
551
|
if (spendCap !== undefined)
|
|
@@ -555,8 +566,10 @@ export class Sandbox {
|
|
|
555
566
|
start.optimizeFor = opts.optimizeFor;
|
|
556
567
|
if (extra.idempotencyKey !== undefined)
|
|
557
568
|
start.idempotencyKey = extra.idempotencyKey;
|
|
558
|
-
extra.
|
|
559
|
-
|
|
569
|
+
if (extra.signal !== undefined)
|
|
570
|
+
start.signal = extra.signal;
|
|
571
|
+
extra.onStart?.(start);
|
|
572
|
+
const run = await client.runs.start(start);
|
|
560
573
|
const envs = opts.envs;
|
|
561
574
|
const mk = (ref) => {
|
|
562
575
|
const init = { sandboxId: ref.sandbox_id, runId: run.run_id, snapshot: snapshotIdOrAlias, client };
|