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