capnweb 0.4.0 → 0.6.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.
@@ -49,6 +49,7 @@ var RpcTarget = workersModule ? workersModule.RpcTarget : class {
49
49
  };
50
50
  var AsyncFunction = (async function() {
51
51
  }).constructor;
52
+ var BUFFER_PROTOTYPE = typeof Buffer !== "undefined" ? Buffer.prototype : void 0;
52
53
  function typeForRpc(value) {
53
54
  switch (typeof value) {
54
55
  case "boolean":
@@ -80,7 +81,18 @@ function typeForRpc(value) {
80
81
  case Date.prototype:
81
82
  return "date";
82
83
  case Uint8Array.prototype:
84
+ case BUFFER_PROTOTYPE:
83
85
  return "bytes";
86
+ case WritableStream.prototype:
87
+ return "writable";
88
+ case ReadableStream.prototype:
89
+ return "readable";
90
+ case Headers.prototype:
91
+ return "headers";
92
+ case Request.prototype:
93
+ return "request";
94
+ case Response.prototype:
95
+ return "response";
84
96
  // TODO: All other structured clone types.
85
97
  case RpcStub.prototype:
86
98
  return "stub";
@@ -108,7 +120,34 @@ function mapNotLoaded() {
108
120
  throw new Error("RPC map() implementation was not loaded.");
109
121
  }
110
122
  var mapImpl = { applyMap: mapNotLoaded, sendMap: mapNotLoaded };
123
+ function streamNotLoaded() {
124
+ throw new Error("Stream implementation was not loaded.");
125
+ }
126
+ var streamImpl = {
127
+ createWritableStreamHook: streamNotLoaded,
128
+ createWritableStreamFromHook: streamNotLoaded,
129
+ createReadableStreamHook: streamNotLoaded
130
+ };
111
131
  var StubHook = class {
132
+ // Like call(), but designed for streaming calls (e.g. WritableStream writes). Returns:
133
+ // - promise: A Promise<void> for the completion of the call.
134
+ // - size: If the call was remote, the byte size of the serialized message. For local calls,
135
+ // undefined is returned, indicating the caller should await the promise to serialize writes
136
+ // (no overlapping).
137
+ stream(path, args) {
138
+ let hook = this.call(path, args);
139
+ let pulled = hook.pull();
140
+ let promise;
141
+ if (pulled instanceof Promise) {
142
+ promise = pulled.then((p) => {
143
+ p.dispose();
144
+ });
145
+ } else {
146
+ pulled.dispose();
147
+ promise = Promise.resolve();
148
+ }
149
+ return { promise };
150
+ }
112
151
  };
113
152
  var ErrorStubHook = class extends StubHook {
114
153
  constructor(error) {
@@ -333,10 +372,10 @@ async function pullPromise(promise) {
333
372
  }
334
373
  var RpcPayload = class _RpcPayload {
335
374
  // Private constructor; use factory functions above to construct.
336
- constructor(value, source, stubs, promises) {
375
+ constructor(value, source, hooks, promises) {
337
376
  this.value = value;
338
377
  this.source = source;
339
- this.stubs = stubs;
378
+ this.hooks = hooks;
340
379
  this.promises = promises;
341
380
  }
342
381
  // Create a payload from a value passed as params to an RPC from the app.
@@ -361,13 +400,13 @@ var RpcPayload = class _RpcPayload {
361
400
  // stubs is transferred from the inputs to the outputs, hence if the output is disposed, the
362
401
  // inputs should not be. (In case of exception, nothing is disposed, though.)
363
402
  static fromArray(array) {
364
- let stubs = [];
403
+ let hooks = [];
365
404
  let promises = [];
366
405
  let resultArray = [];
367
406
  for (let payload of array) {
368
407
  payload.ensureDeepCopied();
369
- for (let stub of payload.stubs) {
370
- stubs.push(stub);
408
+ for (let hook of payload.hooks) {
409
+ hooks.push(hook);
371
410
  }
372
411
  for (let promise of payload.promises) {
373
412
  if (promise.parent === payload) {
@@ -381,12 +420,12 @@ var RpcPayload = class _RpcPayload {
381
420
  }
382
421
  resultArray.push(payload.value);
383
422
  }
384
- return new _RpcPayload(resultArray, "owned", stubs, promises);
423
+ return new _RpcPayload(resultArray, "owned", hooks, promises);
385
424
  }
386
425
  // Create a payload from a value parsed off the wire using Evaluator.evaluate().
387
426
  //
388
- // A payload is constructed with a null value and the given stubs and promises arrays. The value
389
- // is expected to be filled in by the evaluator, and the stubs and promises arrays are expected
427
+ // A payload is constructed with a null value and the given hooks and promises arrays. The value
428
+ // is expected to be filled in by the evaluator, and the hooks and promises arrays are expected
390
429
  // to be extended with stubs found during parsing. (This weird usage model is necessary so that
391
430
  // if the root value turns out to be a promise, its `parent` in `promises` can be the payload
392
431
  // object itself.)
@@ -394,8 +433,8 @@ var RpcPayload = class _RpcPayload {
394
433
  // When done, the payload takes ownership of the final value and all the stubs within. It may
395
434
  // modify the value in preparation for delivery, and may deliver the value directly to the app
396
435
  // without copying.
397
- static forEvaluate(stubs, promises) {
398
- return new _RpcPayload(null, "owned", stubs, promises);
436
+ static forEvaluate(hooks, promises) {
437
+ return new _RpcPayload(null, "owned", hooks, promises);
399
438
  }
400
439
  // Deep-copy the given value, including dup()ing all stubs.
401
440
  //
@@ -417,8 +456,8 @@ var RpcPayload = class _RpcPayload {
417
456
  return result;
418
457
  }
419
458
  // For `source === "return"` payloads only, this tracks any StubHooks created around RpcTargets
420
- // found in the payload at the time that it is serialized (or deep-copied) for return, so that we
421
- // can make sure they are not disposed before the pipeline ends.
459
+ // or WritableStreams found in the payload at the time that it is serialized (or deep-copied) for
460
+ // return, so that we can make sure they are not disposed before the pipeline ends.
422
461
  //
423
462
  // This is initialized on first use.
424
463
  rpcTargets;
@@ -457,6 +496,64 @@ var RpcPayload = class _RpcPayload {
457
496
  throw new Error("owned payload shouldn't contain raw RpcTargets");
458
497
  }
459
498
  }
499
+ // Get the StubHook representing the given WritableStream found inside this payload.
500
+ getHookForWritableStream(stream, parent, dupStubs = true) {
501
+ if (this.source === "params") {
502
+ return streamImpl.createWritableStreamHook(stream);
503
+ } else if (this.source === "return") {
504
+ let hook = this.rpcTargets?.get(stream);
505
+ if (hook) {
506
+ if (dupStubs) {
507
+ return hook.dup();
508
+ } else {
509
+ this.rpcTargets?.delete(stream);
510
+ return hook;
511
+ }
512
+ } else {
513
+ hook = streamImpl.createWritableStreamHook(stream);
514
+ if (dupStubs) {
515
+ if (!this.rpcTargets) {
516
+ this.rpcTargets = /* @__PURE__ */ new Map();
517
+ }
518
+ this.rpcTargets.set(stream, hook);
519
+ return hook.dup();
520
+ } else {
521
+ return hook;
522
+ }
523
+ }
524
+ } else {
525
+ throw new Error("owned payload shouldn't contain raw WritableStreams");
526
+ }
527
+ }
528
+ // Get the StubHook representing the given ReadableStream found inside this payload.
529
+ getHookForReadableStream(stream, parent, dupStubs = true) {
530
+ if (this.source === "params") {
531
+ return streamImpl.createReadableStreamHook(stream);
532
+ } else if (this.source === "return") {
533
+ let hook = this.rpcTargets?.get(stream);
534
+ if (hook) {
535
+ if (dupStubs) {
536
+ return hook.dup();
537
+ } else {
538
+ this.rpcTargets?.delete(stream);
539
+ return hook;
540
+ }
541
+ } else {
542
+ hook = streamImpl.createReadableStreamHook(stream);
543
+ if (dupStubs) {
544
+ if (!this.rpcTargets) {
545
+ this.rpcTargets = /* @__PURE__ */ new Map();
546
+ }
547
+ this.rpcTargets.set(stream, hook);
548
+ return hook.dup();
549
+ } else {
550
+ return hook;
551
+ }
552
+ }
553
+ } else {
554
+ throw new Error("owned payload shouldn't contain raw ReadableStreams");
555
+ }
556
+ }
460
557
  deepCopy(value, oldParent, property, parent, dupStubs, owner) {
461
558
  let kind = typeForRpc(value);
462
559
  switch (kind) {
@@ -500,22 +597,21 @@ var RpcPayload = class _RpcPayload {
500
597
  this.promises.push({ parent, property, promise });
501
598
  return promise;
502
599
  } else {
503
- let newStub = new RpcStub(hook);
504
- this.stubs.push(newStub);
505
- return newStub;
600
+ this.hooks.push(hook);
601
+ return new RpcStub(hook);
506
602
  }
507
603
  }
508
604
  case "function":
509
605
  case "rpc-target": {
510
606
  let target = value;
511
- let stub;
607
+ let hook;
512
608
  if (owner) {
513
- stub = new RpcStub(owner.getHookForRpcTarget(target, oldParent, dupStubs));
609
+ hook = owner.getHookForRpcTarget(target, oldParent, dupStubs);
514
610
  } else {
515
- stub = new RpcStub(TargetStubHook.create(target, oldParent));
611
+ hook = TargetStubHook.create(target, oldParent);
516
612
  }
517
- this.stubs.push(stub);
518
- return stub;
613
+ this.hooks.push(hook);
614
+ return new RpcStub(hook);
519
615
  }
520
616
  case "rpc-thenable": {
521
617
  let target = value;
@@ -528,6 +624,44 @@ var RpcPayload = class _RpcPayload {
528
624
  this.promises.push({ parent, property, promise });
529
625
  return promise;
530
626
  }
627
+ case "writable": {
628
+ let stream = value;
629
+ let hook;
630
+ if (owner) {
631
+ hook = owner.getHookForWritableStream(stream, oldParent, dupStubs);
632
+ } else {
633
+ hook = streamImpl.createWritableStreamHook(stream);
634
+ }
635
+ this.hooks.push(hook);
636
+ return stream;
637
+ }
638
+ case "readable": {
639
+ let stream = value;
640
+ let hook;
641
+ if (owner) {
642
+ hook = owner.getHookForReadableStream(stream, oldParent, dupStubs);
643
+ } else {
644
+ hook = streamImpl.createReadableStreamHook(stream);
645
+ }
646
+ this.hooks.push(hook);
647
+ return stream;
648
+ }
649
+ case "headers":
650
+ return new Headers(value);
651
+ case "request": {
652
+ let req = value;
653
+ if (req.body) {
654
+ this.deepCopy(req.body, req, "body", req, dupStubs, owner);
655
+ }
656
+ return new Request(req);
657
+ }
658
+ case "response": {
659
+ let resp = value;
660
+ if (resp.body) {
661
+ this.deepCopy(resp.body, resp, "body", resp, dupStubs, owner);
662
+ }
663
+ return new Response(resp.body, resp);
664
+ }
531
665
  default:
532
666
  throw new Error("unreachable");
533
667
  }
@@ -537,12 +671,12 @@ var RpcPayload = class _RpcPayload {
537
671
  ensureDeepCopied() {
538
672
  if (this.source !== "owned") {
539
673
  let dupStubs = this.source === "params";
540
- this.stubs = [];
674
+ this.hooks = [];
541
675
  this.promises = [];
542
676
  try {
543
677
  this.value = this.deepCopy(this.value, void 0, "value", this, dupStubs, this);
544
678
  } catch (err) {
545
- this.stubs = void 0;
679
+ this.hooks = void 0;
546
680
  this.promises = void 0;
547
681
  throw err;
548
682
  }
@@ -645,7 +779,7 @@ var RpcPayload = class _RpcPayload {
645
779
  }
646
780
  dispose() {
647
781
  if (this.source === "owned") {
648
- this.stubs.forEach((stub) => stub[Symbol.dispose]());
782
+ this.hooks.forEach((hook) => hook.dispose());
649
783
  this.promises.forEach((promise) => promise.promise[Symbol.dispose]());
650
784
  } else if (this.source === "return") {
651
785
  this.disposeImpl(this.value, void 0);
@@ -654,7 +788,7 @@ var RpcPayload = class _RpcPayload {
654
788
  }
655
789
  } else ;
656
790
  this.source = "owned";
657
- this.stubs = [];
791
+ this.hooks = [];
658
792
  this.promises = [];
659
793
  }
660
794
  // Recursive dispose, called only when `source` is "return".
@@ -707,6 +841,40 @@ var RpcPayload = class _RpcPayload {
707
841
  }
708
842
  case "rpc-thenable":
709
843
  return;
844
+ case "headers":
845
+ return;
846
+ case "request": {
847
+ let req = value;
848
+ if (req.body) this.disposeImpl(req.body, req);
849
+ return;
850
+ }
851
+ case "response": {
852
+ let resp = value;
853
+ if (resp.body) this.disposeImpl(resp.body, resp);
854
+ return;
855
+ }
856
+ case "writable": {
857
+ let stream = value;
858
+ let hook = this.rpcTargets?.get(stream);
859
+ if (hook) {
860
+ this.rpcTargets.delete(stream);
861
+ } else {
862
+ hook = streamImpl.createWritableStreamHook(stream);
863
+ }
864
+ hook.dispose();
865
+ return;
866
+ }
867
+ case "readable": {
868
+ let stream = value;
869
+ let hook = this.rpcTargets?.get(stream);
870
+ if (hook) {
871
+ this.rpcTargets.delete(stream);
872
+ } else {
873
+ hook = streamImpl.createReadableStreamHook(stream);
874
+ }
875
+ hook.dispose();
876
+ return;
877
+ }
710
878
  default:
711
879
  return;
712
880
  }
@@ -715,9 +883,9 @@ var RpcPayload = class _RpcPayload {
715
883
  // *would* be awaited if this payload were to be delivered. See the similarly-named method of
716
884
  // StubHook for explanation.
717
885
  ignoreUnhandledRejections() {
718
- if (this.stubs) {
719
- this.stubs.forEach((stub) => {
720
- unwrapStubOrParent(stub).ignoreUnhandledRejections();
886
+ if (this.hooks) {
887
+ this.hooks.forEach((hook) => {
888
+ hook.ignoreUnhandledRejections();
721
889
  });
722
890
  this.promises.forEach(
723
891
  (promise) => unwrapStubOrParent(promise.promise).ignoreUnhandledRejections()
@@ -738,6 +906,11 @@ var RpcPayload = class _RpcPayload {
738
906
  case "undefined":
739
907
  case "function":
740
908
  case "rpc-target":
909
+ case "writable":
910
+ case "readable":
911
+ case "headers":
912
+ case "request":
913
+ case "response":
741
914
  return;
742
915
  case "array": {
743
916
  let array = value;
@@ -810,11 +983,20 @@ function followPath(value, parent, path, owner) {
810
983
  let { hook, pathIfPromise } = unwrapStubAndPath(value);
811
984
  return { hook, remainingPath: pathIfPromise ? pathIfPromise.concat(path.slice(i)) : path.slice(i) };
812
985
  }
986
+ case "writable":
987
+ value = void 0;
988
+ break;
989
+ case "readable":
990
+ value = void 0;
991
+ break;
813
992
  case "primitive":
814
993
  case "bigint":
815
994
  case "bytes":
816
995
  case "date":
817
996
  case "error":
997
+ case "headers":
998
+ case "request":
999
+ case "response":
818
1000
  value = void 0;
819
1001
  break;
820
1002
  case "undefined":
@@ -1049,6 +1231,14 @@ var PromiseStubHook = class _PromiseStubHook extends StubHook {
1049
1231
  args.ensureDeepCopied();
1050
1232
  return new _PromiseStubHook(this.promise.then((hook) => hook.call(path, args)));
1051
1233
  }
1234
+ stream(path, args) {
1235
+ args.ensureDeepCopied();
1236
+ let promise = this.promise.then((hook) => {
1237
+ let result = hook.stream(path, args);
1238
+ return result.promise;
1239
+ });
1240
+ return { promise };
1241
+ }
1052
1242
  map(path, captures, instructions) {
1053
1243
  return new _PromiseStubHook(this.promise.then(
1054
1244
  (hook) => hook.map(path, captures, instructions),
@@ -1121,6 +1311,9 @@ var NullExporter = class {
1121
1311
  }
1122
1312
  unexport(ids) {
1123
1313
  }
1314
+ createPipe(readable) {
1315
+ throw new Error("Cannot create pipes without an RPC session.");
1316
+ }
1124
1317
  onSendError(error) {
1125
1318
  }
1126
1319
  };
@@ -1218,12 +1411,86 @@ var Devaluator = class _Devaluator {
1218
1411
  let bytes = value;
1219
1412
  if (bytes.toBase64) {
1220
1413
  return ["bytes", bytes.toBase64({ omitPadding: true })];
1414
+ }
1415
+ let b64;
1416
+ if (typeof Buffer !== "undefined") {
1417
+ let buf = bytes instanceof Buffer ? bytes : Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
1418
+ b64 = buf.toString("base64");
1221
1419
  } else {
1222
- return [
1223
- "bytes",
1224
- btoa(String.fromCharCode.apply(null, bytes).replace(/=*$/, ""))
1225
- ];
1420
+ let binary = "";
1421
+ for (let i = 0; i < bytes.length; i++) {
1422
+ binary += String.fromCharCode(bytes[i]);
1423
+ }
1424
+ b64 = btoa(binary);
1425
+ }
1426
+ return ["bytes", b64.replace(/=+$/, "")];
1427
+ }
1428
+ case "headers":
1429
+ return ["headers", [...value]];
1430
+ case "request": {
1431
+ let req = value;
1432
+ let init = {};
1433
+ if (req.method !== "GET") init.method = req.method;
1434
+ let headers = [...req.headers];
1435
+ if (headers.length > 0) {
1436
+ init.headers = headers;
1437
+ }
1438
+ if (req.body) {
1439
+ init.body = this.devaluateImpl(req.body, req, depth + 1);
1440
+ init.duplex = req.duplex || "half";
1441
+ } else if (req.body === void 0 && !["GET", "HEAD", "OPTIONS", "TRACE", "DELETE"].includes(req.method)) {
1442
+ let bodyPromise = req.arrayBuffer();
1443
+ let readable = new ReadableStream({
1444
+ async start(controller) {
1445
+ try {
1446
+ controller.enqueue(new Uint8Array(await bodyPromise));
1447
+ controller.close();
1448
+ } catch (err) {
1449
+ controller.error(err);
1450
+ }
1451
+ }
1452
+ });
1453
+ let hook = streamImpl.createReadableStreamHook(readable);
1454
+ let importId = this.exporter.createPipe(readable, hook);
1455
+ init.body = ["readable", importId];
1456
+ init.duplex = req.duplex || "half";
1226
1457
  }
1458
+ if (req.cache && req.cache !== "default") init.cache = req.cache;
1459
+ if (req.redirect !== "follow") init.redirect = req.redirect;
1460
+ if (req.integrity) init.integrity = req.integrity;
1461
+ if (req.mode && req.mode !== "cors") init.mode = req.mode;
1462
+ if (req.credentials && req.credentials !== "same-origin") {
1463
+ init.credentials = req.credentials;
1464
+ }
1465
+ if (req.referrer && req.referrer !== "about:client") init.referrer = req.referrer;
1466
+ if (req.referrerPolicy) init.referrerPolicy = req.referrerPolicy;
1467
+ if (req.keepalive) init.keepalive = req.keepalive;
1468
+ let cfReq = req;
1469
+ if (cfReq.cf) init.cf = cfReq.cf;
1470
+ if (cfReq.encodeResponseBody && cfReq.encodeResponseBody !== "automatic") {
1471
+ init.encodeResponseBody = cfReq.encodeResponseBody;
1472
+ }
1473
+ return ["request", req.url, init];
1474
+ }
1475
+ case "response": {
1476
+ let resp = value;
1477
+ let body = this.devaluateImpl(resp.body, resp, depth + 1);
1478
+ let init = {};
1479
+ if (resp.status !== 200) init.status = resp.status;
1480
+ if (resp.statusText) init.statusText = resp.statusText;
1481
+ let headers = [...resp.headers];
1482
+ if (headers.length > 0) {
1483
+ init.headers = headers;
1484
+ }
1485
+ let cfResp = resp;
1486
+ if (cfResp.cf) init.cf = cfResp.cf;
1487
+ if (cfResp.encodeBody && cfResp.encodeBody !== "automatic") {
1488
+ init.encodeBody = cfResp.encodeBody;
1489
+ }
1490
+ if (cfResp.webSocket) {
1491
+ throw new TypeError("Can't serialize a Response containing a webSocket.");
1492
+ }
1493
+ return ["response", body, init];
1227
1494
  }
1228
1495
  case "error": {
1229
1496
  let e = value;
@@ -1279,6 +1546,22 @@ var Devaluator = class _Devaluator {
1279
1546
  let hook = this.source.getHookForRpcTarget(value, parent);
1280
1547
  return this.devaluateHook("promise", hook);
1281
1548
  }
1549
+ case "writable": {
1550
+ if (!this.source) {
1551
+ throw new Error("Can't serialize WritableStream in this context.");
1552
+ }
1553
+ let hook = this.source.getHookForWritableStream(value, parent);
1554
+ return this.devaluateHook("writable", hook);
1555
+ }
1556
+ case "readable": {
1557
+ if (!this.source) {
1558
+ throw new Error("Can't serialize ReadableStream in this context.");
1559
+ }
1560
+ let ws = value;
1561
+ let hook = this.source.getHookForReadableStream(ws, parent);
1562
+ let importId = this.exporter.createPipe(ws, hook);
1563
+ return ["readable", importId];
1564
+ }
1282
1565
  default:
1283
1566
  throw new Error("unreachable");
1284
1567
  }
@@ -1303,16 +1586,27 @@ var NullImporter = class {
1303
1586
  getExport(idx) {
1304
1587
  return void 0;
1305
1588
  }
1589
+ getPipeReadable(exportId) {
1590
+ throw new Error("Cannot retrieve pipe readable without an RPC session.");
1591
+ }
1306
1592
  };
1307
1593
  var NULL_IMPORTER = new NullImporter();
1594
+ function fixBrokenRequestBody(request, body) {
1595
+ let promise = new Response(body).arrayBuffer().then((arrayBuffer) => {
1596
+ let bytes = new Uint8Array(arrayBuffer);
1597
+ let result = new Request(request, { body: bytes });
1598
+ return new PayloadStubHook(RpcPayload.fromAppReturn(result));
1599
+ });
1600
+ return new RpcPromise(new PromiseStubHook(promise), []);
1601
+ }
1308
1602
  var Evaluator = class _Evaluator {
1309
1603
  constructor(importer) {
1310
1604
  this.importer = importer;
1311
1605
  }
1312
- stubs = [];
1606
+ hooks = [];
1313
1607
  promises = [];
1314
1608
  evaluate(value) {
1315
- let payload = RpcPayload.forEvaluate(this.stubs, this.promises);
1609
+ let payload = RpcPayload.forEvaluate(this.hooks, this.promises);
1316
1610
  try {
1317
1611
  payload.value = this.evaluateImpl(value, payload, "value");
1318
1612
  return payload;
@@ -1345,10 +1639,11 @@ var Evaluator = class _Evaluator {
1345
1639
  }
1346
1640
  break;
1347
1641
  case "bytes": {
1348
- let b64 = Uint8Array;
1349
1642
  if (typeof value[1] == "string") {
1350
- if (b64.fromBase64) {
1351
- return b64.fromBase64(value[1]);
1643
+ if (typeof Buffer !== "undefined") {
1644
+ return Buffer.from(value[1], "base64");
1645
+ } else if (Uint8Array.fromBase64) {
1646
+ return Uint8Array.fromBase64(value[1]);
1352
1647
  } else {
1353
1648
  let bs = atob(value[1]);
1354
1649
  let len = bs.length;
@@ -1382,6 +1677,56 @@ var Evaluator = class _Evaluator {
1382
1677
  return -Infinity;
1383
1678
  case "nan":
1384
1679
  return NaN;
1680
+ case "headers":
1681
+ if (value.length === 2 && value[1] instanceof Array) {
1682
+ return new Headers(value[1]);
1683
+ }
1684
+ break;
1685
+ case "request": {
1686
+ if (value.length !== 3 || typeof value[1] !== "string") break;
1687
+ let url = value[1];
1688
+ let init = value[2];
1689
+ if (typeof init !== "object" || init === null) break;
1690
+ if (init.body) {
1691
+ init.body = this.evaluateImpl(init.body, init, "body");
1692
+ if (init.body === null || typeof init.body === "string" || init.body instanceof Uint8Array || init.body instanceof ReadableStream) ; else {
1693
+ throw new TypeError("Request body must be of type ReadableStream.");
1694
+ }
1695
+ }
1696
+ if (init.signal) {
1697
+ init.signal = this.evaluateImpl(init.signal, init, "signal");
1698
+ if (!(init.signal instanceof AbortSignal)) {
1699
+ throw new TypeError("Request siganl must be of type AbortSignal.");
1700
+ }
1701
+ }
1702
+ if (init.headers && !(init.headers instanceof Array)) {
1703
+ throw new TypeError("Request headers must be serialized as an array of pairs.");
1704
+ }
1705
+ let result = new Request(url, init);
1706
+ if (init.body instanceof ReadableStream && result.body === void 0) {
1707
+ let promise = fixBrokenRequestBody(result, init.body);
1708
+ this.promises.push({ promise, parent, property });
1709
+ return promise;
1710
+ } else {
1711
+ return result;
1712
+ }
1713
+ }
1714
+ case "response": {
1715
+ if (value.length !== 3) break;
1716
+ let body = this.evaluateImpl(value[1], parent, property);
1717
+ if (body === null || typeof body === "string" || body instanceof Uint8Array || body instanceof ReadableStream) ; else {
1718
+ throw new TypeError("Response body must be of type ReadableStream.");
1719
+ }
1720
+ let init = value[2];
1721
+ if (typeof init !== "object" || init === null) break;
1722
+ if (init.webSocket) {
1723
+ throw new TypeError("Can't deserialize a Response containing a webSocket.");
1724
+ }
1725
+ if (init.headers && !(init.headers instanceof Array)) {
1726
+ throw new TypeError("Request headers must be serialized as an array of pairs.");
1727
+ }
1728
+ return new Response(body, init);
1729
+ }
1385
1730
  case "import":
1386
1731
  case "pipeline": {
1387
1732
  if (value.length < 2 || value.length > 4) {
@@ -1401,9 +1746,8 @@ var Evaluator = class _Evaluator {
1401
1746
  this.promises.push({ promise, parent, property });
1402
1747
  return promise;
1403
1748
  } else {
1404
- let stub = new RpcPromise(hook2, []);
1405
- this.stubs.push(stub);
1406
- return stub;
1749
+ this.hooks.push(hook2);
1750
+ return new RpcPromise(hook2, []);
1407
1751
  }
1408
1752
  };
1409
1753
  if (value.length == 2) {
@@ -1481,12 +1825,27 @@ var Evaluator = class _Evaluator {
1481
1825
  return promise;
1482
1826
  } else {
1483
1827
  let hook = this.importer.importStub(value[1]);
1484
- let stub = new RpcStub(hook);
1485
- this.stubs.push(stub);
1486
- return stub;
1828
+ this.hooks.push(hook);
1829
+ return new RpcStub(hook);
1487
1830
  }
1488
1831
  }
1489
1832
  break;
1833
+ case "writable":
1834
+ if (typeof value[1] == "number") {
1835
+ let hook = this.importer.importStub(value[1]);
1836
+ let stream = streamImpl.createWritableStreamFromHook(hook);
1837
+ this.hooks.push(hook);
1838
+ return stream;
1839
+ }
1840
+ break;
1841
+ case "readable":
1842
+ if (typeof value[1] == "number") {
1843
+ let stream = this.importer.getPipeReadable(value[1]);
1844
+ let hook = streamImpl.createReadableStreamHook(stream);
1845
+ this.hooks.push(hook);
1846
+ return stream;
1847
+ }
1848
+ break;
1490
1849
  }
1491
1850
  throw new TypeError(`unknown special value: ${JSON.stringify(value)}`);
1492
1851
  } else if (value instanceof Object) {
@@ -1626,6 +1985,14 @@ var RpcImportHook = class _RpcImportHook extends StubHook {
1626
1985
  return entry.session.sendCall(entry.importId, path, args);
1627
1986
  }
1628
1987
  }
1988
+ stream(path, args) {
1989
+ let entry = this.getEntry();
1990
+ if (entry.resolution) {
1991
+ return entry.resolution.stream(path, args);
1992
+ } else {
1993
+ return entry.session.sendStream(entry.importId, path, args);
1994
+ }
1995
+ }
1629
1996
  map(path, captures, instructions) {
1630
1997
  let entry;
1631
1998
  try {
@@ -1798,19 +2165,23 @@ var RpcSessionImpl = class {
1798
2165
  return payload;
1799
2166
  }
1800
2167
  };
2168
+ let autoRelease = exp.autoRelease;
1801
2169
  ++this.pullCount;
1802
2170
  exp.pull = resolve().then(
1803
2171
  (payload) => {
1804
2172
  let value = Devaluator.devaluate(payload.value, void 0, this, payload);
1805
2173
  this.send(["resolve", exportId, value]);
2174
+ if (autoRelease) this.releaseExport(exportId, 1);
1806
2175
  },
1807
2176
  (error) => {
1808
2177
  this.send(["reject", exportId, Devaluator.devaluate(error, void 0, this)]);
2178
+ if (autoRelease) this.releaseExport(exportId, 1);
1809
2179
  }
1810
2180
  ).catch(
1811
2181
  (error) => {
1812
2182
  try {
1813
2183
  this.send(["reject", exportId, Devaluator.devaluate(error, void 0, this)]);
2184
+ if (autoRelease) this.releaseExport(exportId, 1);
1814
2185
  } catch (error2) {
1815
2186
  this.abort(error2);
1816
2187
  }
@@ -1862,9 +2233,35 @@ var RpcSessionImpl = class {
1862
2233
  getExport(idx) {
1863
2234
  return this.exports[idx]?.hook;
1864
2235
  }
2236
+ getPipeReadable(exportId) {
2237
+ let entry = this.exports[exportId];
2238
+ if (!entry || !entry.pipeReadable) {
2239
+ throw new Error(`Export ${exportId} is not a pipe or its readable end was already consumed.`);
2240
+ }
2241
+ let readable = entry.pipeReadable;
2242
+ entry.pipeReadable = void 0;
2243
+ return readable;
2244
+ }
2245
+ createPipe(readable, readableHook) {
2246
+ if (this.abortReason) throw this.abortReason;
2247
+ this.send(["pipe"]);
2248
+ let importId = this.imports.length;
2249
+ let entry = new ImportTableEntry(this, importId, false);
2250
+ this.imports.push(entry);
2251
+ let hook = new RpcImportHook(
2252
+ /*isPromise=*/
2253
+ false,
2254
+ entry
2255
+ );
2256
+ let writable = streamImpl.createWritableStreamFromHook(hook);
2257
+ readable.pipeTo(writable).catch(() => {
2258
+ }).finally(() => readableHook.dispose());
2259
+ return importId;
2260
+ }
2261
+ // Serializes and sends a message. Returns the byte length of the serialized message.
1865
2262
  send(msg) {
1866
2263
  if (this.abortReason !== void 0) {
1867
- return;
2264
+ return 0;
1868
2265
  }
1869
2266
  let msgText;
1870
2267
  try {
@@ -1877,6 +2274,7 @@ var RpcSessionImpl = class {
1877
2274
  throw err;
1878
2275
  }
1879
2276
  this.transport.send(msgText).catch((err) => this.abort(err, false));
2277
+ return msgText.length;
1880
2278
  }
1881
2279
  sendCall(id, path, args) {
1882
2280
  if (this.abortReason) throw this.abortReason;
@@ -1894,6 +2292,34 @@ var RpcSessionImpl = class {
1894
2292
  entry
1895
2293
  );
1896
2294
  }
2295
+ sendStream(id, path, args) {
2296
+ if (this.abortReason) throw this.abortReason;
2297
+ let value = ["pipeline", id, path];
2298
+ let devalue = Devaluator.devaluate(args.value, void 0, this, args);
2299
+ value.push(devalue[0]);
2300
+ let size = this.send(["stream", value]);
2301
+ let importId = this.imports.length;
2302
+ let entry = new ImportTableEntry(
2303
+ this,
2304
+ importId,
2305
+ /*pulling=*/
2306
+ true
2307
+ );
2308
+ entry.remoteRefcount = 0;
2309
+ entry.localRefcount = 1;
2310
+ this.imports.push(entry);
2311
+ let promise = entry.awaitResolution().then(
2312
+ (p) => {
2313
+ p.dispose();
2314
+ delete this.imports[importId];
2315
+ },
2316
+ (err) => {
2317
+ delete this.imports[importId];
2318
+ throw err;
2319
+ }
2320
+ );
2321
+ return { promise, size };
2322
+ }
1897
2323
  sendMap(id, path, captures, instructions) {
1898
2324
  if (this.abortReason) {
1899
2325
  for (let cap of captures) {
@@ -1981,6 +2407,24 @@ var RpcSessionImpl = class {
1981
2407
  continue;
1982
2408
  }
1983
2409
  break;
2410
+ case "stream": {
2411
+ if (msg.length > 1) {
2412
+ let payload = new Evaluator(this).evaluate(msg[1]);
2413
+ let hook = new PayloadStubHook(payload);
2414
+ hook.ignoreUnhandledRejections();
2415
+ let exportId = this.exports.length;
2416
+ this.exports.push({ hook, refcount: 1, autoRelease: true });
2417
+ this.ensureResolvingExport(exportId);
2418
+ continue;
2419
+ }
2420
+ break;
2421
+ }
2422
+ case "pipe": {
2423
+ let { readable, writable } = new TransformStream();
2424
+ let hook = streamImpl.createWritableStreamHook(writable);
2425
+ this.exports.push({ hook, refcount: 1, pipeReadable: readable });
2426
+ continue;
2427
+ }
1984
2428
  case "pull": {
1985
2429
  let exportId = msg[1];
1986
2430
  if (typeof exportId == "number") {
@@ -2476,6 +2920,9 @@ var MapBuilder = class {
2476
2920
  }
2477
2921
  unexport(ids) {
2478
2922
  }
2923
+ createPipe(readable) {
2924
+ throw new Error("Cannot send ReadableStream inside a mapper function.");
2925
+ }
2479
2926
  onSendError(error) {
2480
2927
  }
2481
2928
  };
@@ -2585,6 +3032,9 @@ var MapApplicator = class {
2585
3032
  return this.variables[idx];
2586
3033
  }
2587
3034
  }
3035
+ getPipeReadable(exportId) {
3036
+ throw new Error("A mapper function cannot use pipe readables.");
3037
+ }
2588
3038
  };
2589
3039
  function applyMapToElement(input, parent, owner, captures, instructions) {
2590
3040
  let inputHook = new PayloadStubHook(RpcPayload.deepCopyFrom(input, parent, owner));
@@ -2625,6 +3075,333 @@ mapImpl.applyMap = (input, parent, owner, captures, instructions) => {
2625
3075
  }
2626
3076
  }
2627
3077
  };
3078
+
3079
+ // src/streams.ts
3080
+ var WritableStreamStubHook = class _WritableStreamStubHook extends StubHook {
3081
+ state;
3082
+ // undefined when disposed
3083
+ // Creates a new WritableStreamStubHook that is not duplicated from an existing hook.
3084
+ static create(stream) {
3085
+ let writer = stream.getWriter();
3086
+ return new _WritableStreamStubHook({ refcount: 1, writer, closed: false });
3087
+ }
3088
+ constructor(state, dupFrom) {
3089
+ super();
3090
+ this.state = state;
3091
+ if (dupFrom) {
3092
+ ++state.refcount;
3093
+ }
3094
+ }
3095
+ getState() {
3096
+ if (this.state) {
3097
+ return this.state;
3098
+ } else {
3099
+ throw new Error("Attempted to use a WritableStreamStubHook after it was disposed.");
3100
+ }
3101
+ }
3102
+ call(path, args) {
3103
+ try {
3104
+ let state = this.getState();
3105
+ if (path.length !== 1 || typeof path[0] !== "string") {
3106
+ throw new Error("WritableStream stub only supports direct method calls");
3107
+ }
3108
+ const method = path[0];
3109
+ if (method !== "write" && method !== "close" && method !== "abort") {
3110
+ args.dispose();
3111
+ throw new Error(`Unknown WritableStream method: ${method}`);
3112
+ }
3113
+ if (method === "close" || method === "abort") {
3114
+ state.closed = true;
3115
+ }
3116
+ let func = state.writer[method];
3117
+ let promise = args.deliverCall(func, state.writer);
3118
+ return new PromiseStubHook(promise.then((payload) => new PayloadStubHook(payload)));
3119
+ } catch (err) {
3120
+ return new ErrorStubHook(err);
3121
+ }
3122
+ }
3123
+ map(path, captures, instructions) {
3124
+ for (let cap of captures) {
3125
+ cap.dispose();
3126
+ }
3127
+ return new ErrorStubHook(new Error("Cannot use map() on a WritableStream"));
3128
+ }
3129
+ get(path) {
3130
+ return new ErrorStubHook(new Error("Cannot access properties on a WritableStream stub"));
3131
+ }
3132
+ dup() {
3133
+ let state = this.getState();
3134
+ return new _WritableStreamStubHook(state, this);
3135
+ }
3136
+ pull() {
3137
+ return Promise.reject(new Error("Cannot pull a WritableStream stub"));
3138
+ }
3139
+ ignoreUnhandledRejections() {
3140
+ }
3141
+ dispose() {
3142
+ let state = this.state;
3143
+ this.state = void 0;
3144
+ if (state) {
3145
+ if (--state.refcount === 0) {
3146
+ if (!state.closed) {
3147
+ state.writer.abort(new Error("WritableStream RPC stub was disposed without calling close()")).catch(() => {
3148
+ });
3149
+ }
3150
+ state.writer.releaseLock();
3151
+ }
3152
+ }
3153
+ }
3154
+ onBroken(callback) {
3155
+ }
3156
+ };
3157
+ var INITIAL_WINDOW = 256 * 1024;
3158
+ var MAX_WINDOW = 1024 * 1024 * 1024;
3159
+ var MIN_WINDOW = 64 * 1024;
3160
+ var STARTUP_GROWTH_FACTOR = 2;
3161
+ var STEADY_GROWTH_FACTOR = 1.25;
3162
+ var DECAY_FACTOR = 0.9;
3163
+ var STARTUP_EXIT_ROUNDS = 3;
3164
+ var FlowController = class {
3165
+ constructor(now) {
3166
+ this.now = now;
3167
+ }
3168
+ // The current window size in bytes. The sender blocks when bytesInFlight >= window.
3169
+ window = INITIAL_WINDOW;
3170
+ // Total bytes currently in flight (sent but not yet acked).
3171
+ bytesInFlight = 0;
3172
+ // Whether we're still in the startup phase.
3173
+ inStartupPhase = true;
3174
+ // ----- BDP estimation state (private) -----
3175
+ // Total bytes acked so far.
3176
+ delivered = 0;
3177
+ // Time of most recent ack.
3178
+ deliveredTime = 0;
3179
+ // Time when the very first ack was received.
3180
+ firstAckTime = 0;
3181
+ firstAckDelivered = 0;
3182
+ // Global minimum RTT observed (milliseconds).
3183
+ minRtt = Infinity;
3184
+ // For startup exit: count of consecutive RTT rounds where the window didn't meaningfully grow.
3185
+ roundsWithoutIncrease = 0;
3186
+ // Window size at the start of the current round, for startup exit detection.
3187
+ lastRoundWindow = 0;
3188
+ // Time when the current round started.
3189
+ roundStartTime = 0;
3190
+ // Called when a write of `size` bytes is about to be sent. Returns a token that must be
3191
+ // passed to onAck() when the ack arrives, and whether the sender should block (window full).
3192
+ onSend(size) {
3193
+ this.bytesInFlight += size;
3194
+ let token = {
3195
+ sentTime: this.now(),
3196
+ size,
3197
+ deliveredAtSend: this.delivered,
3198
+ deliveredTimeAtSend: this.deliveredTime,
3199
+ windowAtSend: this.window,
3200
+ windowFullAtSend: this.bytesInFlight >= this.window
3201
+ };
3202
+ return { token, shouldBlock: token.windowFullAtSend };
3203
+ }
3204
+ // Called when a previously-sent write fails. Restores bytesInFlight without updating
3205
+ // any BDP estimates.
3206
+ onError(token) {
3207
+ this.bytesInFlight -= token.size;
3208
+ }
3209
+ // Called when an ack is received for a previously-sent write. Updates BDP estimates and
3210
+ // the window. Returns whether a blocked sender should now unblock.
3211
+ onAck(token) {
3212
+ let ackTime = this.now();
3213
+ this.delivered += token.size;
3214
+ this.deliveredTime = ackTime;
3215
+ this.bytesInFlight -= token.size;
3216
+ let rtt = ackTime - token.sentTime;
3217
+ this.minRtt = Math.min(this.minRtt, rtt);
3218
+ if (this.firstAckTime === 0) {
3219
+ this.firstAckTime = ackTime;
3220
+ this.firstAckDelivered = this.delivered;
3221
+ } else {
3222
+ let baseTime;
3223
+ let baseDelivered;
3224
+ if (token.deliveredTimeAtSend === 0) {
3225
+ baseTime = this.firstAckTime;
3226
+ baseDelivered = this.firstAckDelivered;
3227
+ } else {
3228
+ baseTime = token.deliveredTimeAtSend;
3229
+ baseDelivered = token.deliveredAtSend;
3230
+ }
3231
+ let interval = ackTime - baseTime;
3232
+ let bytes = this.delivered - baseDelivered;
3233
+ let bandwidth = bytes / interval;
3234
+ let growthFactor = this.inStartupPhase ? STARTUP_GROWTH_FACTOR : STEADY_GROWTH_FACTOR;
3235
+ let newWindow = bandwidth * this.minRtt * growthFactor;
3236
+ newWindow = Math.min(newWindow, token.windowAtSend * growthFactor);
3237
+ if (token.windowFullAtSend) {
3238
+ newWindow = Math.max(newWindow, token.windowAtSend * DECAY_FACTOR);
3239
+ } else {
3240
+ newWindow = Math.max(newWindow, this.window);
3241
+ }
3242
+ this.window = Math.max(Math.min(newWindow, MAX_WINDOW), MIN_WINDOW);
3243
+ if (this.inStartupPhase && token.sentTime >= this.roundStartTime) {
3244
+ if (this.window > this.lastRoundWindow * STEADY_GROWTH_FACTOR) {
3245
+ this.roundsWithoutIncrease = 0;
3246
+ } else {
3247
+ if (++this.roundsWithoutIncrease >= STARTUP_EXIT_ROUNDS) {
3248
+ this.inStartupPhase = false;
3249
+ }
3250
+ }
3251
+ this.roundStartTime = ackTime;
3252
+ this.lastRoundWindow = this.window;
3253
+ }
3254
+ }
3255
+ return this.bytesInFlight < this.window;
3256
+ }
3257
+ };
3258
+ function createWritableStreamFromHook(hook) {
3259
+ let pendingError = void 0;
3260
+ let hookDisposed = false;
3261
+ let fc = new FlowController(() => performance.now());
3262
+ let windowResolve;
3263
+ let windowReject;
3264
+ const disposeHook = () => {
3265
+ if (!hookDisposed) {
3266
+ hookDisposed = true;
3267
+ hook.dispose();
3268
+ }
3269
+ };
3270
+ return new WritableStream({
3271
+ write(chunk, controller) {
3272
+ if (pendingError !== void 0) {
3273
+ throw pendingError;
3274
+ }
3275
+ const payload = RpcPayload.fromAppParams([chunk]);
3276
+ const { promise, size } = hook.stream(["write"], payload);
3277
+ if (size === void 0) {
3278
+ return promise.catch((err) => {
3279
+ if (pendingError === void 0) {
3280
+ pendingError = err;
3281
+ }
3282
+ throw err;
3283
+ });
3284
+ } else {
3285
+ let { token, shouldBlock } = fc.onSend(size);
3286
+ promise.then(() => {
3287
+ let hasCapacity = fc.onAck(token);
3288
+ if (hasCapacity && windowResolve) {
3289
+ windowResolve();
3290
+ windowResolve = void 0;
3291
+ windowReject = void 0;
3292
+ }
3293
+ }, (err) => {
3294
+ fc.onError(token);
3295
+ if (pendingError === void 0) {
3296
+ pendingError = err;
3297
+ controller.error(err);
3298
+ disposeHook();
3299
+ }
3300
+ if (windowReject) {
3301
+ windowReject(err);
3302
+ windowResolve = void 0;
3303
+ windowReject = void 0;
3304
+ }
3305
+ });
3306
+ if (shouldBlock) {
3307
+ return new Promise((resolve, reject) => {
3308
+ windowResolve = resolve;
3309
+ windowReject = reject;
3310
+ });
3311
+ }
3312
+ }
3313
+ },
3314
+ async close() {
3315
+ if (pendingError !== void 0) {
3316
+ disposeHook();
3317
+ throw pendingError;
3318
+ }
3319
+ const { promise } = hook.stream(["close"], RpcPayload.fromAppParams([]));
3320
+ try {
3321
+ await promise;
3322
+ } catch (err) {
3323
+ throw pendingError ?? err;
3324
+ } finally {
3325
+ disposeHook();
3326
+ }
3327
+ },
3328
+ abort(reason) {
3329
+ if (pendingError !== void 0) {
3330
+ return;
3331
+ }
3332
+ pendingError = reason ?? new Error("WritableStream was aborted");
3333
+ if (windowReject) {
3334
+ windowReject(pendingError);
3335
+ windowResolve = void 0;
3336
+ windowReject = void 0;
3337
+ }
3338
+ const { promise } = hook.stream(["abort"], RpcPayload.fromAppParams([reason]));
3339
+ promise.then(() => disposeHook(), () => disposeHook());
3340
+ }
3341
+ });
3342
+ }
3343
+ var ReadableStreamStubHook = class _ReadableStreamStubHook extends StubHook {
3344
+ state;
3345
+ // undefined when disposed
3346
+ // Creates a new ReadableStreamStubHook.
3347
+ static create(stream) {
3348
+ return new _ReadableStreamStubHook({ refcount: 1, stream, canceled: false });
3349
+ }
3350
+ constructor(state, dupFrom) {
3351
+ super();
3352
+ this.state = state;
3353
+ if (dupFrom) {
3354
+ ++state.refcount;
3355
+ }
3356
+ }
3357
+ call(path, args) {
3358
+ args.dispose();
3359
+ return new ErrorStubHook(new Error("Cannot call methods on a ReadableStream stub"));
3360
+ }
3361
+ map(path, captures, instructions) {
3362
+ for (let cap of captures) {
3363
+ cap.dispose();
3364
+ }
3365
+ return new ErrorStubHook(new Error("Cannot use map() on a ReadableStream"));
3366
+ }
3367
+ get(path) {
3368
+ return new ErrorStubHook(new Error("Cannot access properties on a ReadableStream stub"));
3369
+ }
3370
+ dup() {
3371
+ let state = this.state;
3372
+ if (!state) {
3373
+ throw new Error("Attempted to dup a ReadableStreamStubHook after it was disposed.");
3374
+ }
3375
+ return new _ReadableStreamStubHook(state, this);
3376
+ }
3377
+ pull() {
3378
+ return Promise.reject(new Error("Cannot pull a ReadableStream stub"));
3379
+ }
3380
+ ignoreUnhandledRejections() {
3381
+ }
3382
+ dispose() {
3383
+ let state = this.state;
3384
+ this.state = void 0;
3385
+ if (state) {
3386
+ if (--state.refcount === 0) {
3387
+ if (!state.canceled) {
3388
+ state.canceled = true;
3389
+ if (!state.stream.locked) {
3390
+ state.stream.cancel(
3391
+ new Error("ReadableStream RPC stub was disposed without being consumed")
3392
+ ).catch(() => {
3393
+ });
3394
+ }
3395
+ }
3396
+ }
3397
+ }
3398
+ }
3399
+ onBroken(callback) {
3400
+ }
3401
+ };
3402
+ streamImpl.createWritableStreamHook = WritableStreamStubHook.create;
3403
+ streamImpl.createWritableStreamFromHook = createWritableStreamFromHook;
3404
+ streamImpl.createReadableStreamHook = ReadableStreamStubHook.create;
2628
3405
  var RpcStub2 = RpcStub;
2629
3406
  var RpcPromise2 = RpcPromise;
2630
3407
  var RpcSession2 = RpcSession;