@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.
@@ -1,5 +1,5 @@
1
1
  /** Promigence SDK — constants, error hierarchy and shared types. */
2
- export declare const VERSION = "0.1.0-beta.12";
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/cjs/core.js CHANGED
@@ -7,7 +7,7 @@ exports.fixBeyondMessage = fixBeyondMessage;
7
7
  exports.previewsOff = previewsOff;
8
8
  exports.exitForCode = exitForCode;
9
9
  const hint_js_1 = require("./hint.js");
10
- exports.VERSION = "0.1.0-beta.12";
10
+ exports.VERSION = "0.1.0-beta.13";
11
11
  exports.DOCS_URL = "https://promigence.ai";
12
12
  exports.ERRORS_DOCS_URL = "https://www.promigence.ai/docs/errors";
13
13
  exports.DEFAULT_API_URL = "https://api.promigence.ai/v1";
@@ -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). */
@@ -52,7 +52,9 @@ function composeWith(base, ms) {
52
52
  timeouts.set(d, { headersTimeout: ms, bodyTimeout: ms });
53
53
  return d;
54
54
  }
55
+ let lookups = 0;
55
56
  function longRequestDispatcher(timeoutMs, base = firstGlobalDispatcher()) {
57
+ lookups++;
56
58
  try {
57
59
  const needed = timeoutMs + exports.LONG_REQUEST_SLACK_MS;
58
60
  if (!(needed > exports.FETCH_DEFAULT_TIMEOUT_MS))
@@ -78,6 +80,9 @@ function longRequestDispatcher(timeoutMs, base = firstGlobalDispatcher()) {
78
80
  }
79
81
  }
80
82
  exports.__dispatcherInternals = {
83
+ lookups() {
84
+ return lookups;
85
+ },
81
86
  timeoutsOf(d) {
82
87
  return d !== null && typeof d === "object" ? timeouts.get(d) : undefined;
83
88
  },
@@ -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/cjs/http2.js CHANGED
@@ -6,6 +6,7 @@ exports.warmTls = warmTls;
6
6
  exports.parseSessions = parseSessions;
7
7
  exports.extraOrders = extraOrders;
8
8
  exports.lookupApiName = lookupApiName;
9
+ exports.sharedTransport = sharedTransport;
9
10
  exports.warmTransport = warmTransport;
10
11
  const http2 = require("node:http2");
11
12
  const node_net_1 = require("node:net");
@@ -855,18 +856,18 @@ const earlies = new Map();
855
856
  const MAX_EARLIES = 4;
856
857
  const EARLY_MAX_AGE_MS = 60_000;
857
858
  const EARLY_TIMEOUT_MS = 2_000;
858
- const LOOKUP_AT_LOAD = (() => {
859
+ const DNS_AT_LOAD = (() => {
859
860
  try {
860
- return nodeDns()?.lookup;
861
+ return nodeDns();
861
862
  }
862
863
  catch {
863
864
  return undefined;
864
865
  }
865
866
  })();
867
+ const LOOKUP_AT_LOAD = DNS_AT_LOAD?.lookup;
866
868
  function lookupReplaced() {
867
869
  try {
868
- const d = nodeDns();
869
- return d !== undefined && d.lookup !== LOOKUP_AT_LOAD;
870
+ return DNS_AT_LOAD !== undefined && DNS_AT_LOAD.lookup !== LOOKUP_AT_LOAD;
870
871
  }
871
872
  catch {
872
873
  return true;
@@ -1300,12 +1301,13 @@ function targetOf(url, known, remember) {
1300
1301
  }
1301
1302
  function fetchInit(init, deadline) {
1302
1303
  const timeout = AbortSignal.timeout(Math.max(0, Math.ceil(deadline - performance.now())));
1304
+ const dispatcher = init.dispatcher ?? init.dispatcherFor?.(init.timeoutMs);
1303
1305
  return {
1304
1306
  method: init.method,
1305
1307
  headers: init.headers,
1306
1308
  signal: init.signal === undefined ? timeout : AbortSignal.any([timeout, init.signal]),
1307
1309
  ...(init.body === undefined ? {} : { body: init.body }),
1308
- ...(init.dispatcher === undefined ? {} : { dispatcher: init.dispatcher }),
1310
+ ...(dispatcher === undefined ? {} : { dispatcher }),
1309
1311
  };
1310
1312
  }
1311
1313
  function fetchFailed(cause) {
@@ -1322,11 +1324,14 @@ class HandBack {
1322
1324
  const UNSENT = new HandBack(0, 0);
1323
1325
  const VIA_FETCH = Symbol("via-fetch");
1324
1326
  const THREAD_GRACE_MS = 1_000;
1327
+ const THREAD_SWEEP_MS = 250;
1328
+ const THREAD_MARKS = 4096;
1325
1329
  const THREAD_PHASES = ["w.got", "w.sent", "w.head", "w.end"];
1326
1330
  class TransportThread {
1327
1331
  origin;
1328
1332
  worker;
1329
1333
  port;
1334
+ acks;
1330
1335
  state = "starting";
1331
1336
  sessions = 0;
1332
1337
  limit = DEFAULT_STREAM_LIMIT;
@@ -1336,13 +1341,26 @@ class TransportThread {
1336
1341
  inflight = new Map();
1337
1342
  nextId = 1;
1338
1343
  listening = false;
1344
+ refd = false;
1345
+ sweep;
1346
+ freeMarks = [];
1347
+ messages = 0;
1348
+ outbox = [];
1349
+ turnOpen = false;
1350
+ posts = 0;
1339
1351
  answered = 0;
1340
1352
  pace = THREAD_DEFAULTS.pace;
1341
1353
  hrOffset = Number(process.hrtime.bigint()) / 1e6 - performance.now();
1342
- constructor(origin, worker, port) {
1354
+ constructor(origin, worker, port, acks) {
1343
1355
  this.origin = origin;
1344
1356
  this.worker = worker;
1345
1357
  this.port = port;
1358
+ this.acks = acks;
1359
+ for (let i = acks.length - 1; i >= 0; i--)
1360
+ this.freeMarks.push(i);
1361
+ }
1362
+ acked(p) {
1363
+ return Atomics.load(this.acks, p.mark) === p.id;
1346
1364
  }
1347
1365
  poll() {
1348
1366
  for (;;) {
@@ -1359,12 +1377,13 @@ class TransportThread {
1359
1377
  }
1360
1378
  }
1361
1379
  ready() {
1362
- if (this.state !== "closed")
1380
+ if (!this.listening && this.state !== "closed")
1363
1381
  this.poll();
1364
- return this.state === "ready" && this.sessions > 0 && this.inflight.size < this.sessions * this.limit;
1382
+ return this.state === "ready" && this.sessions > 0 && this.inflight.size < this.sessions * this.limit && this.freeMarks.length > 0;
1365
1383
  }
1366
1384
  onMessage(m) {
1367
1385
  const msg = m;
1386
+ this.messages++;
1368
1387
  switch (msg?.t) {
1369
1388
  case "ready":
1370
1389
  if (this.state === "starting" || this.state === "ready") {
@@ -1401,12 +1420,6 @@ class TransportThread {
1401
1420
  case "closed":
1402
1421
  this.close(msg.reason ?? "closed");
1403
1422
  return;
1404
- case "ack": {
1405
- const p = this.inflight.get(msg.id ?? -1);
1406
- if (p !== undefined)
1407
- p.acked = true;
1408
- return;
1409
- }
1410
1423
  case "res":
1411
1424
  case "err":
1412
1425
  this.settle(m);
@@ -1490,10 +1503,12 @@ class TransportThread {
1490
1503
  if (this.closedReason === "")
1491
1504
  this.closedReason = reason;
1492
1505
  (0, trace_js_1.traceEvent)("thread:closed");
1506
+ this.outbox = [];
1493
1507
  for (const [id, p] of [...this.inflight]) {
1508
+ const sent = this.acked(p);
1494
1509
  this.inflight.delete(id);
1495
1510
  this.release(p);
1496
- if (!p.acked)
1511
+ if (!sent)
1497
1512
  p.resolve(UNSENT);
1498
1513
  else if (p.replayable)
1499
1514
  p.resolve(new HandBack(0, 1));
@@ -1515,18 +1530,49 @@ class TransportThread {
1515
1530
  this.poll();
1516
1531
  this.port.on("message", (m) => { this.onMessage(m); });
1517
1532
  }
1533
+ if (this.refd)
1534
+ return;
1535
+ this.refd = true;
1518
1536
  safely(() => { this.port.ref(); });
1519
1537
  safely(() => { this.worker.ref(); });
1538
+ this.sweep = setInterval(() => { this.sweepBackstops(); }, THREAD_SWEEP_MS);
1539
+ safely(() => { this.sweep?.unref?.(); });
1520
1540
  }
1521
1541
  unref() {
1542
+ if (this.sweep !== undefined) {
1543
+ clearInterval(this.sweep);
1544
+ this.sweep = undefined;
1545
+ }
1546
+ if (!this.refd)
1547
+ return;
1548
+ this.refd = false;
1522
1549
  safely(() => { this.port.unref(); });
1523
1550
  safely(() => { this.worker.unref(); });
1524
1551
  }
1552
+ sweepBackstops() {
1553
+ this.poll();
1554
+ const now = performance.now();
1555
+ let late = false;
1556
+ for (const [id, p] of [...this.inflight]) {
1557
+ if (p.backstopAt > now)
1558
+ continue;
1559
+ late = true;
1560
+ this.inflight.delete(id);
1561
+ this.release(p);
1562
+ try {
1563
+ this.port.postMessage({ t: "cancel", id });
1564
+ }
1565
+ catch { }
1566
+ p.reject(timeoutReason());
1567
+ }
1568
+ if (late)
1569
+ this.close("no answer from the transport thread");
1570
+ }
1525
1571
  release(p) {
1526
1572
  if (p.signal !== undefined && p.onAbort !== undefined)
1527
1573
  p.signal.removeEventListener("abort", p.onAbort);
1528
- if (p.backstop !== undefined)
1529
- clearTimeout(p.backstop);
1574
+ Atomics.store(this.acks, p.mark, 0);
1575
+ this.freeMarks.push(p.mark);
1530
1576
  if (this.inflight.size === 0)
1531
1577
  this.unref();
1532
1578
  }
@@ -1551,8 +1597,11 @@ class TransportThread {
1551
1597
  headers["content-length"] = Buffer.byteLength(init.body, "utf8");
1552
1598
  const id = this.nextId++;
1553
1599
  const tr = init.trace;
1600
+ const place = this.freeMarks.pop();
1601
+ if (place === undefined)
1602
+ return Promise.resolve(UNSENT);
1554
1603
  return new Promise((resolve, reject) => {
1555
- const p = { resolve, reject, method: init.method, replayable, acked: false, trace: tr };
1604
+ const p = { id, resolve, reject, method: init.method, replayable, mark: place, backstopAt: performance.now() + remaining + THREAD_GRACE_MS, trace: tr };
1556
1605
  const signal = init.signal;
1557
1606
  if (signal !== undefined) {
1558
1607
  p.signal = signal;
@@ -1561,47 +1610,59 @@ class TransportThread {
1561
1610
  return;
1562
1611
  this.inflight.delete(id);
1563
1612
  this.release(p);
1564
- try {
1565
- this.port.postMessage({ t: "cancel", id });
1613
+ const queued = this.outbox.findIndex((b) => b.id === id);
1614
+ if (queued >= 0)
1615
+ this.outbox.splice(queued, 1);
1616
+ else {
1617
+ try {
1618
+ this.port.postMessage({ t: "cancel", id });
1619
+ }
1620
+ catch { }
1566
1621
  }
1567
- catch { }
1568
1622
  reject(signal.reason);
1569
1623
  };
1570
1624
  signal.addEventListener("abort", p.onAbort, { once: true });
1571
1625
  }
1572
- p.backstop = setTimeout(() => {
1573
- this.poll();
1574
- if (!this.inflight.has(id))
1575
- return;
1576
- this.inflight.delete(id);
1577
- this.release(p);
1578
- try {
1579
- this.port.postMessage({ t: "cancel", id });
1580
- }
1581
- catch { }
1582
- reject(timeoutReason());
1583
- this.close("no answer from the transport thread");
1584
- }, remaining + THREAD_GRACE_MS);
1585
- safely(() => { p.backstop?.unref?.(); });
1586
1626
  this.inflight.set(id, p);
1587
1627
  this.ref();
1588
- try {
1589
- const paced = this.pace === "all" || (this.pace === "creates" && init.method === "POST" && CREATE_PATH.test(target.path));
1590
- this.port.postMessage({ t: "req", id, headers, sensitive: ["authorization"], body: init.body, timeoutMs: remaining, replayable, paced });
1628
+ const paced = this.pace === "all" || (this.pace === "creates" && init.method === "POST" && CREATE_PATH.test(target.path));
1629
+ this.outbox.push({ id, msg: { t: "req", id, mark: place, headers, sensitive: ["authorization"], body: init.body, timeoutMs: remaining, replayable, paced }, p, resolve, trace: tr });
1630
+ if (!this.turnOpen) {
1631
+ this.turnOpen = true;
1632
+ queueMicrotask(() => { this.turnOpen = false; this.handOver(); });
1633
+ this.handOver();
1591
1634
  }
1592
- catch {
1593
- this.inflight.delete(id);
1594
- this.release(p);
1595
- resolve(UNSENT);
1596
- return;
1635
+ else if (this.outbox.length >= OUTBOX_GROUP)
1636
+ this.handOver();
1637
+ });
1638
+ }
1639
+ handOver() {
1640
+ if (this.outbox.length === 0)
1641
+ return;
1642
+ const batch = this.outbox;
1643
+ this.outbox = [];
1644
+ try {
1645
+ this.port.postMessage(batch.length === 1 ? batch[0].msg : { t: "reqs", list: batch.map((b) => b.msg) });
1646
+ this.posts++;
1647
+ }
1648
+ catch {
1649
+ for (const b of batch) {
1650
+ if (!this.inflight.has(b.id))
1651
+ continue;
1652
+ this.inflight.delete(b.id);
1653
+ this.release(b.p);
1654
+ b.resolve(UNSENT);
1597
1655
  }
1598
- if (tr !== undefined) {
1599
- (0, trace_js_1.mark)(tr, "sub");
1600
- tr.via = "thread";
1656
+ return;
1657
+ }
1658
+ for (const b of batch)
1659
+ if (b.trace !== undefined) {
1660
+ (0, trace_js_1.mark)(b.trace, "sub");
1661
+ b.trace.via = "thread";
1601
1662
  }
1602
- });
1603
1663
  }
1604
1664
  }
1665
+ const OUTBOX_GROUP = 8;
1605
1666
  let transportThread;
1606
1667
  function threadFor(origin) {
1607
1668
  const t = transportThread;
@@ -1646,9 +1707,10 @@ function startTransportThread(origin, onFail) {
1646
1707
  const knobs = threadKnobs();
1647
1708
  const file = typeof seam.transportFile === "string" ? seam.transportFile : here.replace(/http2\.js$/, "transport-worker.js");
1648
1709
  const { port1, port2 } = new node_worker_threads_1.MessageChannel();
1710
+ const acks = new Int32Array(new SharedArrayBuffer(4 * THREAD_MARKS));
1649
1711
  const worker = new node_worker_threads_1.Worker(file.startsWith("file:") ? new URL(file) : file, {
1650
1712
  workerData: {
1651
- promigenceTransport: true, port: port2, origin: origin.origin, sessions: knobs.sessions, gapUs: knobs.gapUs,
1713
+ promigenceTransport: true, port: port2, acks, origin: origin.origin, sessions: knobs.sessions, gapUs: knobs.gapUs,
1652
1714
  firstUseMs: exports.tuning.loadFirstUseMs, idleMs: IDLE_CLOSE_MS, connectTimeoutMs: CONNECT_TIMEOUT_MS,
1653
1715
  lookupTimeoutMs: EARLY_TIMEOUT_MS, trace: trace_js_1.TRACE_ON,
1654
1716
  servers: exports.tuning.earlyServers ?? (Array.isArray(seam.servers) ? seam.servers : undefined),
@@ -1661,7 +1723,7 @@ function startTransportThread(origin, onFail) {
1661
1723
  });
1662
1724
  worker.unref();
1663
1725
  port1.unref();
1664
- const t = new TransportThread(origin.origin, worker, port1);
1726
+ const t = new TransportThread(origin.origin, worker, port1, acks);
1665
1727
  t.pace = knobs.pace;
1666
1728
  t.onFail = onFail;
1667
1729
  worker.on("error", () => { t.poll(); t.close("error"); });
@@ -1683,6 +1745,18 @@ function knownPrefix(url) {
1683
1745
  }
1684
1746
  return undefined;
1685
1747
  }
1748
+ let lastTargetUrl;
1749
+ let lastTargetRead;
1750
+ let targetReads = 0;
1751
+ function lastTarget(url) {
1752
+ if (url === lastTargetUrl)
1753
+ return lastTargetRead;
1754
+ targetReads++;
1755
+ const target = targetOf(url, knownPrefix(url), remember);
1756
+ lastTargetUrl = url;
1757
+ lastTargetRead = target;
1758
+ return target;
1759
+ }
1686
1760
  function remember(p) {
1687
1761
  for (let i = 0; i < prefixes.length; i++)
1688
1762
  if (prefixes[i].text === p.text)
@@ -1718,7 +1792,7 @@ class Http2Transport {
1718
1792
  const deadline = performance.now() + init.timeoutMs;
1719
1793
  let target;
1720
1794
  try {
1721
- target = targetOf(url, knownPrefix(url), remember);
1795
+ target = lastTarget(url);
1722
1796
  }
1723
1797
  catch {
1724
1798
  return this.viaFetch(url, init, deadline);
@@ -1779,6 +1853,15 @@ class Http2Transport {
1779
1853
  }
1780
1854
  }
1781
1855
  exports.Http2Transport = Http2Transport;
1856
+ let sharedFallback;
1857
+ let shared;
1858
+ function sharedTransport(fallback) {
1859
+ if (shared === undefined || sharedFallback !== fallback) {
1860
+ shared = new Http2Transport(fallback);
1861
+ sharedFallback = fallback;
1862
+ }
1863
+ return shared;
1864
+ }
1782
1865
  function warmTransport() {
1783
1866
  const res = toResponse(200, { "content-type": "application/json" }, Buffer.from("{}"), "POST");
1784
1867
  res.header("server-timing");
@@ -1791,6 +1874,9 @@ function warmTransport() {
1791
1874
  exchange({ end() { } }, undefined, { origin: none, host: none, path: none }, undefined, 0).catch(() => { });
1792
1875
  }
1793
1876
  exports.__http2Internals = {
1877
+ targetReads() {
1878
+ return targetReads;
1879
+ },
1794
1880
  pools() {
1795
1881
  return [...pools.values()].map((p) => ({
1796
1882
  origin: p.origin.origin,
@@ -1831,11 +1917,17 @@ exports.__http2Internals = {
1831
1917
  if (t === undefined)
1832
1918
  return undefined;
1833
1919
  t.poll();
1834
- return { state: t.state, sessions: t.sessions, limit: t.limit, answered: t.answered, everReady: t.everReady, closedReason: t.closedReason };
1920
+ 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 };
1835
1921
  },
1836
1922
  threadKnobs(env) {
1837
1923
  return threadKnobs(env);
1838
1924
  },
1925
+ wedgeThreadForTest() {
1926
+ try {
1927
+ transportThread?.port.postMessage({ t: "wedge-for-test" });
1928
+ }
1929
+ catch { }
1930
+ },
1839
1931
  crashThreadForTest() {
1840
1932
  try {
1841
1933
  transportThread?.port.postMessage({ t: "crash-for-test" });
@@ -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;
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.__keyedInternals = void 0;
4
+ exports.isKeyedCreate = isKeyedCreate;
5
+ const keyed_gen_js_1 = require("./keyed.gen.js");
6
+ const KEYED_PATTERNS = keyed_gen_js_1.KEYED_CREATES.map(([m, p]) => [m, p.split("/").filter(Boolean)]);
7
+ const KEYED_METHODS = new Set(keyed_gen_js_1.KEYED_CREATES.map(([m]) => m));
8
+ let keyedLastMethod = "";
9
+ let keyedLastPath = "";
10
+ let keyedLastAnswer = false;
11
+ let keyedReads = 0;
12
+ function isKeyedCreate(method, path) {
13
+ if (!KEYED_METHODS.has(method))
14
+ return false;
15
+ if (path === keyedLastPath && method === keyedLastMethod)
16
+ return keyedLastAnswer;
17
+ const answer = keyedCreateOf(method, path);
18
+ keyedLastMethod = method;
19
+ keyedLastPath = path;
20
+ keyedLastAnswer = answer;
21
+ return answer;
22
+ }
23
+ function keyedCreateOf(method, path) {
24
+ keyedReads++;
25
+ let end = path.indexOf("?");
26
+ if (end < 0)
27
+ end = path.length;
28
+ for (let p = 0; p < KEYED_PATTERNS.length; p++) {
29
+ const entry = KEYED_PATTERNS[p];
30
+ if (entry[0] === method && segmentsMatch(path, end, entry[1]))
31
+ return true;
32
+ }
33
+ return false;
34
+ }
35
+ function segmentsMatch(path, end, pat) {
36
+ let i = 0;
37
+ let k = 0;
38
+ for (;;) {
39
+ while (i < end && path.charCodeAt(i) === 0x2f)
40
+ i++;
41
+ if (i >= end)
42
+ return k === pat.length;
43
+ if (k >= pat.length)
44
+ return false;
45
+ let j = i;
46
+ while (j < end && path.charCodeAt(j) !== 0x2f)
47
+ j++;
48
+ const s = pat[k];
49
+ if (s.charCodeAt(0) !== 0x7b && (j - i !== s.length || !path.startsWith(s, i)))
50
+ return false;
51
+ k++;
52
+ i = j;
53
+ }
54
+ }
55
+ exports.__keyedInternals = {
56
+ reads() {
57
+ return keyedReads;
58
+ },
59
+ };
package/dist/cjs/proxy.js CHANGED
@@ -65,12 +65,23 @@ function noProxy(host, port, env = process.env) {
65
65
  }
66
66
  return false;
67
67
  }
68
+ let proxyLastInputs;
69
+ let proxyLast;
68
70
  function proxyFor(url, env = process.env) {
69
71
  const https = envOf(env, "HTTPS_PROXY");
70
72
  const http = envOf(env, "HTTP_PROXY");
71
73
  const all = envOf(env, "ALL_PROXY");
72
74
  if (https === undefined && http === undefined && all === undefined)
73
75
  return undefined;
76
+ const inputs = `${url}\n${https ?? "\0"}\n${http ?? "\0"}\n${all ?? "\0"}\n${envOf(env, "NO_PROXY") ?? "\0"}`;
77
+ if (inputs === proxyLastInputs)
78
+ return proxyLast;
79
+ const found = proxyForOf(url, env, https, http, all);
80
+ proxyLastInputs = inputs;
81
+ proxyLast = found;
82
+ return found;
83
+ }
84
+ function proxyForOf(url, env, https, http, all) {
74
85
  let u;
75
86
  try {
76
87
  u = new URL(url);
@@ -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`). */