@sproutboat/runtime 0.42.1 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +1 -1
  3. package/src/native-fetch-prelude.js +186 -24
  4. package/src/transport-embedded.js +35 -6
  5. package/test/native-base64.ts +12 -26
  6. package/test/native-bodies.ts +19 -26
  7. package/test/native-byte-text.ts +3 -19
  8. package/test/native-compile.ts +163 -0
  9. package/test/native-cpu-requests.ts +3 -20
  10. package/test/native-cpu-stamp.ts +3 -20
  11. package/test/native-crypto.ts +3 -22
  12. package/test/native-domexception.ts +3 -21
  13. package/test/native-errors.ts +12 -24
  14. package/test/native-events.ts +21 -25
  15. package/test/native-fetch-request.ts +11 -20
  16. package/test/native-getrandomvalues.ts +3 -21
  17. package/test/native-headers.ts +3 -21
  18. package/test/native-http.ts +10 -20
  19. package/test/native-iterators.ts +24 -27
  20. package/test/native-json-strings.ts +3 -21
  21. package/test/native-json.ts +15 -25
  22. package/test/native-jwt.ts +3 -22
  23. package/test/native-model-resolver.ts +5 -24
  24. package/test/native-raw-body.ts +7 -53
  25. package/test/native-request-log.ts +197 -0
  26. package/test/native-request-streaming.ts +3 -19
  27. package/test/native-request.ts +24 -26
  28. package/test/native-response-defaults.ts +4 -22
  29. package/test/native-service-bindings.ts +53 -27
  30. package/test/native-sockets-broker.ts +65 -20
  31. package/test/native-sockets-starttls.ts +404 -0
  32. package/test/native-streaming.ts +4 -22
  33. package/test/native-streams.ts +22 -25
  34. package/test/native-subtle.ts +3 -21
  35. package/test/native-textdecoder-encodings.ts +3 -21
  36. package/test/native-timers.ts +4 -22
  37. package/test/native-unhandled-rejection.ts +3 -20
  38. package/test/native-url.ts +15 -25
  39. package/test/native-utf8.ts +15 -21
  40. package/test/native-waituntil.ts +7 -23
  41. package/test/native-web-apis.ts +3 -22
  42. package/test/native-websockets.ts +3 -19
@@ -1,8 +1,8 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
- import { join, resolve } from "node:path";
5
- import { ensurePorffor } from "../../toolchain/src/acquire";
4
+ import { join } from "node:path";
5
+ import { compileNative } from "./native-compile";
6
6
  import { preludePath, TRANSPORT_MARKER, transportPath, wrapNativeFetchHandler } from "../src/wrap";
7
7
 
8
8
  // Streamed request bodies, from compatibility date 2026-10-27: the handler runs once the
@@ -79,9 +79,6 @@ const expectedSum = (total: number) => {
79
79
  const workdir = await mkdtemp(join(tmpdir(), "sb-native-request-streaming-"));
80
80
  let server: ReturnType<typeof Bun.spawn> | undefined;
81
81
  try {
82
- const porffor = await ensurePorffor();
83
- const node = Bun.which("node");
84
- if (!node) throw new Error("native request-streaming test needs node on PATH");
85
82
  const [core, broker] = await Promise.all([readFile(preludePath, "utf8"), readFile(transportPath("broker"), "utf8")]);
86
83
  assert(core.includes(TRANSPORT_MARKER), "runtime prelude is missing its transport marker");
87
84
  const generated = join(workdir, "stream.generated.js");
@@ -95,20 +92,7 @@ try {
95
92
  "2026-10-27",
96
93
  );
97
94
  await writeFile(generated, source);
98
- const compile = Bun.spawn(
99
- [node, resolve(porffor, "runtime/index.js"), "native", generated, "-o", binary, "-s", "-O0"],
100
- {
101
- cwd: workdir,
102
- stdout: "pipe",
103
- stderr: "pipe",
104
- },
105
- );
106
- const [exitCode, stdout, stderr] = await Promise.all([
107
- compile.exited,
108
- new Response(compile.stdout).text(),
109
- new Response(compile.stderr).text(),
110
- ]);
111
- assert.equal(exitCode, 0, `Porffor native compile failed:\n${stderr}\n${stdout}`);
95
+ await compileNative({ generated, binary, dir: workdir });
112
96
 
113
97
  const port = freePort();
114
98
  const linux = process.platform === "linux";
@@ -1,8 +1,8 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
- import { join, resolve } from "node:path";
5
- import { ensurePorffor } from "../../toolchain/src/acquire";
4
+ import { join } from "node:path";
5
+ import { compileNative } from "./native-compile";
6
6
  import { preludePath, TRANSPORT_MARKER, transportPath, wrapNativeFetchHandler } from "../src/wrap";
7
7
 
8
8
  // new Request(request, init) and fetch-style Request copies, compared with Bun: the copy
@@ -31,7 +31,15 @@ ${CASES.map((c) => ` () => (${c}),`).join("\n")}
31
31
  }
32
32
  return new Response(JSON.stringify(out));
33
33
  }};`;
34
- const want = await Promise.all(CASES.map(async (c) => { try { return String(await new Function("return " + c)()); } catch (e) { return "THROWS " + (e instanceof Error ? e.message : String(e)); } }));
34
+ const want = await Promise.all(
35
+ CASES.map(async (c) => {
36
+ try {
37
+ return String(await new Function("return " + c)());
38
+ } catch (e) {
39
+ return "THROWS " + (e instanceof Error ? e.message : String(e));
40
+ }
41
+ }),
42
+ );
35
43
 
36
44
  function freePort(): number {
37
45
  const listener = Bun.listen({ hostname: "127.0.0.1", port: 0, socket: { data() {} } });
@@ -43,14 +51,7 @@ function freePort(): number {
43
51
  const workdir = await mkdtemp(join(tmpdir(), "sb-native-request-"));
44
52
  let server: ReturnType<typeof Bun.spawn> | undefined;
45
53
  try {
46
- const porffor = await ensurePorffor();
47
- const node = Bun.which("node");
48
- if (!node) throw new Error("native streams test needs node on PATH");
49
-
50
- const [core, broker] = await Promise.all([
51
- readFile(preludePath, "utf8"),
52
- readFile(transportPath("broker"), "utf8"),
53
- ]);
54
+ const [core, broker] = await Promise.all([readFile(preludePath, "utf8"), readFile(transportPath("broker"), "utf8")]);
54
55
  assert(core.includes(TRANSPORT_MARKER), "runtime prelude is missing its transport marker");
55
56
  const prelude = core.replace(TRANSPORT_MARKER, broker);
56
57
  const generated = join(workdir, "request.generated.js");
@@ -58,18 +59,7 @@ try {
58
59
  await writeFile(generated, wrapNativeFetchHandler(handler, prelude));
59
60
  await rm(binary, { force: true });
60
61
 
61
- const compile = Bun.spawn([node, resolve(porffor, "runtime/index.js"), "native", generated, "-o", binary, "-s", "-O0"], {
62
- cwd: workdir,
63
- env: process.env,
64
- stdout: "pipe",
65
- stderr: "pipe",
66
- });
67
- const [exitCode, stdout, stderr] = await Promise.all([
68
- compile.exited,
69
- new Response(compile.stdout).text(),
70
- new Response(compile.stderr).text(),
71
- ]);
72
- assert.equal(exitCode, 0, `Porffor native compile failed:\n${stderr}\n${stdout}`);
62
+ await compileNative({ generated, binary, dir: workdir });
73
63
 
74
64
  const port = freePort();
75
65
  server = Bun.spawn([binary], {
@@ -92,11 +82,19 @@ try {
92
82
  // SAFETY: the handler above is the only producer of this body: a JSON array with one
93
83
  // string per case; the comparison below reports any entry that differs.
94
84
  const got = JSON.parse(await (await fetch(`http://127.0.0.1:${port}/`)).text()) as string[];
95
- const diffs = CASES.flatMap((c, i) => (got[i] === want[i] ? [] : [`${c}\n want ${JSON.stringify(want[i])}\n got ${JSON.stringify(got[i])}`]));
96
- assert.deepEqual(diffs, [], `Request copies differ from Bun in ${diffs.length} of ${CASES.length} cases:\n${diffs.join("\n")}`);
85
+ const diffs = CASES.flatMap((c, i) =>
86
+ got[i] === want[i] ? [] : [`${c}\n want ${JSON.stringify(want[i])}\n got ${JSON.stringify(got[i])}`],
87
+ );
88
+ assert.deepEqual(
89
+ diffs,
90
+ [],
91
+ `Request copies differ from Bun in ${diffs.length} of ${CASES.length} cases:\n${diffs.join("\n")}`,
92
+ );
97
93
  const posted = await (await fetch(`http://127.0.0.1:${port}/`, { method: "POST", body: "posted é" })).text();
98
94
  assert.equal(posted, "false:string:posted é", "before 2026-10-21 the incoming body stays the raw value");
99
- console.log(`Native Request copies match Bun in all ${CASES.length} cases; an app before 2026-10-21 keeps its raw body`);
95
+ console.log(
96
+ `Native Request copies match Bun in all ${CASES.length} cases; an app before 2026-10-21 keeps its raw body`,
97
+ );
100
98
  } finally {
101
99
  if (server) {
102
100
  server.kill(9);
@@ -1,8 +1,8 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
- import { join, resolve } from "node:path";
5
- import { ensurePorffor } from "../../toolchain/src/acquire";
4
+ import { join } from "node:path";
5
+ import { compileNative } from "./native-compile";
6
6
  import { preludePath, TRANSPORT_MARKER, transportPath, wrapNativeFetchHandler } from "../src/wrap";
7
7
 
8
8
  // What workerd answers for the same handler (checked 2026-10-19, workerd 2026-10-16).
@@ -37,14 +37,7 @@ function freePort(): number {
37
37
  const workdir = await mkdtemp(join(tmpdir(), "sb-native-response-defaults-"));
38
38
  let server: ReturnType<typeof Bun.spawn> | undefined;
39
39
  try {
40
- const porffor = await ensurePorffor();
41
- const node = Bun.which("node");
42
- if (!node) throw new Error("native streams test needs node on PATH");
43
-
44
- const [core, broker] = await Promise.all([
45
- readFile(preludePath, "utf8"),
46
- readFile(transportPath("broker"), "utf8"),
47
- ]);
40
+ const [core, broker] = await Promise.all([readFile(preludePath, "utf8"), readFile(transportPath("broker"), "utf8")]);
48
41
  assert(core.includes(TRANSPORT_MARKER), "runtime prelude is missing its transport marker");
49
42
  const prelude = core.replace(TRANSPORT_MARKER, broker);
50
43
  const generated = join(workdir, "response.generated.js");
@@ -52,18 +45,7 @@ try {
52
45
  await writeFile(generated, wrapNativeFetchHandler(handler, prelude));
53
46
  await rm(binary, { force: true });
54
47
 
55
- const compile = Bun.spawn([node, resolve(porffor, "runtime/index.js"), "native", generated, "-o", binary, "-s", "-O0"], {
56
- cwd: workdir,
57
- env: process.env,
58
- stdout: "pipe",
59
- stderr: "pipe",
60
- });
61
- const [exitCode, stdout, stderr] = await Promise.all([
62
- compile.exited,
63
- new Response(compile.stdout).text(),
64
- new Response(compile.stderr).text(),
65
- ]);
66
- assert.equal(exitCode, 0, `Porffor native compile failed:\n${stderr}\n${stdout}`);
48
+ await compileNative({ generated, binary, dir: workdir });
67
49
 
68
50
  const port = freePort();
69
51
  server = Bun.spawn([binary], {
@@ -1,8 +1,8 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
- import { join, resolve } from "node:path";
5
- import { ensurePorffor } from "../../toolchain/src/acquire";
4
+ import { join } from "node:path";
5
+ import { compileNative } from "./native-compile";
6
6
  import { createBroker, listen } from "../../wire/src/broker";
7
7
  import { EMPTY_BINDINGS, preludePath, TRANSPORT_MARKER, transportPath, wrapNativeFetchHandler } from "../src/wrap";
8
8
 
@@ -182,7 +182,11 @@ const edge = Bun.serve({
182
182
  });
183
183
  }
184
184
  if (url.pathname === "/typed") {
185
- return Response.json({ method: request.method, type: request.headers.get("content-type"), body: await request.text() });
185
+ return Response.json({
186
+ method: request.method,
187
+ type: request.headers.get("content-type"),
188
+ body: await request.text(),
189
+ });
186
190
  }
187
191
  if (url.pathname === "/redirect") {
188
192
  const status = Number(url.searchParams.get("status"));
@@ -246,9 +250,6 @@ const get = async (path: string) => {
246
250
  return { status: res.status, text: await res.text(), ms: performance.now() - t0 };
247
251
  };
248
252
  try {
249
- const porffor = await ensurePorffor({ cacheRoot: workdir });
250
- const node = Bun.which("node");
251
- if (!node) throw new Error("native service-binding test needs node on PATH");
252
253
  const [core, transport] = await Promise.all([
253
254
  readFile(preludePath, "utf8"),
254
255
  readFile(transportPath("broker"), "utf8"),
@@ -256,20 +257,12 @@ try {
256
257
  assert(core.includes(TRANSPORT_MARKER), "runtime prelude is missing its transport marker");
257
258
  const generated = join(workdir, "services.generated.js");
258
259
  const binary = join(workdir, "services-native");
259
- await writeFile(generated, wrapNativeFetchHandler(handler, core.replace(TRANSPORT_MARKER, transport), {}, bindings, undefined, "2026-10-22"));
260
+ await writeFile(
261
+ generated,
262
+ wrapNativeFetchHandler(handler, core.replace(TRANSPORT_MARKER, transport), {}, bindings, undefined, "2026-10-22"),
263
+ );
260
264
 
261
- const compile = Bun.spawn([node, resolve(porffor, "runtime/index.js"), "native", generated, "-o", binary, "-s", "-O0"], {
262
- cwd: workdir,
263
- env: process.env,
264
- stdout: "pipe",
265
- stderr: "pipe",
266
- });
267
- const [exitCode, stdout, stderr] = await Promise.all([
268
- compile.exited,
269
- new Response(compile.stdout).text(),
270
- new Response(compile.stderr).text(),
271
- ]);
272
- assert.equal(exitCode, 0, `Porffor native compile failed:\n${stderr}\n${stdout}`);
265
+ await compileNative({ generated, binary, dir: workdir, cacheRoot: workdir });
273
266
 
274
267
  server = Bun.spawn([binary], {
275
268
  cwd: workdir,
@@ -329,7 +322,10 @@ try {
329
322
  const t0 = performance.now();
330
323
  const all = await Promise.all(Array.from({ length: 8 }, () => get("/slow?ms=500")));
331
324
  const total = performance.now() - t0;
332
- assert(all.every((r) => r.text === "slow done"), JSON.stringify(all));
325
+ assert(
326
+ all.every((r) => r.text === "slow done"),
327
+ JSON.stringify(all),
328
+ );
333
329
  assert(total < 1500, `8 concurrent 500 ms calls took ${Math.round(total)} ms`);
334
330
 
335
331
  // Cancellation: an aborted signal rejects at once, and the abort reaches the upstream
@@ -337,7 +333,11 @@ try {
337
333
  const abandonedBefore = slowAbandoned;
338
334
  const aborts = await get("/abort");
339
335
  assert.equal(aborts.status, 200, aborts.text);
340
- assert.deepEqual(JSON.parse(aborts.text), { aborted: "AbortError fast", timedOut: "TimeoutError fast", already: "AbortError fast" });
336
+ assert.deepEqual(JSON.parse(aborts.text), {
337
+ aborted: "AbortError fast",
338
+ timedOut: "TimeoutError fast",
339
+ already: "AbortError fast",
340
+ });
341
341
  await Bun.sleep(300);
342
342
  assert.equal(slowAbandoned - abandonedBefore, 2, "both in-flight calls were stopped upstream");
343
343
 
@@ -375,7 +375,11 @@ try {
375
375
  context: { env: true, waitUntil: true },
376
376
  });
377
377
  // A client cannot call a method: without the edge's vouching, x-sb-rpc is just a header.
378
- const forgedRpc = await fetch(`http://127.0.0.1:${port}/ping`, { method: "POST", headers: { "x-sb-rpc": "fail" }, body: "[\"x\"]" });
378
+ const forgedRpc = await fetch(`http://127.0.0.1:${port}/ping`, {
379
+ method: "POST",
380
+ headers: { "x-sb-rpc": "fail" },
381
+ body: '["x"]',
382
+ });
379
383
  assert.equal(await forgedRpc.text(), "pong");
380
384
 
381
385
  // A deployment calling itself, 30 levels deep: every level waits on the next at once.
@@ -394,19 +398,41 @@ export default {
394
398
  const legacyPort = freePort();
395
399
  const legacySource = join(workdir, "legacy.generated.js");
396
400
  const legacyBinary = join(workdir, "legacy-native");
397
- await writeFile(legacySource, wrapNativeFetchHandler(legacyHandler, core.replace(TRANSPORT_MARKER, transport), {}, bindings, undefined, "2026-10-21"));
398
- const legacyCompile = Bun.spawnSync([node, resolve(porffor, "runtime/index.js"), "native", legacySource, "-o", legacyBinary, "-s", "-O0"], { cwd: workdir });
399
- assert.equal(legacyCompile.exitCode, 0, `legacy compile failed:\n${legacyCompile.stderr}`);
401
+ await writeFile(
402
+ legacySource,
403
+ wrapNativeFetchHandler(
404
+ legacyHandler,
405
+ core.replace(TRANSPORT_MARKER, transport),
406
+ {},
407
+ bindings,
408
+ undefined,
409
+ "2026-10-21",
410
+ ),
411
+ );
412
+ await compileNative({
413
+ generated: legacySource,
414
+ binary: legacyBinary,
415
+ dir: workdir,
416
+ cacheRoot: workdir,
417
+ failure: "legacy compile failed",
418
+ });
400
419
  legacy = Bun.spawn([legacyBinary], {
401
420
  cwd: workdir,
402
- env: { ...process.env, PORT: String(legacyPort), SB_BROKER_PORT: String(brokerServer.port), SB_BROKER_TOKEN: "native-services" },
421
+ env: {
422
+ ...process.env,
423
+ PORT: String(legacyPort),
424
+ SB_BROKER_PORT: String(brokerServer.port),
425
+ SB_BROKER_TOKEN: "native-services",
426
+ },
403
427
  stdout: "ignore",
404
428
  stderr: "inherit",
405
429
  });
406
430
  let legacyText = "";
407
431
  for (let attempt = 0; attempt < 50 && !legacyText; attempt++) {
408
432
  try {
409
- legacyText = await (await fetch(`http://127.0.0.1:${legacyPort}/`, { signal: AbortSignal.timeout(20_000) })).text();
433
+ legacyText = await (
434
+ await fetch(`http://127.0.0.1:${legacyPort}/`, { signal: AbortSignal.timeout(20_000) })
435
+ ).text();
410
436
  } catch {
411
437
  await Bun.sleep(100);
412
438
  }
@@ -2,9 +2,10 @@ import assert from "node:assert/strict";
2
2
  import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
3
  import net from "node:net";
4
4
  import { tmpdir } from "node:os";
5
- import { join, resolve } from "node:path";
5
+ import { join } from "node:path";
6
+ import { Duplex } from "node:stream";
6
7
  import tls from "node:tls";
7
- import { ensurePorffor } from "../../toolchain/src/acquire";
8
+ import { compileNative } from "./native-compile";
8
9
  import { createBroker, listen } from "../../wire/src/broker";
9
10
  import { EMPTY_BINDINGS, preludePath, TRANSPORT_MARKER, transportPath, wrapNativeFetchHandler } from "../src/wrap";
10
11
 
@@ -82,6 +83,31 @@ export default {
82
83
  await secure.close();
83
84
  return Response.json({ greeting, go, echoed, upgraded: s.upgraded });
84
85
  }
86
+ if (url.pathname === "/starttls-race") {
87
+ const s = connect("127.0.0.1:" + port, { secureTransport: "starttls" });
88
+ const w = s.writable.getWriter();
89
+ const r = s.readable.getReader();
90
+ await withTimeout(readN(r, 11), 5000, "greeting");
91
+ await w.write(enc.encode("STARTTLS\\r\\n"));
92
+ await withTimeout(readN(r, 4), 5000, "go");
93
+ r.releaseLock();
94
+ w.releaseLock();
95
+ const secure = s.startTls({ expectedServerHostname: "localhost" });
96
+ const w2 = secure.writable.getWriter();
97
+ const r2 = secure.readable.getReader();
98
+ w2.write(enc.encode("secret-1"));
99
+ w2.write(enc.encode("secret-2"));
100
+ w2.write(enc.encode("secret-3"));
101
+ await withTimeout(secure.opened, 5000, "tls");
102
+ let echoed = "";
103
+ while (!echoed.includes("secret-3")) {
104
+ const chunk = await withTimeout(r2.read(), 5000, "tls echo");
105
+ if (chunk.done) break;
106
+ echoed += dec.decode(chunk.value);
107
+ }
108
+ await secure.close();
109
+ return Response.json({ echoed });
110
+ }
85
111
  if (url.pathname === "/tls") {
86
112
  const host = q.get("host") || "localhost";
87
113
  const s = connect({ hostname: host, port }, { secureTransport: "on" });
@@ -271,6 +297,34 @@ const starttlsPort = listenOn(
271
297
  });
272
298
  }),
273
299
  );
300
+ // Every raw byte the recording origin receives after it sent "go", TLS records included.
301
+ const rawAfterGo: Buffer[] = [];
302
+ const starttlsRecordingPort = listenOn(
303
+ net.createServer((s) => {
304
+ // a TLSSocket over a net.Socket reads the handle directly, so the TLS side runs over a tap
305
+ // that replays the bytes this listener saw
306
+ const tap = new Duplex({
307
+ read() {},
308
+ write(chunk, _enc, done) {
309
+ s.write(chunk, done);
310
+ },
311
+ });
312
+ let afterGo = false;
313
+ s.prependListener("data", (c: Buffer) => {
314
+ if (!afterGo) return;
315
+ rawAfterGo.push(Buffer.from(c));
316
+ tap.push(c);
317
+ });
318
+ s.write("220 hello\r\n");
319
+ s.once("data", () => {
320
+ afterGo = true;
321
+ s.write("go\r\n");
322
+ const secure = new tls.TLSSocket(tap, { isServer: true, key, cert });
323
+ secure.on("data", (c) => secure.write(Buffer.concat([Buffer.from("tls:"), c])));
324
+ secure.on("error", () => {});
325
+ });
326
+ }),
327
+ );
274
328
  const tlsPort = listenOn(
275
329
  tls.createServer({ key, cert }, (s) => {
276
330
  s.on("data", (c) => s.write(c));
@@ -310,9 +364,6 @@ const get = async (path: string, base = port) => {
310
364
  };
311
365
 
312
366
  try {
313
- const porffor = await ensurePorffor({ cacheRoot: workdir });
314
- const node = Bun.which("node");
315
- if (!node) throw new Error("native sockets test needs node on PATH");
316
367
  const [core, transport] = await Promise.all([
317
368
  readFile(preludePath, "utf8"),
318
369
  readFile(transportPath("broker"), "utf8"),
@@ -331,21 +382,7 @@ try {
331
382
  "2026-11-17",
332
383
  ),
333
384
  );
334
- const compile = Bun.spawn(
335
- [node, resolve(porffor, "runtime/index.js"), "native", generated, "-o", binary, "-s", "-O0"],
336
- {
337
- cwd: workdir,
338
- env: process.env,
339
- stdout: "pipe",
340
- stderr: "pipe",
341
- },
342
- );
343
- const [exitCode, stdout, stderr] = await Promise.all([
344
- compile.exited,
345
- new Response(compile.stdout).text(),
346
- new Response(compile.stderr).text(),
347
- ]);
348
- assert.equal(exitCode, 0, `Porffor native compile failed:\n${stderr}\n${stdout}`);
385
+ await compileNative({ generated, binary, dir: workdir, cacheRoot: workdir });
349
386
 
350
387
  const start = async (listening: number, brokerPort: number) => {
351
388
  const child = Bun.spawn([binary], {
@@ -386,6 +423,14 @@ try {
386
423
  });
387
424
  console.log("startTls upgrades in the broker, verified against the expected host name");
388
425
 
426
+ const race = await get(`/starttls-race?port=${starttlsRecordingPort}`);
427
+ const echoedRace = String(race.echoed);
428
+ for (const part of ["secret-1", "secret-2", "secret-3"]) assert.ok(echoedRace.includes(part), `echoed ${part}`);
429
+ const rawBytes = Buffer.concat(rawAfterGo);
430
+ assert.equal(rawBytes[0], 0x16, "the first bytes after the upgrade are a TLS handshake record");
431
+ assert.equal(rawBytes.includes("secret"), false, "no application bytes reached the origin in plaintext");
432
+ console.log("startTls: writes issued before the handshake finished only reach the origin inside TLS");
433
+
389
434
  assert.deepEqual(await get(`/tls?port=${tlsPort}`), { got: "secret" });
390
435
  const wrongName = await get(`/tls?port=${tlsPort}&host=127.0.0.1`);
391
436
  assert.match(String(wrongName.error), /proxy request failed, cannot connect/);