@scriptc/compiler 0.0.12 → 0.0.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.
Files changed (46) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +141 -15
  2. package/dist/backend/cc.d.ts +8 -0
  3. package/dist/backend/cc.js +12 -0
  4. package/dist/backend/cc.js.map +1 -1
  5. package/dist/backend/emission/emit-exprs.js +138 -12
  6. package/dist/backend/emission/emit-exprs.js.map +1 -1
  7. package/dist/backend/llvm/emitter.js +43 -6
  8. package/dist/backend/llvm/emitter.js.map +1 -1
  9. package/dist/frontend/lowering/crypto-tables.d.ts +7 -0
  10. package/dist/frontend/lowering/crypto-tables.js +139 -0
  11. package/dist/frontend/lowering/crypto-tables.js.map +1 -0
  12. package/dist/frontend/lowering/lower-builtins.d.ts +38 -20
  13. package/dist/frontend/lowering/lower-builtins.js +111 -39
  14. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  15. package/dist/frontend/lowering/lower-calls.js +8 -0
  16. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  17. package/dist/frontend/lowering/lower-classes.js +10 -2
  18. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  19. package/dist/frontend/lowering/lower-exprs.js +69 -7
  20. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  21. package/dist/frontend/lowering/lower-modules.js +1 -1
  22. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  23. package/dist/frontend/lowering/lower-server.d.ts +21 -0
  24. package/dist/frontend/lowering/lower-server.js +267 -12
  25. package/dist/frontend/lowering/lower-server.js.map +1 -1
  26. package/dist/frontend/lowering/lower-stmts.js +4 -4
  27. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  28. package/dist/frontend/lowering/lower-stream.js +1 -1
  29. package/dist/frontend/lowering/lower-stream.js.map +1 -1
  30. package/dist/frontend/lowering/lowerer.d.ts +21 -3
  31. package/dist/frontend/lowering/lowerer.js +48 -8
  32. package/dist/frontend/lowering/lowerer.js.map +1 -1
  33. package/dist/frontend/lowering/surfaces.d.ts +6 -0
  34. package/dist/frontend/lowering/surfaces.js +76 -0
  35. package/dist/frontend/lowering/surfaces.js.map +1 -1
  36. package/dist/frontend/types.js +12 -0
  37. package/dist/frontend/types.js.map +1 -1
  38. package/dist/index.js +6 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/ir/nodes.d.ts +99 -9
  41. package/dist/ir/nodes.js +52 -1
  42. package/dist/ir/nodes.js.map +1 -1
  43. package/dist/ir/validate.js +52 -4
  44. package/dist/ir/validate.js.map +1 -1
  45. package/package.json +2 -2
  46. package/surface-manifest.json +286 -1
@@ -12,7 +12,7 @@ import * as ts from "../ts7/adapter.js";
12
12
  import { ladderFenceExpr, nodeThrowExpr } from "./lowerer.js";
13
13
  import { isJsSourceFile, locOf } from "../program.js";
14
14
  import { arrayOf, BOOL, BYTES_U8, canBoxFuncIntoDyn, canConvertToDyn, DYN, DYN_HANDLE_KINDS, F64, funcOf, HTTP2SESSION_T, HTTP2STREAM_T, HTTPCLIENTREQ_T, HTTPREQ_T, HTTPRES_T, NETSERVER_T, NETSOCKET_T, NULL_T, SECURECTX_T, STRING, UNDEFINED_T, typeKey, VOID } from "../../ir/nodes.js";
15
- import { builtinFenceHintOf, builtinModuleFnOf, fenceOrDropOptionKey, HTTP_CLIENT_DOCUMENTED_OPTIONS, HTTP_SERVER_DOCUMENTED_OPTIONS, HTTP2_SECURE_SERVER_DOCUMENTED_OPTIONS, HTTPS_CLIENT_DOCUMENTED_OPTIONS, sideEffectFreeOptionValue, TLS_SERVER_DOCUMENTED_OPTIONS, } from "./surfaces.js";
15
+ import { AGENT_DOCUMENTED_OPTIONS, builtinFenceHintOf, builtinModuleFnOf, fenceOrDropOptionKey, HTTP_CLIENT_DOCUMENTED_OPTIONS, HTTP_SERVER_DOCUMENTED_OPTIONS, HTTP2_SECURE_SERVER_DOCUMENTED_OPTIONS, HTTPS_CLIENT_DOCUMENTED_OPTIONS, sideEffectFreeOptionValue, TLS_SERVER_DOCUMENTED_OPTIONS, } from "./surfaces.js";
16
16
  import { conditionalSpreadOf } from "./lower-exprs.js";
17
17
  /** The lowered value members of the net module — the surfaces.ts table's
18
18
  * spoke-side twin (the call SHAPES are all special-cased here, so the
@@ -1244,6 +1244,40 @@ function lowerNetSocketMethodCall(L, call, access) {
1244
1244
  const ms = L.lowerExprExpecting(args[0], F64);
1245
1245
  return { kind: "libCall", fn: "net.sockSetTimeout", args: [receiver, ms], type: VOID, loc };
1246
1246
  }
1247
+ if (name === "pause" || name === "resume") {
1248
+ // Flow control (the struct's flag comments in scr_net.c): pause holds
1249
+ // reads off — kernel/TCP backpressure is the buffer; resume flows
1250
+ // (and discards sans listeners, so 'end' is reachable). Both answer
1251
+ // the socket, Node's chaining.
1252
+ if (args.length !== 0) {
1253
+ L.noLowering(`${name} with ${args.length} arguments`, call, `the form is ${name}()`);
1254
+ }
1255
+ const receiver = handleReceiver(L, access.expression, NETSOCKET_T);
1256
+ const fn = name === "pause" ? "net.sockPause" : "net.sockResume";
1257
+ return { kind: "libCall", fn, args: [receiver], type: NETSOCKET_T, loc };
1258
+ }
1259
+ if (name === "setNoDelay") {
1260
+ // TCP_NODELAY on the live fd; missing/undefined means true (Node).
1261
+ // Answers the socket, Node's chaining.
1262
+ if (args.length > 1) {
1263
+ L.noLowering(`setNoDelay with ${args.length} arguments`, call, "the form is setNoDelay(noDelay?)");
1264
+ }
1265
+ const receiver = handleReceiver(L, access.expression, NETSOCKET_T);
1266
+ const enable = args.length === 1
1267
+ ? L.lowerExprExpecting(args[0], BOOL)
1268
+ : boolLit(true, loc);
1269
+ return { kind: "libCall", fn: "net.sockSetNoDelay", args: [receiver, enable], type: NETSOCKET_T, loc };
1270
+ }
1271
+ if (name === "destroySoon") {
1272
+ // end() now, destroy once the FIN actually flushed — Node's
1273
+ // 'finish'-then-destroy.
1274
+ requireStatementPosition(L, call, "socket.destroySoon()");
1275
+ if (args.length !== 0) {
1276
+ L.noLowering(`destroySoon with ${args.length} arguments`, call, "the form is destroySoon()");
1277
+ }
1278
+ const receiver = handleReceiver(L, access.expression, NETSOCKET_T);
1279
+ return { kind: "libCall", fn: "net.sockDestroySoon", args: [receiver], type: VOID, loc };
1280
+ }
1247
1281
  if (name === "read") {
1248
1282
  // socket.read(n?) — the demux peek. Answers the interned
1249
1283
  // `Buffer | null` union: exactly n buffered bytes, or null (Node's
@@ -1311,6 +1345,12 @@ function lowerNetSocketMethodCall(L, call, access) {
1311
1345
  : "net.sockOnConnect";
1312
1346
  return { kind: "libCall", fn, args: [receiver, cb, once], type: VOID, loc };
1313
1347
  }
1348
+ if (event === "finish") {
1349
+ // Fires once when the FIN goes out — once either way (the event
1350
+ // happens at most once per socket).
1351
+ const { cb } = lowerCallbackArg(L, args[1], "finish listeners", 0, () => false, "use ()", []);
1352
+ return { kind: "libCall", fn: "net.sockOnFinish", args: [receiver, cb], type: VOID, loc };
1353
+ }
1314
1354
  if (event === "secureConnect") {
1315
1355
  // The TLS client's post-handshake event: on a TLS socket the
1316
1356
  // 'connect' list already fires at establishment (scr_net.c's
@@ -1794,6 +1834,17 @@ export function lowerServerProperty(L, expr) {
1794
1834
  const receiver = handleReceiver(L, expr.expression, NETSOCKET_T);
1795
1835
  return { kind: "libCall", fn: "net.sockWritable", args: [receiver], type: BOOL, loc };
1796
1836
  }
1837
+ if (recvKind === "netSocket" && L.isStdlibMember(expr) && expr.name.text === "bytesWritten") {
1838
+ // Every byte the write paths accepted (buffered included — Node
1839
+ // counts those too; plaintext on TLS sockets).
1840
+ const receiver = handleReceiver(L, expr.expression, NETSOCKET_T);
1841
+ return { kind: "libCall", fn: "net.sockBytesWritten", args: [receiver], type: F64, loc };
1842
+ }
1843
+ if (recvKind === "netSocket" && L.isStdlibMember(expr) && expr.name.text === "readable") {
1844
+ // true until the read half is done (peer FIN / destroy).
1845
+ const receiver = handleReceiver(L, expr.expression, NETSOCKET_T);
1846
+ return { kind: "libCall", fn: "net.sockReadable", args: [receiver], type: BOOL, loc };
1847
+ }
1797
1848
  if (recvKind === "netSocket" && L.isStdlibMember(expr) && expr.name.text === "remoteAddress") {
1798
1849
  // `string | undefined` — Node's read-time caching: a value read while
1799
1850
  // connected survives destroy; never-read sockets answer undefined
@@ -2037,6 +2088,113 @@ function lowerHttpCreateServerForms(L, expr, what, loc) {
2037
2088
  * createServer (Node's Server class IS the factory's product); called
2038
2089
  * from lowerNew ahead of the stdlib-constructor fence. Null when the
2039
2090
  * callee is not the http module's Server. */
2091
+ /** `new http.Agent(opts?)` / `new https.Agent(opts?)` (property access or
2092
+ * named import): the Agent lowers to a checked-dynamic HANDLE —
2093
+ * getName/destroy and the sockets/requests/freeSockets counters dispatch
2094
+ * through the dyn handle ops, and the request path threads it (the
2095
+ * requestAgent rows). Options parse at the LITERAL construction site:
2096
+ * keepAlive (a boolean value — TRUE throws the runtime's named pooling
2097
+ * fence: this client dials one connection per request), keepAliveMsecs /
2098
+ * maxSockets / maxFreeSockets / timeout / port as numbers, scheduling
2099
+ * DROPS (no free pool exists, so selection order cannot observe), other
2100
+ * documented keys fence by name, and undocumented keys drop like Node
2101
+ * drops them. Null when the callee isn't the http/https Agent. */
2102
+ export function lowerHttpAgentNew(L, expr) {
2103
+ const callee = expr.expression;
2104
+ const bi = ts.isPropertyAccessExpression(callee) && callee.name.text === "Agent"
2105
+ ? L.builtinMemberOf(callee)
2106
+ : ts.isIdentifier(callee)
2107
+ ? L.builtinImportOf(callee)
2108
+ : null;
2109
+ if (!bi || bi.member !== "Agent" || (bi.module !== "http" && bi.module !== "https"))
2110
+ return null;
2111
+ const loc = locOf(expr);
2112
+ const api = `new ${bi.module}.Agent`;
2113
+ const numLit = (value) => ({ kind: "numLit", value, type: F64, loc });
2114
+ const boolAt = (value) => ({ kind: "boolLit", value, type: BOOL, loc });
2115
+ let keepAlive = boolAt(false);
2116
+ let kaMsecs = numLit(-1);
2117
+ let maxSockets = numLit(-1);
2118
+ let maxFree = numLit(-1);
2119
+ let timeout = numLit(-1);
2120
+ let port = numLit(-1);
2121
+ const args = expr.arguments ?? [];
2122
+ if (args.length > 1) {
2123
+ L.noLowering(`${api} with ${args.length} arguments`, expr, "the supported form is new Agent(options?)");
2124
+ }
2125
+ if (args.length === 1) {
2126
+ const optsNode = stripParensAndCasts(args[0]);
2127
+ if (!ts.isObjectLiteralExpression(optsNode)) {
2128
+ L.noLowering(`${api} with a non-literal options value`, args[0], "spell the options as an object literal at the construction site");
2129
+ }
2130
+ for (const prop of optsNode.properties) {
2131
+ if (ts.isSpreadAssignment(prop)) {
2132
+ L.noLowering(`${api} with an options spread`, prop, "write each option inline");
2133
+ }
2134
+ let initializer;
2135
+ if (ts.isPropertyAssignment(prop) &&
2136
+ (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name))) {
2137
+ initializer = prop.initializer;
2138
+ }
2139
+ else if (ts.isShorthandPropertyAssignment(prop)) {
2140
+ initializer = null;
2141
+ }
2142
+ else {
2143
+ L.noLowering(`${api} options with computed keys`, prop, "each option must be a plain `name: value` (or shorthand) entry with a literal key");
2144
+ }
2145
+ const key = prop.name.text;
2146
+ const lowerVal = (want) => {
2147
+ const v = initializer !== null
2148
+ ? L.lowerExpr(initializer)
2149
+ : L.lowerShorthandValue(prop);
2150
+ if (v.type.kind === "dyn") {
2151
+ return { kind: "dynCheck", value: v, type: want === "bool" ? BOOL : F64, loc: locOf(prop) };
2152
+ }
2153
+ if (v.type.kind !== want) {
2154
+ L.noLowering(`a ${api} '${key}' option of '${L.fmt(v.type)}' values`, prop, want === "bool" ? "the option value must be a boolean" : "the option value must be a number");
2155
+ }
2156
+ return v;
2157
+ };
2158
+ switch (key) {
2159
+ case "keepAlive":
2160
+ keepAlive = lowerVal("bool");
2161
+ break;
2162
+ case "keepAliveMsecs":
2163
+ kaMsecs = lowerVal("f64");
2164
+ break;
2165
+ case "maxSockets":
2166
+ maxSockets = lowerVal("f64");
2167
+ break;
2168
+ case "maxFreeSockets":
2169
+ maxFree = lowerVal("f64");
2170
+ break;
2171
+ case "timeout":
2172
+ timeout = lowerVal("f64");
2173
+ break;
2174
+ case "port":
2175
+ // Node merges agent options under portless request options —
2176
+ // the settable defaultPort carries this one.
2177
+ port = lowerVal("f64");
2178
+ break;
2179
+ case "scheduling":
2180
+ // Free-socket selection order: no free pool exists here (no
2181
+ // keep-alive reuse), so the choice cannot observe — dropped.
2182
+ if (initializer !== null)
2183
+ L.lowerExpr(initializer); // evaluate, like Node
2184
+ break;
2185
+ default:
2186
+ fenceOrDropOptionKey(L, prop, key, api, AGENT_DOCUMENTED_OPTIONS, "keepAlive, keepAliveMsecs, maxSockets, maxFreeSockets, timeout, port, and scheduling are the supported options");
2187
+ }
2188
+ }
2189
+ }
2190
+ return {
2191
+ kind: "libCall",
2192
+ fn: "http.agentNew",
2193
+ args: [boolAt(bi.module === "https"), keepAlive, kaMsecs, maxSockets, maxFree, timeout, port],
2194
+ type: DYN,
2195
+ loc,
2196
+ };
2197
+ }
2040
2198
  export function lowerHttpServerNew(L, expr) {
2041
2199
  const callee = expr.expression;
2042
2200
  const isHttpServer = (ts.isPropertyAccessExpression(callee) &&
@@ -2341,9 +2499,45 @@ function lowerTlsModuleCall(L, expr, bi, loc) {
2341
2499
  }
2342
2500
  return { kind: "libCall", fn: "tls.createSecureContext", args: [opts.cert, opts.key], type: SECURECTX_T, loc };
2343
2501
  }
2502
+ // The CA-store introspection pair (scr_tls_ca.c — its own link gate, no
2503
+ // mbedTLS): getCACertificates(type?) answers the per-type cached PEM
2504
+ // string array (an omitted type completes to "default", Node's own
2505
+ // default; unknown strings throw Node's ERR_INVALID_ARG_VALUE at
2506
+ // runtime), and setDefaultCACertificates(certs) replaces the default
2507
+ // set and the client trust anchors. rootCertificates is a VALUE read —
2508
+ // lowerTlsRootCertificates below.
2509
+ if (bi.member === "getCACertificates") {
2510
+ if (args.length > 1) {
2511
+ L.noLowering(`getCACertificates with ${args.length} arguments`, expr);
2512
+ }
2513
+ const typeArg = args.length === 1
2514
+ ? L.lowerExprExpecting(args[0], STRING)
2515
+ : { kind: "strLit", value: "default", type: STRING, loc };
2516
+ return { kind: "libCall", fn: "tlsca.get", args: [typeArg], type: arrayOf(STRING), loc };
2517
+ }
2518
+ if (bi.member === "setDefaultCACertificates") {
2519
+ if (args.length !== 1) {
2520
+ L.noLowering(`setDefaultCACertificates with ${args.length} arguments`, expr);
2521
+ }
2522
+ const certs = L.lowerExpr(args[0]);
2523
+ if (certs.type.kind !== "array" || certs.type.elem.kind !== "string") {
2524
+ L.noLowering(`setDefaultCACertificates of a '${L.fmt(certs.type)}' value`, args[0], "a string[] of PEM certificates is the lowered shape — decode Buffer/typed-array entries to strings first");
2525
+ }
2526
+ return { kind: "libCall", fn: "tlsca.set", args: [certs], type: VOID, loc };
2527
+ }
2344
2528
  L.noLowering(`tls.${bi.member}`, expr, builtinFenceHintOf("tls", bi.member) ??
2345
2529
  "createServer({ cert, key }, handler) and createSecureContext({ cert, key }) are the lowered tls module functions", L.resolveValueSymbol(expr.expression));
2346
2530
  }
2531
+ /** The tls.rootCertificates VALUE read (any import spelling): the same
2532
+ * cached array getCACertificates("bundled") answers — Node's own
2533
+ * equality (test-tls-get-ca-certificates-bundled pins certs ===
2534
+ * rootCertificates). Null for other members (the property chains keep
2535
+ * trying). */
2536
+ export function lowerTlsRootCertificates(L, bi, loc) {
2537
+ if (bi.module !== "tls" || bi.member !== "rootCertificates")
2538
+ return null;
2539
+ return { kind: "libCall", fn: "tlsca.root", args: [], type: arrayOf(STRING), loc };
2540
+ }
2347
2541
  const httpClientFnBindings = new WeakMap();
2348
2542
  export function httpClientFnBindingOf(L, sym) {
2349
2543
  return httpClientFnBindings.get(L)?.get(sym);
@@ -2668,12 +2862,37 @@ function lowerHttp2ModuleCall(L, expr, bi, loc) {
2668
2862
  return { kind: "libCall", fn: "http2.getDefaultSettings", args: [], type: DYN, loc };
2669
2863
  }
2670
2864
  if (bi.member === "createSecureServer") {
2671
- if (args.length !== 1) {
2672
- L.noLowering(`createSecureServer with ${args.length} arguments`, expr, "the supported form is createSecureServer({ allowHTTP1: true, cert, key }) — register the handler with server.on(\"request\", (req, res) => ...)");
2865
+ if (args.length < 1 || args.length > 2) {
2866
+ L.noLowering(`createSecureServer with ${args.length} arguments`, expr, "the supported forms are createSecureServer(options[, onRequestHandler]) with options { allowHTTP1?, cert, key }");
2673
2867
  }
2674
2868
  const optsNode = args[0];
2869
+ // The eager (req, res) handler — Node routes it as the first
2870
+ // 'request' listener; both flavors' compat layers serve it (the
2871
+ // allowHTTP1 server's HTTP/1.1 handles, the h2 server's compat
2872
+ // handles over streams).
2873
+ const handlerNode = args.length === 2 ? args[1] : null;
2675
2874
  if (!ts.isObjectLiteralExpression(optsNode)) {
2676
- L.noLowering("createSecureServer with a non-literal options argument", optsNode, "pass the options as an object literal: { allowHTTP1: true, cert, key }");
2875
+ // The RUNTIME options record (the divergence-66 stance, the
2876
+ // tls/https.createServerDyn precedent): allowHTTP1/cert/key read
2877
+ // at runtime, out-of-bounds TLS members throw the catchable
2878
+ // runtime fence, h2 session-tuning keys drop exactly like the
2879
+ // literal walk ignores them.
2880
+ const v = L.lowerExpr(optsNode);
2881
+ let dynOpts;
2882
+ if (v.type.kind === "dyn") {
2883
+ dynOpts = v;
2884
+ }
2885
+ else if (canConvertToDyn(v.type, (id) => L.shapes.get(id), (id) => L.unions.get(id))) {
2886
+ dynOpts = { kind: "dynFrom", value: v, type: DYN, loc: locOf(optsNode) };
2887
+ }
2888
+ else {
2889
+ L.noLowering("createSecureServer with a non-literal options argument", optsNode, "pass the options as an object literal ({ allowHTTP1: true, cert, key }) or a runtime record of those members");
2890
+ }
2891
+ if (handlerNode === null) {
2892
+ return { kind: "libCall", fn: "http2.createSecureServerDyn", args: [dynOpts], type: NETSERVER_T, loc };
2893
+ }
2894
+ const cb = lowerRequestHandlerArg(L, handlerNode);
2895
+ return { kind: "libCall", fn: "http2.createSecureServerDynCb", args: [dynOpts, cb], type: NETSERVER_T, loc };
2677
2896
  }
2678
2897
  let cert = null;
2679
2898
  let key = null;
@@ -2835,6 +3054,9 @@ function lowerHttp2ModuleCall(L, expr, bi, loc) {
2835
3054
  if (cert === null || key === null) {
2836
3055
  L.noLowering("createSecureServer without both cert and key", optsNode, "the supported options object is { allowHTTP1?, cert, key } (PEM strings or Buffers)");
2837
3056
  }
3057
+ if (sni !== null && handlerNode !== null) {
3058
+ L.noLowering("createSecureServer with both an SNICallback and an eager handler", expr, "register the handler separately: server.on(\"request\", (req, res) => ...)");
3059
+ }
2838
3060
  if (!allowHttp1) {
2839
3061
  // The REAL h2-over-TLS server (scr_http2.c + scr_tls.c): ALPN
2840
3062
  // advertises h2 alone and the h2 session attaches at establishment
@@ -2844,11 +3066,19 @@ function lowerHttp2ModuleCall(L, expr, bi, loc) {
2844
3066
  if (sni !== null) {
2845
3067
  L.noLowering("createSecureServer with an SNICallback and no allowHTTP1", optsNode, "SNICallback lowers on the allowHTTP1 compatibility server — the ALPN=h2 server serves one cert/key pair");
2846
3068
  }
3069
+ if (handlerNode !== null) {
3070
+ const cb = lowerRequestHandlerArg(L, handlerNode);
3071
+ return { kind: "libCall", fn: "http2.createSecureServerH2Req", args: [cert, key, cb], type: NETSERVER_T, loc };
3072
+ }
2847
3073
  return { kind: "libCall", fn: "http2.createSecureServerH2", args: [cert, key], type: NETSERVER_T, loc };
2848
3074
  }
2849
3075
  if (sni !== null) {
2850
3076
  return { kind: "libCall", fn: "http2.createSecureServerSni", args: [cert, key, sni], type: NETSERVER_T, loc };
2851
3077
  }
3078
+ if (handlerNode !== null) {
3079
+ const cb = lowerRequestHandlerArg(L, handlerNode);
3080
+ return { kind: "libCall", fn: "http2.createSecureServerReq", args: [cert, key, cb], type: NETSERVER_T, loc };
3081
+ }
2852
3082
  return { kind: "libCall", fn: "http2.createSecureServer", args: [cert, key], type: NETSERVER_T, loc };
2853
3083
  }
2854
3084
  L.noLowering(`http2.${bi.member}`, expr, builtinFenceHintOf("http2", bi.member) ??
@@ -2918,6 +3148,8 @@ function lowerHttpClientCall(L, expr, member, loc, secure = false) {
2918
3148
  let connCb = null;
2919
3149
  /** agent: false — inject Connection: close into the request head. */
2920
3150
  let agentClose = null;
3151
+ /** agent: <Agent value> — the requestAgent rows thread the handle. */
3152
+ let agentVal = null;
2921
3153
  for (const prop of optsNode.properties) {
2922
3154
  // Shorthand entries ({ port } for { port: port }) are the natural
2923
3155
  // spelling at these call sites — the shorthand's VALUE lowering
@@ -3055,8 +3287,11 @@ function lowerHttpClientCall(L, expr, member, loc, secure = false) {
3055
3287
  // model, so it LOWERS (the header injects below). null/undefined
3056
3288
  // pick the default agent, whose keep-alive request header the
3057
3289
  // compiled client already sends (socket REUSE stays the
3058
- // documented divergence either way). Agent instances configure
3059
- // pooling the runtime does not have — fence.
3290
+ // documented divergence either way). An Agent VALUE (the lowered
3291
+ // `new http.Agent(...)` handle — a checked-dynamic value) threads
3292
+ // through the requestAgent rows: getName-keyed maxSockets
3293
+ // accounting over one-dial-per-request connections; a runtime
3294
+ // non-Agent value is Node's ERR_INVALID_ARG_TYPE.
3060
3295
  const e = initializer !== null ? stripParensAndCasts(initializer) : null;
3061
3296
  if (e !== null && e.kind === ts.SyntaxKind.FalseKeyword) {
3062
3297
  agentClose = prop;
@@ -3067,9 +3302,14 @@ function lowerHttpClientCall(L, expr, member, loc, secure = false) {
3067
3302
  (ts.isIdentifier(e) && e.text === "undefined"))) {
3068
3303
  break; // the default agent: what the agent-free call compiles
3069
3304
  }
3070
- L.noLowering(`a ${member} 'agent' option carrying an Agent value`, prop, "compiled clients dial one connection per request and close it with the response — " +
3071
- "agent: false (lowered: sends Connection: close) and agent: null/undefined (the default) " +
3072
- "compile; pooling Agent instances have no lowering");
3305
+ const v = initializer !== null
3306
+ ? L.lowerExpr(initializer)
3307
+ : L.lowerShorthandValue(prop);
3308
+ if (v.type.kind !== "dyn") {
3309
+ L.noLowering(`a ${member} 'agent' option of '${L.fmt(v.type)}' values`, prop, "the agent is the checked-dynamic Agent handle new http.Agent(...) answers " +
3310
+ "(or false / null / undefined)");
3311
+ }
3312
+ agentVal = v;
3073
3313
  break;
3074
3314
  }
3075
3315
  default:
@@ -3107,12 +3347,21 @@ function lowerHttpClientCall(L, expr, member, loc, secure = false) {
3107
3347
  // exists — a silent half-use; set headers.host instead.
3108
3348
  L.noLowering(`${member} mixing createConnection with hostname/host/port`, optsNode, "the dialer supplies the socket — set the Host header via headers: { host: ... } instead");
3109
3349
  }
3350
+ if (agentVal !== null && connCb !== null) {
3351
+ L.noLowering(`${member} mixing an agent value with createConnection`, optsNode, "both own the dial — pass one or the other");
3352
+ }
3353
+ if (agentVal !== null && binding !== null) {
3354
+ L.noLowering(`${member} through a module-function binding with an agent value`, optsNode, "call http.request/https.request directly when passing an Agent");
3355
+ }
3110
3356
  host ??= strLit("localhost");
3111
3357
  // The binding mode's default port follows the runtime dial: 443 on the
3112
3358
  // TLS arm, 80 on the plain one — exactly each client's own default.
3359
+ // With an AGENT the sentinel -1 says "no port option": the runtime
3360
+ // consults the agent's (settable) defaultPort first, Node's merge.
3113
3361
  port ??= binding !== null
3114
3362
  ? { kind: "ternary", cond: secureExpr(), then: numLit(443), else_: numLit(80), type: F64, loc }
3115
- : numLit(secure === true ? 443 : 80);
3363
+ : agentVal !== null ? numLit(-1)
3364
+ : numLit(secure === true ? 443 : 80);
3116
3365
  path ??= strLit("/");
3117
3366
  method ??= strLit("GET");
3118
3367
  timeout ??= numLit(0);
@@ -3132,14 +3381,20 @@ function lowerHttpClientCall(L, expr, member, loc, secure = false) {
3132
3381
  ca ??= strLit(""); /* none: /etc/ssl/cert.pem stands in for Node's roots */
3133
3382
  base.push(reject, ca);
3134
3383
  }
3384
+ if (agentVal !== null)
3385
+ base.push(agentVal);
3135
3386
  if (binding !== null)
3136
3387
  base.unshift(secureExpr());
3137
3388
  if (args.length === 1) {
3138
- const fn = binding !== null ? "https.requestFn" : secure === true ? "https.request" : "http.request";
3389
+ const fn = binding !== null ? "https.requestFn"
3390
+ : secure === true ? (agentVal !== null ? "https.requestAgent" : "https.request")
3391
+ : agentVal !== null ? "http.requestAgent" : "http.request";
3139
3392
  return { kind: "libCall", fn, args: base, type: HTTPCLIENTREQ_T, loc };
3140
3393
  }
3141
3394
  const { cb } = lowerCallbackArg(L, args[1], "response callbacks", 1, (p) => p.kind === "httpReq", "use (res) or ()", [HTTPREQ_T]);
3142
- const fn = binding !== null ? "https.requestFnCb" : secure === true ? "https.requestCb" : "http.requestCb";
3395
+ const fn = binding !== null ? "https.requestFnCb"
3396
+ : secure === true ? (agentVal !== null ? "https.requestAgentCb" : "https.requestCb")
3397
+ : agentVal !== null ? "http.requestAgentCb" : "http.requestCb";
3143
3398
  return { kind: "libCall", fn, args: [...base, cb], type: HTTPCLIENTREQ_T, loc };
3144
3399
  }
3145
3400
  /** The headers option of http.request: an object literal with literal