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.
package/dist/index.js CHANGED
@@ -24,6 +24,7 @@ var RpcTarget = workersModule ? workersModule.RpcTarget : class {
24
24
  };
25
25
  var AsyncFunction = (async function() {
26
26
  }).constructor;
27
+ var BUFFER_PROTOTYPE = typeof Buffer !== "undefined" ? Buffer.prototype : void 0;
27
28
  function typeForRpc(value) {
28
29
  switch (typeof value) {
29
30
  case "boolean":
@@ -55,7 +56,18 @@ function typeForRpc(value) {
55
56
  case Date.prototype:
56
57
  return "date";
57
58
  case Uint8Array.prototype:
59
+ case BUFFER_PROTOTYPE:
58
60
  return "bytes";
61
+ case WritableStream.prototype:
62
+ return "writable";
63
+ case ReadableStream.prototype:
64
+ return "readable";
65
+ case Headers.prototype:
66
+ return "headers";
67
+ case Request.prototype:
68
+ return "request";
69
+ case Response.prototype:
70
+ return "response";
59
71
  // TODO: All other structured clone types.
60
72
  case RpcStub.prototype:
61
73
  return "stub";
@@ -83,7 +95,34 @@ function mapNotLoaded() {
83
95
  throw new Error("RPC map() implementation was not loaded.");
84
96
  }
85
97
  var mapImpl = { applyMap: mapNotLoaded, sendMap: mapNotLoaded };
98
+ function streamNotLoaded() {
99
+ throw new Error("Stream implementation was not loaded.");
100
+ }
101
+ var streamImpl = {
102
+ createWritableStreamHook: streamNotLoaded,
103
+ createWritableStreamFromHook: streamNotLoaded,
104
+ createReadableStreamHook: streamNotLoaded
105
+ };
86
106
  var StubHook = class {
107
+ // Like call(), but designed for streaming calls (e.g. WritableStream writes). Returns:
108
+ // - promise: A Promise<void> for the completion of the call.
109
+ // - size: If the call was remote, the byte size of the serialized message. For local calls,
110
+ // undefined is returned, indicating the caller should await the promise to serialize writes
111
+ // (no overlapping).
112
+ stream(path, args) {
113
+ let hook = this.call(path, args);
114
+ let pulled = hook.pull();
115
+ let promise;
116
+ if (pulled instanceof Promise) {
117
+ promise = pulled.then((p) => {
118
+ p.dispose();
119
+ });
120
+ } else {
121
+ pulled.dispose();
122
+ promise = Promise.resolve();
123
+ }
124
+ return { promise };
125
+ }
87
126
  };
88
127
  var ErrorStubHook = class extends StubHook {
89
128
  constructor(error) {
@@ -308,10 +347,10 @@ async function pullPromise(promise) {
308
347
  }
309
348
  var RpcPayload = class _RpcPayload {
310
349
  // Private constructor; use factory functions above to construct.
311
- constructor(value, source, stubs, promises) {
350
+ constructor(value, source, hooks, promises) {
312
351
  this.value = value;
313
352
  this.source = source;
314
- this.stubs = stubs;
353
+ this.hooks = hooks;
315
354
  this.promises = promises;
316
355
  }
317
356
  // Create a payload from a value passed as params to an RPC from the app.
@@ -336,13 +375,13 @@ var RpcPayload = class _RpcPayload {
336
375
  // stubs is transferred from the inputs to the outputs, hence if the output is disposed, the
337
376
  // inputs should not be. (In case of exception, nothing is disposed, though.)
338
377
  static fromArray(array) {
339
- let stubs = [];
378
+ let hooks = [];
340
379
  let promises = [];
341
380
  let resultArray = [];
342
381
  for (let payload of array) {
343
382
  payload.ensureDeepCopied();
344
- for (let stub of payload.stubs) {
345
- stubs.push(stub);
383
+ for (let hook of payload.hooks) {
384
+ hooks.push(hook);
346
385
  }
347
386
  for (let promise of payload.promises) {
348
387
  if (promise.parent === payload) {
@@ -356,12 +395,12 @@ var RpcPayload = class _RpcPayload {
356
395
  }
357
396
  resultArray.push(payload.value);
358
397
  }
359
- return new _RpcPayload(resultArray, "owned", stubs, promises);
398
+ return new _RpcPayload(resultArray, "owned", hooks, promises);
360
399
  }
361
400
  // Create a payload from a value parsed off the wire using Evaluator.evaluate().
362
401
  //
363
- // A payload is constructed with a null value and the given stubs and promises arrays. The value
364
- // is expected to be filled in by the evaluator, and the stubs and promises arrays are expected
402
+ // A payload is constructed with a null value and the given hooks and promises arrays. The value
403
+ // is expected to be filled in by the evaluator, and the hooks and promises arrays are expected
365
404
  // to be extended with stubs found during parsing. (This weird usage model is necessary so that
366
405
  // if the root value turns out to be a promise, its `parent` in `promises` can be the payload
367
406
  // object itself.)
@@ -369,8 +408,8 @@ var RpcPayload = class _RpcPayload {
369
408
  // When done, the payload takes ownership of the final value and all the stubs within. It may
370
409
  // modify the value in preparation for delivery, and may deliver the value directly to the app
371
410
  // without copying.
372
- static forEvaluate(stubs, promises) {
373
- return new _RpcPayload(null, "owned", stubs, promises);
411
+ static forEvaluate(hooks, promises) {
412
+ return new _RpcPayload(null, "owned", hooks, promises);
374
413
  }
375
414
  // Deep-copy the given value, including dup()ing all stubs.
376
415
  //
@@ -392,8 +431,8 @@ var RpcPayload = class _RpcPayload {
392
431
  return result;
393
432
  }
394
433
  // For `source === "return"` payloads only, this tracks any StubHooks created around RpcTargets
395
- // found in the payload at the time that it is serialized (or deep-copied) for return, so that we
396
- // can make sure they are not disposed before the pipeline ends.
434
+ // or WritableStreams found in the payload at the time that it is serialized (or deep-copied) for
435
+ // return, so that we can make sure they are not disposed before the pipeline ends.
397
436
  //
398
437
  // This is initialized on first use.
399
438
  rpcTargets;
@@ -432,6 +471,64 @@ var RpcPayload = class _RpcPayload {
432
471
  throw new Error("owned payload shouldn't contain raw RpcTargets");
433
472
  }
434
473
  }
474
+ // Get the StubHook representing the given WritableStream found inside this payload.
475
+ getHookForWritableStream(stream, parent, dupStubs = true) {
476
+ if (this.source === "params") {
477
+ return streamImpl.createWritableStreamHook(stream);
478
+ } else if (this.source === "return") {
479
+ let hook = this.rpcTargets?.get(stream);
480
+ if (hook) {
481
+ if (dupStubs) {
482
+ return hook.dup();
483
+ } else {
484
+ this.rpcTargets?.delete(stream);
485
+ return hook;
486
+ }
487
+ } else {
488
+ hook = streamImpl.createWritableStreamHook(stream);
489
+ if (dupStubs) {
490
+ if (!this.rpcTargets) {
491
+ this.rpcTargets = /* @__PURE__ */ new Map();
492
+ }
493
+ this.rpcTargets.set(stream, hook);
494
+ return hook.dup();
495
+ } else {
496
+ return hook;
497
+ }
498
+ }
499
+ } else {
500
+ throw new Error("owned payload shouldn't contain raw WritableStreams");
501
+ }
502
+ }
503
+ // Get the StubHook representing the given ReadableStream found inside this payload.
504
+ getHookForReadableStream(stream, parent, dupStubs = true) {
505
+ if (this.source === "params") {
506
+ return streamImpl.createReadableStreamHook(stream);
507
+ } else if (this.source === "return") {
508
+ let hook = this.rpcTargets?.get(stream);
509
+ if (hook) {
510
+ if (dupStubs) {
511
+ return hook.dup();
512
+ } else {
513
+ this.rpcTargets?.delete(stream);
514
+ return hook;
515
+ }
516
+ } else {
517
+ hook = streamImpl.createReadableStreamHook(stream);
518
+ if (dupStubs) {
519
+ if (!this.rpcTargets) {
520
+ this.rpcTargets = /* @__PURE__ */ new Map();
521
+ }
522
+ this.rpcTargets.set(stream, hook);
523
+ return hook.dup();
524
+ } else {
525
+ return hook;
526
+ }
527
+ }
528
+ } else {
529
+ throw new Error("owned payload shouldn't contain raw ReadableStreams");
530
+ }
531
+ }
435
532
  deepCopy(value, oldParent, property, parent, dupStubs, owner) {
436
533
  let kind = typeForRpc(value);
437
534
  switch (kind) {
@@ -475,22 +572,21 @@ var RpcPayload = class _RpcPayload {
475
572
  this.promises.push({ parent, property, promise });
476
573
  return promise;
477
574
  } else {
478
- let newStub = new RpcStub(hook);
479
- this.stubs.push(newStub);
480
- return newStub;
575
+ this.hooks.push(hook);
576
+ return new RpcStub(hook);
481
577
  }
482
578
  }
483
579
  case "function":
484
580
  case "rpc-target": {
485
581
  let target = value;
486
- let stub;
582
+ let hook;
487
583
  if (owner) {
488
- stub = new RpcStub(owner.getHookForRpcTarget(target, oldParent, dupStubs));
584
+ hook = owner.getHookForRpcTarget(target, oldParent, dupStubs);
489
585
  } else {
490
- stub = new RpcStub(TargetStubHook.create(target, oldParent));
586
+ hook = TargetStubHook.create(target, oldParent);
491
587
  }
492
- this.stubs.push(stub);
493
- return stub;
588
+ this.hooks.push(hook);
589
+ return new RpcStub(hook);
494
590
  }
495
591
  case "rpc-thenable": {
496
592
  let target = value;
@@ -503,6 +599,44 @@ var RpcPayload = class _RpcPayload {
503
599
  this.promises.push({ parent, property, promise });
504
600
  return promise;
505
601
  }
602
+ case "writable": {
603
+ let stream = value;
604
+ let hook;
605
+ if (owner) {
606
+ hook = owner.getHookForWritableStream(stream, oldParent, dupStubs);
607
+ } else {
608
+ hook = streamImpl.createWritableStreamHook(stream);
609
+ }
610
+ this.hooks.push(hook);
611
+ return stream;
612
+ }
613
+ case "readable": {
614
+ let stream = value;
615
+ let hook;
616
+ if (owner) {
617
+ hook = owner.getHookForReadableStream(stream, oldParent, dupStubs);
618
+ } else {
619
+ hook = streamImpl.createReadableStreamHook(stream);
620
+ }
621
+ this.hooks.push(hook);
622
+ return stream;
623
+ }
624
+ case "headers":
625
+ return new Headers(value);
626
+ case "request": {
627
+ let req = value;
628
+ if (req.body) {
629
+ this.deepCopy(req.body, req, "body", req, dupStubs, owner);
630
+ }
631
+ return new Request(req);
632
+ }
633
+ case "response": {
634
+ let resp = value;
635
+ if (resp.body) {
636
+ this.deepCopy(resp.body, resp, "body", resp, dupStubs, owner);
637
+ }
638
+ return new Response(resp.body, resp);
639
+ }
506
640
  default:
507
641
  throw new Error("unreachable");
508
642
  }
@@ -512,12 +646,12 @@ var RpcPayload = class _RpcPayload {
512
646
  ensureDeepCopied() {
513
647
  if (this.source !== "owned") {
514
648
  let dupStubs = this.source === "params";
515
- this.stubs = [];
649
+ this.hooks = [];
516
650
  this.promises = [];
517
651
  try {
518
652
  this.value = this.deepCopy(this.value, void 0, "value", this, dupStubs, this);
519
653
  } catch (err) {
520
- this.stubs = void 0;
654
+ this.hooks = void 0;
521
655
  this.promises = void 0;
522
656
  throw err;
523
657
  }
@@ -620,7 +754,7 @@ var RpcPayload = class _RpcPayload {
620
754
  }
621
755
  dispose() {
622
756
  if (this.source === "owned") {
623
- this.stubs.forEach((stub) => stub[Symbol.dispose]());
757
+ this.hooks.forEach((hook) => hook.dispose());
624
758
  this.promises.forEach((promise) => promise.promise[Symbol.dispose]());
625
759
  } else if (this.source === "return") {
626
760
  this.disposeImpl(this.value, void 0);
@@ -629,7 +763,7 @@ var RpcPayload = class _RpcPayload {
629
763
  }
630
764
  } else ;
631
765
  this.source = "owned";
632
- this.stubs = [];
766
+ this.hooks = [];
633
767
  this.promises = [];
634
768
  }
635
769
  // Recursive dispose, called only when `source` is "return".
@@ -682,6 +816,40 @@ var RpcPayload = class _RpcPayload {
682
816
  }
683
817
  case "rpc-thenable":
684
818
  return;
819
+ case "headers":
820
+ return;
821
+ case "request": {
822
+ let req = value;
823
+ if (req.body) this.disposeImpl(req.body, req);
824
+ return;
825
+ }
826
+ case "response": {
827
+ let resp = value;
828
+ if (resp.body) this.disposeImpl(resp.body, resp);
829
+ return;
830
+ }
831
+ case "writable": {
832
+ let stream = value;
833
+ let hook = this.rpcTargets?.get(stream);
834
+ if (hook) {
835
+ this.rpcTargets.delete(stream);
836
+ } else {
837
+ hook = streamImpl.createWritableStreamHook(stream);
838
+ }
839
+ hook.dispose();
840
+ return;
841
+ }
842
+ case "readable": {
843
+ let stream = value;
844
+ let hook = this.rpcTargets?.get(stream);
845
+ if (hook) {
846
+ this.rpcTargets.delete(stream);
847
+ } else {
848
+ hook = streamImpl.createReadableStreamHook(stream);
849
+ }
850
+ hook.dispose();
851
+ return;
852
+ }
685
853
  default:
686
854
  return;
687
855
  }
@@ -690,9 +858,9 @@ var RpcPayload = class _RpcPayload {
690
858
  // *would* be awaited if this payload were to be delivered. See the similarly-named method of
691
859
  // StubHook for explanation.
692
860
  ignoreUnhandledRejections() {
693
- if (this.stubs) {
694
- this.stubs.forEach((stub) => {
695
- unwrapStubOrParent(stub).ignoreUnhandledRejections();
861
+ if (this.hooks) {
862
+ this.hooks.forEach((hook) => {
863
+ hook.ignoreUnhandledRejections();
696
864
  });
697
865
  this.promises.forEach(
698
866
  (promise) => unwrapStubOrParent(promise.promise).ignoreUnhandledRejections()
@@ -713,6 +881,11 @@ var RpcPayload = class _RpcPayload {
713
881
  case "undefined":
714
882
  case "function":
715
883
  case "rpc-target":
884
+ case "writable":
885
+ case "readable":
886
+ case "headers":
887
+ case "request":
888
+ case "response":
716
889
  return;
717
890
  case "array": {
718
891
  let array = value;
@@ -785,11 +958,20 @@ function followPath(value, parent, path, owner) {
785
958
  let { hook, pathIfPromise } = unwrapStubAndPath(value);
786
959
  return { hook, remainingPath: pathIfPromise ? pathIfPromise.concat(path.slice(i)) : path.slice(i) };
787
960
  }
961
+ case "writable":
962
+ value = void 0;
963
+ break;
964
+ case "readable":
965
+ value = void 0;
966
+ break;
788
967
  case "primitive":
789
968
  case "bigint":
790
969
  case "bytes":
791
970
  case "date":
792
971
  case "error":
972
+ case "headers":
973
+ case "request":
974
+ case "response":
793
975
  value = void 0;
794
976
  break;
795
977
  case "undefined":
@@ -1024,6 +1206,14 @@ var PromiseStubHook = class _PromiseStubHook extends StubHook {
1024
1206
  args.ensureDeepCopied();
1025
1207
  return new _PromiseStubHook(this.promise.then((hook) => hook.call(path, args)));
1026
1208
  }
1209
+ stream(path, args) {
1210
+ args.ensureDeepCopied();
1211
+ let promise = this.promise.then((hook) => {
1212
+ let result = hook.stream(path, args);
1213
+ return result.promise;
1214
+ });
1215
+ return { promise };
1216
+ }
1027
1217
  map(path, captures, instructions) {
1028
1218
  return new _PromiseStubHook(this.promise.then(
1029
1219
  (hook) => hook.map(path, captures, instructions),
@@ -1096,6 +1286,9 @@ var NullExporter = class {
1096
1286
  }
1097
1287
  unexport(ids) {
1098
1288
  }
1289
+ createPipe(readable) {
1290
+ throw new Error("Cannot create pipes without an RPC session.");
1291
+ }
1099
1292
  onSendError(error) {
1100
1293
  }
1101
1294
  };
@@ -1193,12 +1386,86 @@ var Devaluator = class _Devaluator {
1193
1386
  let bytes = value;
1194
1387
  if (bytes.toBase64) {
1195
1388
  return ["bytes", bytes.toBase64({ omitPadding: true })];
1389
+ }
1390
+ let b64;
1391
+ if (typeof Buffer !== "undefined") {
1392
+ let buf = bytes instanceof Buffer ? bytes : Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
1393
+ b64 = buf.toString("base64");
1196
1394
  } else {
1197
- return [
1198
- "bytes",
1199
- btoa(String.fromCharCode.apply(null, bytes).replace(/=*$/, ""))
1200
- ];
1395
+ let binary = "";
1396
+ for (let i = 0; i < bytes.length; i++) {
1397
+ binary += String.fromCharCode(bytes[i]);
1398
+ }
1399
+ b64 = btoa(binary);
1400
+ }
1401
+ return ["bytes", b64.replace(/=+$/, "")];
1402
+ }
1403
+ case "headers":
1404
+ return ["headers", [...value]];
1405
+ case "request": {
1406
+ let req = value;
1407
+ let init = {};
1408
+ if (req.method !== "GET") init.method = req.method;
1409
+ let headers = [...req.headers];
1410
+ if (headers.length > 0) {
1411
+ init.headers = headers;
1412
+ }
1413
+ if (req.body) {
1414
+ init.body = this.devaluateImpl(req.body, req, depth + 1);
1415
+ init.duplex = req.duplex || "half";
1416
+ } else if (req.body === void 0 && !["GET", "HEAD", "OPTIONS", "TRACE", "DELETE"].includes(req.method)) {
1417
+ let bodyPromise = req.arrayBuffer();
1418
+ let readable = new ReadableStream({
1419
+ async start(controller) {
1420
+ try {
1421
+ controller.enqueue(new Uint8Array(await bodyPromise));
1422
+ controller.close();
1423
+ } catch (err) {
1424
+ controller.error(err);
1425
+ }
1426
+ }
1427
+ });
1428
+ let hook = streamImpl.createReadableStreamHook(readable);
1429
+ let importId = this.exporter.createPipe(readable, hook);
1430
+ init.body = ["readable", importId];
1431
+ init.duplex = req.duplex || "half";
1201
1432
  }
1433
+ if (req.cache && req.cache !== "default") init.cache = req.cache;
1434
+ if (req.redirect !== "follow") init.redirect = req.redirect;
1435
+ if (req.integrity) init.integrity = req.integrity;
1436
+ if (req.mode && req.mode !== "cors") init.mode = req.mode;
1437
+ if (req.credentials && req.credentials !== "same-origin") {
1438
+ init.credentials = req.credentials;
1439
+ }
1440
+ if (req.referrer && req.referrer !== "about:client") init.referrer = req.referrer;
1441
+ if (req.referrerPolicy) init.referrerPolicy = req.referrerPolicy;
1442
+ if (req.keepalive) init.keepalive = req.keepalive;
1443
+ let cfReq = req;
1444
+ if (cfReq.cf) init.cf = cfReq.cf;
1445
+ if (cfReq.encodeResponseBody && cfReq.encodeResponseBody !== "automatic") {
1446
+ init.encodeResponseBody = cfReq.encodeResponseBody;
1447
+ }
1448
+ return ["request", req.url, init];
1449
+ }
1450
+ case "response": {
1451
+ let resp = value;
1452
+ let body = this.devaluateImpl(resp.body, resp, depth + 1);
1453
+ let init = {};
1454
+ if (resp.status !== 200) init.status = resp.status;
1455
+ if (resp.statusText) init.statusText = resp.statusText;
1456
+ let headers = [...resp.headers];
1457
+ if (headers.length > 0) {
1458
+ init.headers = headers;
1459
+ }
1460
+ let cfResp = resp;
1461
+ if (cfResp.cf) init.cf = cfResp.cf;
1462
+ if (cfResp.encodeBody && cfResp.encodeBody !== "automatic") {
1463
+ init.encodeBody = cfResp.encodeBody;
1464
+ }
1465
+ if (cfResp.webSocket) {
1466
+ throw new TypeError("Can't serialize a Response containing a webSocket.");
1467
+ }
1468
+ return ["response", body, init];
1202
1469
  }
1203
1470
  case "error": {
1204
1471
  let e = value;
@@ -1254,6 +1521,22 @@ var Devaluator = class _Devaluator {
1254
1521
  let hook = this.source.getHookForRpcTarget(value, parent);
1255
1522
  return this.devaluateHook("promise", hook);
1256
1523
  }
1524
+ case "writable": {
1525
+ if (!this.source) {
1526
+ throw new Error("Can't serialize WritableStream in this context.");
1527
+ }
1528
+ let hook = this.source.getHookForWritableStream(value, parent);
1529
+ return this.devaluateHook("writable", hook);
1530
+ }
1531
+ case "readable": {
1532
+ if (!this.source) {
1533
+ throw new Error("Can't serialize ReadableStream in this context.");
1534
+ }
1535
+ let ws = value;
1536
+ let hook = this.source.getHookForReadableStream(ws, parent);
1537
+ let importId = this.exporter.createPipe(ws, hook);
1538
+ return ["readable", importId];
1539
+ }
1257
1540
  default:
1258
1541
  throw new Error("unreachable");
1259
1542
  }
@@ -1278,16 +1561,27 @@ var NullImporter = class {
1278
1561
  getExport(idx) {
1279
1562
  return void 0;
1280
1563
  }
1564
+ getPipeReadable(exportId) {
1565
+ throw new Error("Cannot retrieve pipe readable without an RPC session.");
1566
+ }
1281
1567
  };
1282
1568
  var NULL_IMPORTER = new NullImporter();
1569
+ function fixBrokenRequestBody(request, body) {
1570
+ let promise = new Response(body).arrayBuffer().then((arrayBuffer) => {
1571
+ let bytes = new Uint8Array(arrayBuffer);
1572
+ let result = new Request(request, { body: bytes });
1573
+ return new PayloadStubHook(RpcPayload.fromAppReturn(result));
1574
+ });
1575
+ return new RpcPromise(new PromiseStubHook(promise), []);
1576
+ }
1283
1577
  var Evaluator = class _Evaluator {
1284
1578
  constructor(importer) {
1285
1579
  this.importer = importer;
1286
1580
  }
1287
- stubs = [];
1581
+ hooks = [];
1288
1582
  promises = [];
1289
1583
  evaluate(value) {
1290
- let payload = RpcPayload.forEvaluate(this.stubs, this.promises);
1584
+ let payload = RpcPayload.forEvaluate(this.hooks, this.promises);
1291
1585
  try {
1292
1586
  payload.value = this.evaluateImpl(value, payload, "value");
1293
1587
  return payload;
@@ -1320,10 +1614,11 @@ var Evaluator = class _Evaluator {
1320
1614
  }
1321
1615
  break;
1322
1616
  case "bytes": {
1323
- let b64 = Uint8Array;
1324
1617
  if (typeof value[1] == "string") {
1325
- if (b64.fromBase64) {
1326
- return b64.fromBase64(value[1]);
1618
+ if (typeof Buffer !== "undefined") {
1619
+ return Buffer.from(value[1], "base64");
1620
+ } else if (Uint8Array.fromBase64) {
1621
+ return Uint8Array.fromBase64(value[1]);
1327
1622
  } else {
1328
1623
  let bs = atob(value[1]);
1329
1624
  let len = bs.length;
@@ -1357,6 +1652,56 @@ var Evaluator = class _Evaluator {
1357
1652
  return -Infinity;
1358
1653
  case "nan":
1359
1654
  return NaN;
1655
+ case "headers":
1656
+ if (value.length === 2 && value[1] instanceof Array) {
1657
+ return new Headers(value[1]);
1658
+ }
1659
+ break;
1660
+ case "request": {
1661
+ if (value.length !== 3 || typeof value[1] !== "string") break;
1662
+ let url = value[1];
1663
+ let init = value[2];
1664
+ if (typeof init !== "object" || init === null) break;
1665
+ if (init.body) {
1666
+ init.body = this.evaluateImpl(init.body, init, "body");
1667
+ if (init.body === null || typeof init.body === "string" || init.body instanceof Uint8Array || init.body instanceof ReadableStream) ; else {
1668
+ throw new TypeError("Request body must be of type ReadableStream.");
1669
+ }
1670
+ }
1671
+ if (init.signal) {
1672
+ init.signal = this.evaluateImpl(init.signal, init, "signal");
1673
+ if (!(init.signal instanceof AbortSignal)) {
1674
+ throw new TypeError("Request siganl must be of type AbortSignal.");
1675
+ }
1676
+ }
1677
+ if (init.headers && !(init.headers instanceof Array)) {
1678
+ throw new TypeError("Request headers must be serialized as an array of pairs.");
1679
+ }
1680
+ let result = new Request(url, init);
1681
+ if (init.body instanceof ReadableStream && result.body === void 0) {
1682
+ let promise = fixBrokenRequestBody(result, init.body);
1683
+ this.promises.push({ promise, parent, property });
1684
+ return promise;
1685
+ } else {
1686
+ return result;
1687
+ }
1688
+ }
1689
+ case "response": {
1690
+ if (value.length !== 3) break;
1691
+ let body = this.evaluateImpl(value[1], parent, property);
1692
+ if (body === null || typeof body === "string" || body instanceof Uint8Array || body instanceof ReadableStream) ; else {
1693
+ throw new TypeError("Response body must be of type ReadableStream.");
1694
+ }
1695
+ let init = value[2];
1696
+ if (typeof init !== "object" || init === null) break;
1697
+ if (init.webSocket) {
1698
+ throw new TypeError("Can't deserialize a Response containing a webSocket.");
1699
+ }
1700
+ if (init.headers && !(init.headers instanceof Array)) {
1701
+ throw new TypeError("Request headers must be serialized as an array of pairs.");
1702
+ }
1703
+ return new Response(body, init);
1704
+ }
1360
1705
  case "import":
1361
1706
  case "pipeline": {
1362
1707
  if (value.length < 2 || value.length > 4) {
@@ -1376,9 +1721,8 @@ var Evaluator = class _Evaluator {
1376
1721
  this.promises.push({ promise, parent, property });
1377
1722
  return promise;
1378
1723
  } else {
1379
- let stub = new RpcPromise(hook2, []);
1380
- this.stubs.push(stub);
1381
- return stub;
1724
+ this.hooks.push(hook2);
1725
+ return new RpcPromise(hook2, []);
1382
1726
  }
1383
1727
  };
1384
1728
  if (value.length == 2) {
@@ -1456,12 +1800,27 @@ var Evaluator = class _Evaluator {
1456
1800
  return promise;
1457
1801
  } else {
1458
1802
  let hook = this.importer.importStub(value[1]);
1459
- let stub = new RpcStub(hook);
1460
- this.stubs.push(stub);
1461
- return stub;
1803
+ this.hooks.push(hook);
1804
+ return new RpcStub(hook);
1462
1805
  }
1463
1806
  }
1464
1807
  break;
1808
+ case "writable":
1809
+ if (typeof value[1] == "number") {
1810
+ let hook = this.importer.importStub(value[1]);
1811
+ let stream = streamImpl.createWritableStreamFromHook(hook);
1812
+ this.hooks.push(hook);
1813
+ return stream;
1814
+ }
1815
+ break;
1816
+ case "readable":
1817
+ if (typeof value[1] == "number") {
1818
+ let stream = this.importer.getPipeReadable(value[1]);
1819
+ let hook = streamImpl.createReadableStreamHook(stream);
1820
+ this.hooks.push(hook);
1821
+ return stream;
1822
+ }
1823
+ break;
1465
1824
  }
1466
1825
  throw new TypeError(`unknown special value: ${JSON.stringify(value)}`);
1467
1826
  } else if (value instanceof Object) {
@@ -1601,6 +1960,14 @@ var RpcImportHook = class _RpcImportHook extends StubHook {
1601
1960
  return entry.session.sendCall(entry.importId, path, args);
1602
1961
  }
1603
1962
  }
1963
+ stream(path, args) {
1964
+ let entry = this.getEntry();
1965
+ if (entry.resolution) {
1966
+ return entry.resolution.stream(path, args);
1967
+ } else {
1968
+ return entry.session.sendStream(entry.importId, path, args);
1969
+ }
1970
+ }
1604
1971
  map(path, captures, instructions) {
1605
1972
  let entry;
1606
1973
  try {
@@ -1773,19 +2140,23 @@ var RpcSessionImpl = class {
1773
2140
  return payload;
1774
2141
  }
1775
2142
  };
2143
+ let autoRelease = exp.autoRelease;
1776
2144
  ++this.pullCount;
1777
2145
  exp.pull = resolve().then(
1778
2146
  (payload) => {
1779
2147
  let value = Devaluator.devaluate(payload.value, void 0, this, payload);
1780
2148
  this.send(["resolve", exportId, value]);
2149
+ if (autoRelease) this.releaseExport(exportId, 1);
1781
2150
  },
1782
2151
  (error) => {
1783
2152
  this.send(["reject", exportId, Devaluator.devaluate(error, void 0, this)]);
2153
+ if (autoRelease) this.releaseExport(exportId, 1);
1784
2154
  }
1785
2155
  ).catch(
1786
2156
  (error) => {
1787
2157
  try {
1788
2158
  this.send(["reject", exportId, Devaluator.devaluate(error, void 0, this)]);
2159
+ if (autoRelease) this.releaseExport(exportId, 1);
1789
2160
  } catch (error2) {
1790
2161
  this.abort(error2);
1791
2162
  }
@@ -1837,9 +2208,35 @@ var RpcSessionImpl = class {
1837
2208
  getExport(idx) {
1838
2209
  return this.exports[idx]?.hook;
1839
2210
  }
2211
+ getPipeReadable(exportId) {
2212
+ let entry = this.exports[exportId];
2213
+ if (!entry || !entry.pipeReadable) {
2214
+ throw new Error(`Export ${exportId} is not a pipe or its readable end was already consumed.`);
2215
+ }
2216
+ let readable = entry.pipeReadable;
2217
+ entry.pipeReadable = void 0;
2218
+ return readable;
2219
+ }
2220
+ createPipe(readable, readableHook) {
2221
+ if (this.abortReason) throw this.abortReason;
2222
+ this.send(["pipe"]);
2223
+ let importId = this.imports.length;
2224
+ let entry = new ImportTableEntry(this, importId, false);
2225
+ this.imports.push(entry);
2226
+ let hook = new RpcImportHook(
2227
+ /*isPromise=*/
2228
+ false,
2229
+ entry
2230
+ );
2231
+ let writable = streamImpl.createWritableStreamFromHook(hook);
2232
+ readable.pipeTo(writable).catch(() => {
2233
+ }).finally(() => readableHook.dispose());
2234
+ return importId;
2235
+ }
2236
+ // Serializes and sends a message. Returns the byte length of the serialized message.
1840
2237
  send(msg) {
1841
2238
  if (this.abortReason !== void 0) {
1842
- return;
2239
+ return 0;
1843
2240
  }
1844
2241
  let msgText;
1845
2242
  try {
@@ -1852,6 +2249,7 @@ var RpcSessionImpl = class {
1852
2249
  throw err;
1853
2250
  }
1854
2251
  this.transport.send(msgText).catch((err) => this.abort(err, false));
2252
+ return msgText.length;
1855
2253
  }
1856
2254
  sendCall(id, path, args) {
1857
2255
  if (this.abortReason) throw this.abortReason;
@@ -1869,6 +2267,34 @@ var RpcSessionImpl = class {
1869
2267
  entry
1870
2268
  );
1871
2269
  }
2270
+ sendStream(id, path, args) {
2271
+ if (this.abortReason) throw this.abortReason;
2272
+ let value = ["pipeline", id, path];
2273
+ let devalue = Devaluator.devaluate(args.value, void 0, this, args);
2274
+ value.push(devalue[0]);
2275
+ let size = this.send(["stream", value]);
2276
+ let importId = this.imports.length;
2277
+ let entry = new ImportTableEntry(
2278
+ this,
2279
+ importId,
2280
+ /*pulling=*/
2281
+ true
2282
+ );
2283
+ entry.remoteRefcount = 0;
2284
+ entry.localRefcount = 1;
2285
+ this.imports.push(entry);
2286
+ let promise = entry.awaitResolution().then(
2287
+ (p) => {
2288
+ p.dispose();
2289
+ delete this.imports[importId];
2290
+ },
2291
+ (err) => {
2292
+ delete this.imports[importId];
2293
+ throw err;
2294
+ }
2295
+ );
2296
+ return { promise, size };
2297
+ }
1872
2298
  sendMap(id, path, captures, instructions) {
1873
2299
  if (this.abortReason) {
1874
2300
  for (let cap of captures) {
@@ -1956,6 +2382,24 @@ var RpcSessionImpl = class {
1956
2382
  continue;
1957
2383
  }
1958
2384
  break;
2385
+ case "stream": {
2386
+ if (msg.length > 1) {
2387
+ let payload = new Evaluator(this).evaluate(msg[1]);
2388
+ let hook = new PayloadStubHook(payload);
2389
+ hook.ignoreUnhandledRejections();
2390
+ let exportId = this.exports.length;
2391
+ this.exports.push({ hook, refcount: 1, autoRelease: true });
2392
+ this.ensureResolvingExport(exportId);
2393
+ continue;
2394
+ }
2395
+ break;
2396
+ }
2397
+ case "pipe": {
2398
+ let { readable, writable } = new TransformStream();
2399
+ let hook = streamImpl.createWritableStreamHook(writable);
2400
+ this.exports.push({ hook, refcount: 1, pipeReadable: readable });
2401
+ continue;
2402
+ }
1959
2403
  case "pull": {
1960
2404
  let exportId = msg[1];
1961
2405
  if (typeof exportId == "number") {
@@ -2451,6 +2895,9 @@ var MapBuilder = class {
2451
2895
  }
2452
2896
  unexport(ids) {
2453
2897
  }
2898
+ createPipe(readable) {
2899
+ throw new Error("Cannot send ReadableStream inside a mapper function.");
2900
+ }
2454
2901
  onSendError(error) {
2455
2902
  }
2456
2903
  };
@@ -2560,6 +3007,9 @@ var MapApplicator = class {
2560
3007
  return this.variables[idx];
2561
3008
  }
2562
3009
  }
3010
+ getPipeReadable(exportId) {
3011
+ throw new Error("A mapper function cannot use pipe readables.");
3012
+ }
2563
3013
  };
2564
3014
  function applyMapToElement(input, parent, owner, captures, instructions) {
2565
3015
  let inputHook = new PayloadStubHook(RpcPayload.deepCopyFrom(input, parent, owner));
@@ -2600,6 +3050,333 @@ mapImpl.applyMap = (input, parent, owner, captures, instructions) => {
2600
3050
  }
2601
3051
  }
2602
3052
  };
3053
+
3054
+ // src/streams.ts
3055
+ var WritableStreamStubHook = class _WritableStreamStubHook extends StubHook {
3056
+ state;
3057
+ // undefined when disposed
3058
+ // Creates a new WritableStreamStubHook that is not duplicated from an existing hook.
3059
+ static create(stream) {
3060
+ let writer = stream.getWriter();
3061
+ return new _WritableStreamStubHook({ refcount: 1, writer, closed: false });
3062
+ }
3063
+ constructor(state, dupFrom) {
3064
+ super();
3065
+ this.state = state;
3066
+ if (dupFrom) {
3067
+ ++state.refcount;
3068
+ }
3069
+ }
3070
+ getState() {
3071
+ if (this.state) {
3072
+ return this.state;
3073
+ } else {
3074
+ throw new Error("Attempted to use a WritableStreamStubHook after it was disposed.");
3075
+ }
3076
+ }
3077
+ call(path, args) {
3078
+ try {
3079
+ let state = this.getState();
3080
+ if (path.length !== 1 || typeof path[0] !== "string") {
3081
+ throw new Error("WritableStream stub only supports direct method calls");
3082
+ }
3083
+ const method = path[0];
3084
+ if (method !== "write" && method !== "close" && method !== "abort") {
3085
+ args.dispose();
3086
+ throw new Error(`Unknown WritableStream method: ${method}`);
3087
+ }
3088
+ if (method === "close" || method === "abort") {
3089
+ state.closed = true;
3090
+ }
3091
+ let func = state.writer[method];
3092
+ let promise = args.deliverCall(func, state.writer);
3093
+ return new PromiseStubHook(promise.then((payload) => new PayloadStubHook(payload)));
3094
+ } catch (err) {
3095
+ return new ErrorStubHook(err);
3096
+ }
3097
+ }
3098
+ map(path, captures, instructions) {
3099
+ for (let cap of captures) {
3100
+ cap.dispose();
3101
+ }
3102
+ return new ErrorStubHook(new Error("Cannot use map() on a WritableStream"));
3103
+ }
3104
+ get(path) {
3105
+ return new ErrorStubHook(new Error("Cannot access properties on a WritableStream stub"));
3106
+ }
3107
+ dup() {
3108
+ let state = this.getState();
3109
+ return new _WritableStreamStubHook(state, this);
3110
+ }
3111
+ pull() {
3112
+ return Promise.reject(new Error("Cannot pull a WritableStream stub"));
3113
+ }
3114
+ ignoreUnhandledRejections() {
3115
+ }
3116
+ dispose() {
3117
+ let state = this.state;
3118
+ this.state = void 0;
3119
+ if (state) {
3120
+ if (--state.refcount === 0) {
3121
+ if (!state.closed) {
3122
+ state.writer.abort(new Error("WritableStream RPC stub was disposed without calling close()")).catch(() => {
3123
+ });
3124
+ }
3125
+ state.writer.releaseLock();
3126
+ }
3127
+ }
3128
+ }
3129
+ onBroken(callback) {
3130
+ }
3131
+ };
3132
+ var INITIAL_WINDOW = 256 * 1024;
3133
+ var MAX_WINDOW = 1024 * 1024 * 1024;
3134
+ var MIN_WINDOW = 64 * 1024;
3135
+ var STARTUP_GROWTH_FACTOR = 2;
3136
+ var STEADY_GROWTH_FACTOR = 1.25;
3137
+ var DECAY_FACTOR = 0.9;
3138
+ var STARTUP_EXIT_ROUNDS = 3;
3139
+ var FlowController = class {
3140
+ constructor(now) {
3141
+ this.now = now;
3142
+ }
3143
+ // The current window size in bytes. The sender blocks when bytesInFlight >= window.
3144
+ window = INITIAL_WINDOW;
3145
+ // Total bytes currently in flight (sent but not yet acked).
3146
+ bytesInFlight = 0;
3147
+ // Whether we're still in the startup phase.
3148
+ inStartupPhase = true;
3149
+ // ----- BDP estimation state (private) -----
3150
+ // Total bytes acked so far.
3151
+ delivered = 0;
3152
+ // Time of most recent ack.
3153
+ deliveredTime = 0;
3154
+ // Time when the very first ack was received.
3155
+ firstAckTime = 0;
3156
+ firstAckDelivered = 0;
3157
+ // Global minimum RTT observed (milliseconds).
3158
+ minRtt = Infinity;
3159
+ // For startup exit: count of consecutive RTT rounds where the window didn't meaningfully grow.
3160
+ roundsWithoutIncrease = 0;
3161
+ // Window size at the start of the current round, for startup exit detection.
3162
+ lastRoundWindow = 0;
3163
+ // Time when the current round started.
3164
+ roundStartTime = 0;
3165
+ // Called when a write of `size` bytes is about to be sent. Returns a token that must be
3166
+ // passed to onAck() when the ack arrives, and whether the sender should block (window full).
3167
+ onSend(size) {
3168
+ this.bytesInFlight += size;
3169
+ let token = {
3170
+ sentTime: this.now(),
3171
+ size,
3172
+ deliveredAtSend: this.delivered,
3173
+ deliveredTimeAtSend: this.deliveredTime,
3174
+ windowAtSend: this.window,
3175
+ windowFullAtSend: this.bytesInFlight >= this.window
3176
+ };
3177
+ return { token, shouldBlock: token.windowFullAtSend };
3178
+ }
3179
+ // Called when a previously-sent write fails. Restores bytesInFlight without updating
3180
+ // any BDP estimates.
3181
+ onError(token) {
3182
+ this.bytesInFlight -= token.size;
3183
+ }
3184
+ // Called when an ack is received for a previously-sent write. Updates BDP estimates and
3185
+ // the window. Returns whether a blocked sender should now unblock.
3186
+ onAck(token) {
3187
+ let ackTime = this.now();
3188
+ this.delivered += token.size;
3189
+ this.deliveredTime = ackTime;
3190
+ this.bytesInFlight -= token.size;
3191
+ let rtt = ackTime - token.sentTime;
3192
+ this.minRtt = Math.min(this.minRtt, rtt);
3193
+ if (this.firstAckTime === 0) {
3194
+ this.firstAckTime = ackTime;
3195
+ this.firstAckDelivered = this.delivered;
3196
+ } else {
3197
+ let baseTime;
3198
+ let baseDelivered;
3199
+ if (token.deliveredTimeAtSend === 0) {
3200
+ baseTime = this.firstAckTime;
3201
+ baseDelivered = this.firstAckDelivered;
3202
+ } else {
3203
+ baseTime = token.deliveredTimeAtSend;
3204
+ baseDelivered = token.deliveredAtSend;
3205
+ }
3206
+ let interval = ackTime - baseTime;
3207
+ let bytes = this.delivered - baseDelivered;
3208
+ let bandwidth = bytes / interval;
3209
+ let growthFactor = this.inStartupPhase ? STARTUP_GROWTH_FACTOR : STEADY_GROWTH_FACTOR;
3210
+ let newWindow = bandwidth * this.minRtt * growthFactor;
3211
+ newWindow = Math.min(newWindow, token.windowAtSend * growthFactor);
3212
+ if (token.windowFullAtSend) {
3213
+ newWindow = Math.max(newWindow, token.windowAtSend * DECAY_FACTOR);
3214
+ } else {
3215
+ newWindow = Math.max(newWindow, this.window);
3216
+ }
3217
+ this.window = Math.max(Math.min(newWindow, MAX_WINDOW), MIN_WINDOW);
3218
+ if (this.inStartupPhase && token.sentTime >= this.roundStartTime) {
3219
+ if (this.window > this.lastRoundWindow * STEADY_GROWTH_FACTOR) {
3220
+ this.roundsWithoutIncrease = 0;
3221
+ } else {
3222
+ if (++this.roundsWithoutIncrease >= STARTUP_EXIT_ROUNDS) {
3223
+ this.inStartupPhase = false;
3224
+ }
3225
+ }
3226
+ this.roundStartTime = ackTime;
3227
+ this.lastRoundWindow = this.window;
3228
+ }
3229
+ }
3230
+ return this.bytesInFlight < this.window;
3231
+ }
3232
+ };
3233
+ function createWritableStreamFromHook(hook) {
3234
+ let pendingError = void 0;
3235
+ let hookDisposed = false;
3236
+ let fc = new FlowController(() => performance.now());
3237
+ let windowResolve;
3238
+ let windowReject;
3239
+ const disposeHook = () => {
3240
+ if (!hookDisposed) {
3241
+ hookDisposed = true;
3242
+ hook.dispose();
3243
+ }
3244
+ };
3245
+ return new WritableStream({
3246
+ write(chunk, controller) {
3247
+ if (pendingError !== void 0) {
3248
+ throw pendingError;
3249
+ }
3250
+ const payload = RpcPayload.fromAppParams([chunk]);
3251
+ const { promise, size } = hook.stream(["write"], payload);
3252
+ if (size === void 0) {
3253
+ return promise.catch((err) => {
3254
+ if (pendingError === void 0) {
3255
+ pendingError = err;
3256
+ }
3257
+ throw err;
3258
+ });
3259
+ } else {
3260
+ let { token, shouldBlock } = fc.onSend(size);
3261
+ promise.then(() => {
3262
+ let hasCapacity = fc.onAck(token);
3263
+ if (hasCapacity && windowResolve) {
3264
+ windowResolve();
3265
+ windowResolve = void 0;
3266
+ windowReject = void 0;
3267
+ }
3268
+ }, (err) => {
3269
+ fc.onError(token);
3270
+ if (pendingError === void 0) {
3271
+ pendingError = err;
3272
+ controller.error(err);
3273
+ disposeHook();
3274
+ }
3275
+ if (windowReject) {
3276
+ windowReject(err);
3277
+ windowResolve = void 0;
3278
+ windowReject = void 0;
3279
+ }
3280
+ });
3281
+ if (shouldBlock) {
3282
+ return new Promise((resolve, reject) => {
3283
+ windowResolve = resolve;
3284
+ windowReject = reject;
3285
+ });
3286
+ }
3287
+ }
3288
+ },
3289
+ async close() {
3290
+ if (pendingError !== void 0) {
3291
+ disposeHook();
3292
+ throw pendingError;
3293
+ }
3294
+ const { promise } = hook.stream(["close"], RpcPayload.fromAppParams([]));
3295
+ try {
3296
+ await promise;
3297
+ } catch (err) {
3298
+ throw pendingError ?? err;
3299
+ } finally {
3300
+ disposeHook();
3301
+ }
3302
+ },
3303
+ abort(reason) {
3304
+ if (pendingError !== void 0) {
3305
+ return;
3306
+ }
3307
+ pendingError = reason ?? new Error("WritableStream was aborted");
3308
+ if (windowReject) {
3309
+ windowReject(pendingError);
3310
+ windowResolve = void 0;
3311
+ windowReject = void 0;
3312
+ }
3313
+ const { promise } = hook.stream(["abort"], RpcPayload.fromAppParams([reason]));
3314
+ promise.then(() => disposeHook(), () => disposeHook());
3315
+ }
3316
+ });
3317
+ }
3318
+ var ReadableStreamStubHook = class _ReadableStreamStubHook extends StubHook {
3319
+ state;
3320
+ // undefined when disposed
3321
+ // Creates a new ReadableStreamStubHook.
3322
+ static create(stream) {
3323
+ return new _ReadableStreamStubHook({ refcount: 1, stream, canceled: false });
3324
+ }
3325
+ constructor(state, dupFrom) {
3326
+ super();
3327
+ this.state = state;
3328
+ if (dupFrom) {
3329
+ ++state.refcount;
3330
+ }
3331
+ }
3332
+ call(path, args) {
3333
+ args.dispose();
3334
+ return new ErrorStubHook(new Error("Cannot call methods on a ReadableStream stub"));
3335
+ }
3336
+ map(path, captures, instructions) {
3337
+ for (let cap of captures) {
3338
+ cap.dispose();
3339
+ }
3340
+ return new ErrorStubHook(new Error("Cannot use map() on a ReadableStream"));
3341
+ }
3342
+ get(path) {
3343
+ return new ErrorStubHook(new Error("Cannot access properties on a ReadableStream stub"));
3344
+ }
3345
+ dup() {
3346
+ let state = this.state;
3347
+ if (!state) {
3348
+ throw new Error("Attempted to dup a ReadableStreamStubHook after it was disposed.");
3349
+ }
3350
+ return new _ReadableStreamStubHook(state, this);
3351
+ }
3352
+ pull() {
3353
+ return Promise.reject(new Error("Cannot pull a ReadableStream stub"));
3354
+ }
3355
+ ignoreUnhandledRejections() {
3356
+ }
3357
+ dispose() {
3358
+ let state = this.state;
3359
+ this.state = void 0;
3360
+ if (state) {
3361
+ if (--state.refcount === 0) {
3362
+ if (!state.canceled) {
3363
+ state.canceled = true;
3364
+ if (!state.stream.locked) {
3365
+ state.stream.cancel(
3366
+ new Error("ReadableStream RPC stub was disposed without being consumed")
3367
+ ).catch(() => {
3368
+ });
3369
+ }
3370
+ }
3371
+ }
3372
+ }
3373
+ }
3374
+ onBroken(callback) {
3375
+ }
3376
+ };
3377
+ streamImpl.createWritableStreamHook = WritableStreamStubHook.create;
3378
+ streamImpl.createWritableStreamFromHook = createWritableStreamFromHook;
3379
+ streamImpl.createReadableStreamHook = ReadableStreamStubHook.create;
2603
3380
  var RpcStub2 = RpcStub;
2604
3381
  var RpcPromise2 = RpcPromise;
2605
3382
  var RpcSession2 = RpcSession;