capnweb 0.3.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.
package/dist/index.cjs CHANGED
@@ -58,6 +58,16 @@ function typeForRpc(value) {
58
58
  return "date";
59
59
  case Uint8Array.prototype:
60
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";
61
71
  // TODO: All other structured clone types.
62
72
  case RpcStub.prototype:
63
73
  return "stub";
@@ -85,7 +95,34 @@ function mapNotLoaded() {
85
95
  throw new Error("RPC map() implementation was not loaded.");
86
96
  }
87
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
+ };
88
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
+ }
89
126
  };
90
127
  var ErrorStubHook = class extends StubHook {
91
128
  constructor(error) {
@@ -310,10 +347,10 @@ async function pullPromise(promise) {
310
347
  }
311
348
  var RpcPayload = class _RpcPayload {
312
349
  // Private constructor; use factory functions above to construct.
313
- constructor(value, source, stubs, promises) {
350
+ constructor(value, source, hooks, promises) {
314
351
  this.value = value;
315
352
  this.source = source;
316
- this.stubs = stubs;
353
+ this.hooks = hooks;
317
354
  this.promises = promises;
318
355
  }
319
356
  // Create a payload from a value passed as params to an RPC from the app.
@@ -338,13 +375,13 @@ var RpcPayload = class _RpcPayload {
338
375
  // stubs is transferred from the inputs to the outputs, hence if the output is disposed, the
339
376
  // inputs should not be. (In case of exception, nothing is disposed, though.)
340
377
  static fromArray(array) {
341
- let stubs = [];
378
+ let hooks = [];
342
379
  let promises = [];
343
380
  let resultArray = [];
344
381
  for (let payload of array) {
345
382
  payload.ensureDeepCopied();
346
- for (let stub of payload.stubs) {
347
- stubs.push(stub);
383
+ for (let hook of payload.hooks) {
384
+ hooks.push(hook);
348
385
  }
349
386
  for (let promise of payload.promises) {
350
387
  if (promise.parent === payload) {
@@ -358,12 +395,12 @@ var RpcPayload = class _RpcPayload {
358
395
  }
359
396
  resultArray.push(payload.value);
360
397
  }
361
- return new _RpcPayload(resultArray, "owned", stubs, promises);
398
+ return new _RpcPayload(resultArray, "owned", hooks, promises);
362
399
  }
363
400
  // Create a payload from a value parsed off the wire using Evaluator.evaluate().
364
401
  //
365
- // A payload is constructed with a null value and the given stubs and promises arrays. The value
366
- // 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
367
404
  // to be extended with stubs found during parsing. (This weird usage model is necessary so that
368
405
  // if the root value turns out to be a promise, its `parent` in `promises` can be the payload
369
406
  // object itself.)
@@ -371,8 +408,8 @@ var RpcPayload = class _RpcPayload {
371
408
  // When done, the payload takes ownership of the final value and all the stubs within. It may
372
409
  // modify the value in preparation for delivery, and may deliver the value directly to the app
373
410
  // without copying.
374
- static forEvaluate(stubs, promises) {
375
- return new _RpcPayload(null, "owned", stubs, promises);
411
+ static forEvaluate(hooks, promises) {
412
+ return new _RpcPayload(null, "owned", hooks, promises);
376
413
  }
377
414
  // Deep-copy the given value, including dup()ing all stubs.
378
415
  //
@@ -394,14 +431,20 @@ var RpcPayload = class _RpcPayload {
394
431
  return result;
395
432
  }
396
433
  // For `source === "return"` payloads only, this tracks any StubHooks created around RpcTargets
397
- // found in the payload at the time that it is serialized (or deep-copied) for return, so that we
398
- // 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.
399
436
  //
400
437
  // This is initialized on first use.
401
438
  rpcTargets;
402
439
  // Get the StubHook representing the given RpcTarget found inside this payload.
403
440
  getHookForRpcTarget(target, parent, dupStubs = true) {
404
441
  if (this.source === "params") {
442
+ if (dupStubs) {
443
+ let dupable = target;
444
+ if (typeof dupable.dup === "function") {
445
+ target = dupable.dup();
446
+ }
447
+ }
405
448
  return TargetStubHook.create(target, parent);
406
449
  } else if (this.source === "return") {
407
450
  let hook = this.rpcTargets?.get(target);
@@ -428,6 +471,64 @@ var RpcPayload = class _RpcPayload {
428
471
  throw new Error("owned payload shouldn't contain raw RpcTargets");
429
472
  }
430
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
+ }
431
532
  deepCopy(value, oldParent, property, parent, dupStubs, owner) {
432
533
  let kind = typeForRpc(value);
433
534
  switch (kind) {
@@ -471,22 +572,21 @@ var RpcPayload = class _RpcPayload {
471
572
  this.promises.push({ parent, property, promise });
472
573
  return promise;
473
574
  } else {
474
- let newStub = new RpcStub(hook);
475
- this.stubs.push(newStub);
476
- return newStub;
575
+ this.hooks.push(hook);
576
+ return new RpcStub(hook);
477
577
  }
478
578
  }
479
579
  case "function":
480
580
  case "rpc-target": {
481
581
  let target = value;
482
- let stub;
582
+ let hook;
483
583
  if (owner) {
484
- stub = new RpcStub(owner.getHookForRpcTarget(target, oldParent, dupStubs));
584
+ hook = owner.getHookForRpcTarget(target, oldParent, dupStubs);
485
585
  } else {
486
- stub = new RpcStub(TargetStubHook.create(target, oldParent));
586
+ hook = TargetStubHook.create(target, oldParent);
487
587
  }
488
- this.stubs.push(stub);
489
- return stub;
588
+ this.hooks.push(hook);
589
+ return new RpcStub(hook);
490
590
  }
491
591
  case "rpc-thenable": {
492
592
  let target = value;
@@ -499,6 +599,44 @@ var RpcPayload = class _RpcPayload {
499
599
  this.promises.push({ parent, property, promise });
500
600
  return promise;
501
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
+ }
502
640
  default:
503
641
  throw new Error("unreachable");
504
642
  }
@@ -508,12 +646,12 @@ var RpcPayload = class _RpcPayload {
508
646
  ensureDeepCopied() {
509
647
  if (this.source !== "owned") {
510
648
  let dupStubs = this.source === "params";
511
- this.stubs = [];
649
+ this.hooks = [];
512
650
  this.promises = [];
513
651
  try {
514
652
  this.value = this.deepCopy(this.value, void 0, "value", this, dupStubs, this);
515
653
  } catch (err) {
516
- this.stubs = void 0;
654
+ this.hooks = void 0;
517
655
  this.promises = void 0;
518
656
  throw err;
519
657
  }
@@ -616,7 +754,7 @@ var RpcPayload = class _RpcPayload {
616
754
  }
617
755
  dispose() {
618
756
  if (this.source === "owned") {
619
- this.stubs.forEach((stub) => stub[Symbol.dispose]());
757
+ this.hooks.forEach((hook) => hook.dispose());
620
758
  this.promises.forEach((promise) => promise.promise[Symbol.dispose]());
621
759
  } else if (this.source === "return") {
622
760
  this.disposeImpl(this.value, void 0);
@@ -625,7 +763,7 @@ var RpcPayload = class _RpcPayload {
625
763
  }
626
764
  } else ;
627
765
  this.source = "owned";
628
- this.stubs = [];
766
+ this.hooks = [];
629
767
  this.promises = [];
630
768
  }
631
769
  // Recursive dispose, called only when `source` is "return".
@@ -678,6 +816,40 @@ var RpcPayload = class _RpcPayload {
678
816
  }
679
817
  case "rpc-thenable":
680
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
+ }
681
853
  default:
682
854
  return;
683
855
  }
@@ -686,9 +858,9 @@ var RpcPayload = class _RpcPayload {
686
858
  // *would* be awaited if this payload were to be delivered. See the similarly-named method of
687
859
  // StubHook for explanation.
688
860
  ignoreUnhandledRejections() {
689
- if (this.stubs) {
690
- this.stubs.forEach((stub) => {
691
- unwrapStubOrParent(stub).ignoreUnhandledRejections();
861
+ if (this.hooks) {
862
+ this.hooks.forEach((hook) => {
863
+ hook.ignoreUnhandledRejections();
692
864
  });
693
865
  this.promises.forEach(
694
866
  (promise) => unwrapStubOrParent(promise.promise).ignoreUnhandledRejections()
@@ -709,6 +881,11 @@ var RpcPayload = class _RpcPayload {
709
881
  case "undefined":
710
882
  case "function":
711
883
  case "rpc-target":
884
+ case "writable":
885
+ case "readable":
886
+ case "headers":
887
+ case "request":
888
+ case "response":
712
889
  return;
713
890
  case "array": {
714
891
  let array = value;
@@ -781,11 +958,20 @@ function followPath(value, parent, path, owner) {
781
958
  let { hook, pathIfPromise } = unwrapStubAndPath(value);
782
959
  return { hook, remainingPath: pathIfPromise ? pathIfPromise.concat(path.slice(i)) : path.slice(i) };
783
960
  }
961
+ case "writable":
962
+ value = void 0;
963
+ break;
964
+ case "readable":
965
+ value = void 0;
966
+ break;
784
967
  case "primitive":
785
968
  case "bigint":
786
969
  case "bytes":
787
970
  case "date":
788
971
  case "error":
972
+ case "headers":
973
+ case "request":
974
+ case "response":
789
975
  value = void 0;
790
976
  break;
791
977
  case "undefined":
@@ -1020,6 +1206,14 @@ var PromiseStubHook = class _PromiseStubHook extends StubHook {
1020
1206
  args.ensureDeepCopied();
1021
1207
  return new _PromiseStubHook(this.promise.then((hook) => hook.call(path, args)));
1022
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
+ }
1023
1217
  map(path, captures, instructions) {
1024
1218
  return new _PromiseStubHook(this.promise.then(
1025
1219
  (hook) => hook.map(path, captures, instructions),
@@ -1092,6 +1286,9 @@ var NullExporter = class {
1092
1286
  }
1093
1287
  unexport(ids) {
1094
1288
  }
1289
+ createPipe(readable) {
1290
+ throw new Error("Cannot create pipes without an RPC session.");
1291
+ }
1095
1292
  onSendError(error) {
1096
1293
  }
1097
1294
  };
@@ -1196,6 +1393,73 @@ var Devaluator = class _Devaluator {
1196
1393
  ];
1197
1394
  }
1198
1395
  }
1396
+ case "headers":
1397
+ return ["headers", [...value]];
1398
+ case "request": {
1399
+ let req = value;
1400
+ let init = {};
1401
+ if (req.method !== "GET") init.method = req.method;
1402
+ let headers = [...req.headers];
1403
+ if (headers.length > 0) {
1404
+ init.headers = headers;
1405
+ }
1406
+ if (req.body) {
1407
+ init.body = this.devaluateImpl(req.body, req, depth + 1);
1408
+ init.duplex = req.duplex || "half";
1409
+ } else if (req.body === void 0 && !["GET", "HEAD", "OPTIONS", "TRACE", "DELETE"].includes(req.method)) {
1410
+ let bodyPromise = req.arrayBuffer();
1411
+ let readable = new ReadableStream({
1412
+ async start(controller) {
1413
+ try {
1414
+ controller.enqueue(new Uint8Array(await bodyPromise));
1415
+ controller.close();
1416
+ } catch (err) {
1417
+ controller.error(err);
1418
+ }
1419
+ }
1420
+ });
1421
+ let hook = streamImpl.createReadableStreamHook(readable);
1422
+ let importId = this.exporter.createPipe(readable, hook);
1423
+ init.body = ["readable", importId];
1424
+ init.duplex = req.duplex || "half";
1425
+ }
1426
+ if (req.cache && req.cache !== "default") init.cache = req.cache;
1427
+ if (req.redirect !== "follow") init.redirect = req.redirect;
1428
+ if (req.integrity) init.integrity = req.integrity;
1429
+ if (req.mode && req.mode !== "cors") init.mode = req.mode;
1430
+ if (req.credentials && req.credentials !== "same-origin") {
1431
+ init.credentials = req.credentials;
1432
+ }
1433
+ if (req.referrer && req.referrer !== "about:client") init.referrer = req.referrer;
1434
+ if (req.referrerPolicy) init.referrerPolicy = req.referrerPolicy;
1435
+ if (req.keepalive) init.keepalive = req.keepalive;
1436
+ let cfReq = req;
1437
+ if (cfReq.cf) init.cf = cfReq.cf;
1438
+ if (cfReq.encodeResponseBody && cfReq.encodeResponseBody !== "automatic") {
1439
+ init.encodeResponseBody = cfReq.encodeResponseBody;
1440
+ }
1441
+ return ["request", req.url, init];
1442
+ }
1443
+ case "response": {
1444
+ let resp = value;
1445
+ let body = this.devaluateImpl(resp.body, resp, depth + 1);
1446
+ let init = {};
1447
+ if (resp.status !== 200) init.status = resp.status;
1448
+ if (resp.statusText) init.statusText = resp.statusText;
1449
+ let headers = [...resp.headers];
1450
+ if (headers.length > 0) {
1451
+ init.headers = headers;
1452
+ }
1453
+ let cfResp = resp;
1454
+ if (cfResp.cf) init.cf = cfResp.cf;
1455
+ if (cfResp.encodeBody && cfResp.encodeBody !== "automatic") {
1456
+ init.encodeBody = cfResp.encodeBody;
1457
+ }
1458
+ if (cfResp.webSocket) {
1459
+ throw new TypeError("Can't serialize a Response containing a webSocket.");
1460
+ }
1461
+ return ["response", body, init];
1462
+ }
1199
1463
  case "error": {
1200
1464
  let e = value;
1201
1465
  let rewritten = this.exporter.onSendError(e);
@@ -1250,6 +1514,22 @@ var Devaluator = class _Devaluator {
1250
1514
  let hook = this.source.getHookForRpcTarget(value, parent);
1251
1515
  return this.devaluateHook("promise", hook);
1252
1516
  }
1517
+ case "writable": {
1518
+ if (!this.source) {
1519
+ throw new Error("Can't serialize WritableStream in this context.");
1520
+ }
1521
+ let hook = this.source.getHookForWritableStream(value, parent);
1522
+ return this.devaluateHook("writable", hook);
1523
+ }
1524
+ case "readable": {
1525
+ if (!this.source) {
1526
+ throw new Error("Can't serialize ReadableStream in this context.");
1527
+ }
1528
+ let ws = value;
1529
+ let hook = this.source.getHookForReadableStream(ws, parent);
1530
+ let importId = this.exporter.createPipe(ws, hook);
1531
+ return ["readable", importId];
1532
+ }
1253
1533
  default:
1254
1534
  throw new Error("unreachable");
1255
1535
  }
@@ -1274,16 +1554,27 @@ var NullImporter = class {
1274
1554
  getExport(idx) {
1275
1555
  return void 0;
1276
1556
  }
1557
+ getPipeReadable(exportId) {
1558
+ throw new Error("Cannot retrieve pipe readable without an RPC session.");
1559
+ }
1277
1560
  };
1278
1561
  var NULL_IMPORTER = new NullImporter();
1562
+ function fixBrokenRequestBody(request, body) {
1563
+ let promise = new Response(body).arrayBuffer().then((arrayBuffer) => {
1564
+ let bytes = new Uint8Array(arrayBuffer);
1565
+ let result = new Request(request, { body: bytes });
1566
+ return new PayloadStubHook(RpcPayload.fromAppReturn(result));
1567
+ });
1568
+ return new RpcPromise(new PromiseStubHook(promise), []);
1569
+ }
1279
1570
  var Evaluator = class _Evaluator {
1280
1571
  constructor(importer) {
1281
1572
  this.importer = importer;
1282
1573
  }
1283
- stubs = [];
1574
+ hooks = [];
1284
1575
  promises = [];
1285
1576
  evaluate(value) {
1286
- let payload = RpcPayload.forEvaluate(this.stubs, this.promises);
1577
+ let payload = RpcPayload.forEvaluate(this.hooks, this.promises);
1287
1578
  try {
1288
1579
  payload.value = this.evaluateImpl(value, payload, "value");
1289
1580
  return payload;
@@ -1353,6 +1644,56 @@ var Evaluator = class _Evaluator {
1353
1644
  return -Infinity;
1354
1645
  case "nan":
1355
1646
  return NaN;
1647
+ case "headers":
1648
+ if (value.length === 2 && value[1] instanceof Array) {
1649
+ return new Headers(value[1]);
1650
+ }
1651
+ break;
1652
+ case "request": {
1653
+ if (value.length !== 3 || typeof value[1] !== "string") break;
1654
+ let url = value[1];
1655
+ let init = value[2];
1656
+ if (typeof init !== "object" || init === null) break;
1657
+ if (init.body) {
1658
+ init.body = this.evaluateImpl(init.body, init, "body");
1659
+ if (init.body === null || typeof init.body === "string" || init.body instanceof Uint8Array || init.body instanceof ReadableStream) ; else {
1660
+ throw new TypeError("Request body must be of type ReadableStream.");
1661
+ }
1662
+ }
1663
+ if (init.signal) {
1664
+ init.signal = this.evaluateImpl(init.signal, init, "signal");
1665
+ if (!(init.signal instanceof AbortSignal)) {
1666
+ throw new TypeError("Request siganl must be of type AbortSignal.");
1667
+ }
1668
+ }
1669
+ if (init.headers && !(init.headers instanceof Array)) {
1670
+ throw new TypeError("Request headers must be serialized as an array of pairs.");
1671
+ }
1672
+ let result = new Request(url, init);
1673
+ if (init.body instanceof ReadableStream && result.body === void 0) {
1674
+ let promise = fixBrokenRequestBody(result, init.body);
1675
+ this.promises.push({ promise, parent, property });
1676
+ return promise;
1677
+ } else {
1678
+ return result;
1679
+ }
1680
+ }
1681
+ case "response": {
1682
+ if (value.length !== 3) break;
1683
+ let body = this.evaluateImpl(value[1], parent, property);
1684
+ if (body === null || typeof body === "string" || body instanceof Uint8Array || body instanceof ReadableStream) ; else {
1685
+ throw new TypeError("Response body must be of type ReadableStream.");
1686
+ }
1687
+ let init = value[2];
1688
+ if (typeof init !== "object" || init === null) break;
1689
+ if (init.webSocket) {
1690
+ throw new TypeError("Can't deserialize a Response containing a webSocket.");
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
+ return new Response(body, init);
1696
+ }
1356
1697
  case "import":
1357
1698
  case "pipeline": {
1358
1699
  if (value.length < 2 || value.length > 4) {
@@ -1372,9 +1713,8 @@ var Evaluator = class _Evaluator {
1372
1713
  this.promises.push({ promise, parent, property });
1373
1714
  return promise;
1374
1715
  } else {
1375
- let stub = new RpcPromise(hook2, []);
1376
- this.stubs.push(stub);
1377
- return stub;
1716
+ this.hooks.push(hook2);
1717
+ return new RpcPromise(hook2, []);
1378
1718
  }
1379
1719
  };
1380
1720
  if (value.length == 2) {
@@ -1452,12 +1792,27 @@ var Evaluator = class _Evaluator {
1452
1792
  return promise;
1453
1793
  } else {
1454
1794
  let hook = this.importer.importStub(value[1]);
1455
- let stub = new RpcStub(hook);
1456
- this.stubs.push(stub);
1457
- return stub;
1795
+ this.hooks.push(hook);
1796
+ return new RpcStub(hook);
1458
1797
  }
1459
1798
  }
1460
1799
  break;
1800
+ case "writable":
1801
+ if (typeof value[1] == "number") {
1802
+ let hook = this.importer.importStub(value[1]);
1803
+ let stream = streamImpl.createWritableStreamFromHook(hook);
1804
+ this.hooks.push(hook);
1805
+ return stream;
1806
+ }
1807
+ break;
1808
+ case "readable":
1809
+ if (typeof value[1] == "number") {
1810
+ let stream = this.importer.getPipeReadable(value[1]);
1811
+ let hook = streamImpl.createReadableStreamHook(stream);
1812
+ this.hooks.push(hook);
1813
+ return stream;
1814
+ }
1815
+ break;
1461
1816
  }
1462
1817
  throw new TypeError(`unknown special value: ${JSON.stringify(value)}`);
1463
1818
  } else if (value instanceof Object) {
@@ -1597,6 +1952,14 @@ var RpcImportHook = class _RpcImportHook extends StubHook {
1597
1952
  return entry.session.sendCall(entry.importId, path, args);
1598
1953
  }
1599
1954
  }
1955
+ stream(path, args) {
1956
+ let entry = this.getEntry();
1957
+ if (entry.resolution) {
1958
+ return entry.resolution.stream(path, args);
1959
+ } else {
1960
+ return entry.session.sendStream(entry.importId, path, args);
1961
+ }
1962
+ }
1600
1963
  map(path, captures, instructions) {
1601
1964
  let entry;
1602
1965
  try {
@@ -1769,19 +2132,23 @@ var RpcSessionImpl = class {
1769
2132
  return payload;
1770
2133
  }
1771
2134
  };
2135
+ let autoRelease = exp.autoRelease;
1772
2136
  ++this.pullCount;
1773
2137
  exp.pull = resolve().then(
1774
2138
  (payload) => {
1775
2139
  let value = Devaluator.devaluate(payload.value, void 0, this, payload);
1776
2140
  this.send(["resolve", exportId, value]);
2141
+ if (autoRelease) this.releaseExport(exportId, 1);
1777
2142
  },
1778
2143
  (error) => {
1779
2144
  this.send(["reject", exportId, Devaluator.devaluate(error, void 0, this)]);
2145
+ if (autoRelease) this.releaseExport(exportId, 1);
1780
2146
  }
1781
2147
  ).catch(
1782
2148
  (error) => {
1783
2149
  try {
1784
2150
  this.send(["reject", exportId, Devaluator.devaluate(error, void 0, this)]);
2151
+ if (autoRelease) this.releaseExport(exportId, 1);
1785
2152
  } catch (error2) {
1786
2153
  this.abort(error2);
1787
2154
  }
@@ -1833,9 +2200,35 @@ var RpcSessionImpl = class {
1833
2200
  getExport(idx) {
1834
2201
  return this.exports[idx]?.hook;
1835
2202
  }
2203
+ getPipeReadable(exportId) {
2204
+ let entry = this.exports[exportId];
2205
+ if (!entry || !entry.pipeReadable) {
2206
+ throw new Error(`Export ${exportId} is not a pipe or its readable end was already consumed.`);
2207
+ }
2208
+ let readable = entry.pipeReadable;
2209
+ entry.pipeReadable = void 0;
2210
+ return readable;
2211
+ }
2212
+ createPipe(readable, readableHook) {
2213
+ if (this.abortReason) throw this.abortReason;
2214
+ this.send(["pipe"]);
2215
+ let importId = this.imports.length;
2216
+ let entry = new ImportTableEntry(this, importId, false);
2217
+ this.imports.push(entry);
2218
+ let hook = new RpcImportHook(
2219
+ /*isPromise=*/
2220
+ false,
2221
+ entry
2222
+ );
2223
+ let writable = streamImpl.createWritableStreamFromHook(hook);
2224
+ readable.pipeTo(writable).catch(() => {
2225
+ }).finally(() => readableHook.dispose());
2226
+ return importId;
2227
+ }
2228
+ // Serializes and sends a message. Returns the byte length of the serialized message.
1836
2229
  send(msg) {
1837
2230
  if (this.abortReason !== void 0) {
1838
- return;
2231
+ return 0;
1839
2232
  }
1840
2233
  let msgText;
1841
2234
  try {
@@ -1848,6 +2241,7 @@ var RpcSessionImpl = class {
1848
2241
  throw err;
1849
2242
  }
1850
2243
  this.transport.send(msgText).catch((err) => this.abort(err, false));
2244
+ return msgText.length;
1851
2245
  }
1852
2246
  sendCall(id, path, args) {
1853
2247
  if (this.abortReason) throw this.abortReason;
@@ -1865,6 +2259,34 @@ var RpcSessionImpl = class {
1865
2259
  entry
1866
2260
  );
1867
2261
  }
2262
+ sendStream(id, path, args) {
2263
+ if (this.abortReason) throw this.abortReason;
2264
+ let value = ["pipeline", id, path];
2265
+ let devalue = Devaluator.devaluate(args.value, void 0, this, args);
2266
+ value.push(devalue[0]);
2267
+ let size = this.send(["stream", value]);
2268
+ let importId = this.imports.length;
2269
+ let entry = new ImportTableEntry(
2270
+ this,
2271
+ importId,
2272
+ /*pulling=*/
2273
+ true
2274
+ );
2275
+ entry.remoteRefcount = 0;
2276
+ entry.localRefcount = 1;
2277
+ this.imports.push(entry);
2278
+ let promise = entry.awaitResolution().then(
2279
+ (p) => {
2280
+ p.dispose();
2281
+ delete this.imports[importId];
2282
+ },
2283
+ (err) => {
2284
+ delete this.imports[importId];
2285
+ throw err;
2286
+ }
2287
+ );
2288
+ return { promise, size };
2289
+ }
1868
2290
  sendMap(id, path, captures, instructions) {
1869
2291
  if (this.abortReason) {
1870
2292
  for (let cap of captures) {
@@ -1952,6 +2374,24 @@ var RpcSessionImpl = class {
1952
2374
  continue;
1953
2375
  }
1954
2376
  break;
2377
+ case "stream": {
2378
+ if (msg.length > 1) {
2379
+ let payload = new Evaluator(this).evaluate(msg[1]);
2380
+ let hook = new PayloadStubHook(payload);
2381
+ hook.ignoreUnhandledRejections();
2382
+ let exportId = this.exports.length;
2383
+ this.exports.push({ hook, refcount: 1, autoRelease: true });
2384
+ this.ensureResolvingExport(exportId);
2385
+ continue;
2386
+ }
2387
+ break;
2388
+ }
2389
+ case "pipe": {
2390
+ let { readable, writable } = new TransformStream();
2391
+ let hook = streamImpl.createWritableStreamHook(writable);
2392
+ this.exports.push({ hook, refcount: 1, pipeReadable: readable });
2393
+ continue;
2394
+ }
1955
2395
  case "pull": {
1956
2396
  let exportId = msg[1];
1957
2397
  if (typeof exportId == "number") {
@@ -2447,6 +2887,9 @@ var MapBuilder = class {
2447
2887
  }
2448
2888
  unexport(ids) {
2449
2889
  }
2890
+ createPipe(readable) {
2891
+ throw new Error("Cannot send ReadableStream inside a mapper function.");
2892
+ }
2450
2893
  onSendError(error) {
2451
2894
  }
2452
2895
  };
@@ -2556,6 +2999,9 @@ var MapApplicator = class {
2556
2999
  return this.variables[idx];
2557
3000
  }
2558
3001
  }
3002
+ getPipeReadable(exportId) {
3003
+ throw new Error("A mapper function cannot use pipe readables.");
3004
+ }
2559
3005
  };
2560
3006
  function applyMapToElement(input, parent, owner, captures, instructions) {
2561
3007
  let inputHook = new PayloadStubHook(RpcPayload.deepCopyFrom(input, parent, owner));
@@ -2596,6 +3042,333 @@ mapImpl.applyMap = (input, parent, owner, captures, instructions) => {
2596
3042
  }
2597
3043
  }
2598
3044
  };
3045
+
3046
+ // src/streams.ts
3047
+ var WritableStreamStubHook = class _WritableStreamStubHook extends StubHook {
3048
+ state;
3049
+ // undefined when disposed
3050
+ // Creates a new WritableStreamStubHook that is not duplicated from an existing hook.
3051
+ static create(stream) {
3052
+ let writer = stream.getWriter();
3053
+ return new _WritableStreamStubHook({ refcount: 1, writer, closed: false });
3054
+ }
3055
+ constructor(state, dupFrom) {
3056
+ super();
3057
+ this.state = state;
3058
+ if (dupFrom) {
3059
+ ++state.refcount;
3060
+ }
3061
+ }
3062
+ getState() {
3063
+ if (this.state) {
3064
+ return this.state;
3065
+ } else {
3066
+ throw new Error("Attempted to use a WritableStreamStubHook after it was disposed.");
3067
+ }
3068
+ }
3069
+ call(path, args) {
3070
+ try {
3071
+ let state = this.getState();
3072
+ if (path.length !== 1 || typeof path[0] !== "string") {
3073
+ throw new Error("WritableStream stub only supports direct method calls");
3074
+ }
3075
+ const method = path[0];
3076
+ if (method !== "write" && method !== "close" && method !== "abort") {
3077
+ args.dispose();
3078
+ throw new Error(`Unknown WritableStream method: ${method}`);
3079
+ }
3080
+ if (method === "close" || method === "abort") {
3081
+ state.closed = true;
3082
+ }
3083
+ let func = state.writer[method];
3084
+ let promise = args.deliverCall(func, state.writer);
3085
+ return new PromiseStubHook(promise.then((payload) => new PayloadStubHook(payload)));
3086
+ } catch (err) {
3087
+ return new ErrorStubHook(err);
3088
+ }
3089
+ }
3090
+ map(path, captures, instructions) {
3091
+ for (let cap of captures) {
3092
+ cap.dispose();
3093
+ }
3094
+ return new ErrorStubHook(new Error("Cannot use map() on a WritableStream"));
3095
+ }
3096
+ get(path) {
3097
+ return new ErrorStubHook(new Error("Cannot access properties on a WritableStream stub"));
3098
+ }
3099
+ dup() {
3100
+ let state = this.getState();
3101
+ return new _WritableStreamStubHook(state, this);
3102
+ }
3103
+ pull() {
3104
+ return Promise.reject(new Error("Cannot pull a WritableStream stub"));
3105
+ }
3106
+ ignoreUnhandledRejections() {
3107
+ }
3108
+ dispose() {
3109
+ let state = this.state;
3110
+ this.state = void 0;
3111
+ if (state) {
3112
+ if (--state.refcount === 0) {
3113
+ if (!state.closed) {
3114
+ state.writer.abort(new Error("WritableStream RPC stub was disposed without calling close()")).catch(() => {
3115
+ });
3116
+ }
3117
+ state.writer.releaseLock();
3118
+ }
3119
+ }
3120
+ }
3121
+ onBroken(callback) {
3122
+ }
3123
+ };
3124
+ var INITIAL_WINDOW = 256 * 1024;
3125
+ var MAX_WINDOW = 1024 * 1024 * 1024;
3126
+ var MIN_WINDOW = 64 * 1024;
3127
+ var STARTUP_GROWTH_FACTOR = 2;
3128
+ var STEADY_GROWTH_FACTOR = 1.25;
3129
+ var DECAY_FACTOR = 0.9;
3130
+ var STARTUP_EXIT_ROUNDS = 3;
3131
+ var FlowController = class {
3132
+ constructor(now) {
3133
+ this.now = now;
3134
+ }
3135
+ // The current window size in bytes. The sender blocks when bytesInFlight >= window.
3136
+ window = INITIAL_WINDOW;
3137
+ // Total bytes currently in flight (sent but not yet acked).
3138
+ bytesInFlight = 0;
3139
+ // Whether we're still in the startup phase.
3140
+ inStartupPhase = true;
3141
+ // ----- BDP estimation state (private) -----
3142
+ // Total bytes acked so far.
3143
+ delivered = 0;
3144
+ // Time of most recent ack.
3145
+ deliveredTime = 0;
3146
+ // Time when the very first ack was received.
3147
+ firstAckTime = 0;
3148
+ firstAckDelivered = 0;
3149
+ // Global minimum RTT observed (milliseconds).
3150
+ minRtt = Infinity;
3151
+ // For startup exit: count of consecutive RTT rounds where the window didn't meaningfully grow.
3152
+ roundsWithoutIncrease = 0;
3153
+ // Window size at the start of the current round, for startup exit detection.
3154
+ lastRoundWindow = 0;
3155
+ // Time when the current round started.
3156
+ roundStartTime = 0;
3157
+ // Called when a write of `size` bytes is about to be sent. Returns a token that must be
3158
+ // passed to onAck() when the ack arrives, and whether the sender should block (window full).
3159
+ onSend(size) {
3160
+ this.bytesInFlight += size;
3161
+ let token = {
3162
+ sentTime: this.now(),
3163
+ size,
3164
+ deliveredAtSend: this.delivered,
3165
+ deliveredTimeAtSend: this.deliveredTime,
3166
+ windowAtSend: this.window,
3167
+ windowFullAtSend: this.bytesInFlight >= this.window
3168
+ };
3169
+ return { token, shouldBlock: token.windowFullAtSend };
3170
+ }
3171
+ // Called when a previously-sent write fails. Restores bytesInFlight without updating
3172
+ // any BDP estimates.
3173
+ onError(token) {
3174
+ this.bytesInFlight -= token.size;
3175
+ }
3176
+ // Called when an ack is received for a previously-sent write. Updates BDP estimates and
3177
+ // the window. Returns whether a blocked sender should now unblock.
3178
+ onAck(token) {
3179
+ let ackTime = this.now();
3180
+ this.delivered += token.size;
3181
+ this.deliveredTime = ackTime;
3182
+ this.bytesInFlight -= token.size;
3183
+ let rtt = ackTime - token.sentTime;
3184
+ this.minRtt = Math.min(this.minRtt, rtt);
3185
+ if (this.firstAckTime === 0) {
3186
+ this.firstAckTime = ackTime;
3187
+ this.firstAckDelivered = this.delivered;
3188
+ } else {
3189
+ let baseTime;
3190
+ let baseDelivered;
3191
+ if (token.deliveredTimeAtSend === 0) {
3192
+ baseTime = this.firstAckTime;
3193
+ baseDelivered = this.firstAckDelivered;
3194
+ } else {
3195
+ baseTime = token.deliveredTimeAtSend;
3196
+ baseDelivered = token.deliveredAtSend;
3197
+ }
3198
+ let interval = ackTime - baseTime;
3199
+ let bytes = this.delivered - baseDelivered;
3200
+ let bandwidth = bytes / interval;
3201
+ let growthFactor = this.inStartupPhase ? STARTUP_GROWTH_FACTOR : STEADY_GROWTH_FACTOR;
3202
+ let newWindow = bandwidth * this.minRtt * growthFactor;
3203
+ newWindow = Math.min(newWindow, token.windowAtSend * growthFactor);
3204
+ if (token.windowFullAtSend) {
3205
+ newWindow = Math.max(newWindow, token.windowAtSend * DECAY_FACTOR);
3206
+ } else {
3207
+ newWindow = Math.max(newWindow, this.window);
3208
+ }
3209
+ this.window = Math.max(Math.min(newWindow, MAX_WINDOW), MIN_WINDOW);
3210
+ if (this.inStartupPhase && token.sentTime >= this.roundStartTime) {
3211
+ if (this.window > this.lastRoundWindow * STEADY_GROWTH_FACTOR) {
3212
+ this.roundsWithoutIncrease = 0;
3213
+ } else {
3214
+ if (++this.roundsWithoutIncrease >= STARTUP_EXIT_ROUNDS) {
3215
+ this.inStartupPhase = false;
3216
+ }
3217
+ }
3218
+ this.roundStartTime = ackTime;
3219
+ this.lastRoundWindow = this.window;
3220
+ }
3221
+ }
3222
+ return this.bytesInFlight < this.window;
3223
+ }
3224
+ };
3225
+ function createWritableStreamFromHook(hook) {
3226
+ let pendingError = void 0;
3227
+ let hookDisposed = false;
3228
+ let fc = new FlowController(() => performance.now());
3229
+ let windowResolve;
3230
+ let windowReject;
3231
+ const disposeHook = () => {
3232
+ if (!hookDisposed) {
3233
+ hookDisposed = true;
3234
+ hook.dispose();
3235
+ }
3236
+ };
3237
+ return new WritableStream({
3238
+ write(chunk, controller) {
3239
+ if (pendingError !== void 0) {
3240
+ throw pendingError;
3241
+ }
3242
+ const payload = RpcPayload.fromAppParams([chunk]);
3243
+ const { promise, size } = hook.stream(["write"], payload);
3244
+ if (size === void 0) {
3245
+ return promise.catch((err) => {
3246
+ if (pendingError === void 0) {
3247
+ pendingError = err;
3248
+ }
3249
+ throw err;
3250
+ });
3251
+ } else {
3252
+ let { token, shouldBlock } = fc.onSend(size);
3253
+ promise.then(() => {
3254
+ let hasCapacity = fc.onAck(token);
3255
+ if (hasCapacity && windowResolve) {
3256
+ windowResolve();
3257
+ windowResolve = void 0;
3258
+ windowReject = void 0;
3259
+ }
3260
+ }, (err) => {
3261
+ fc.onError(token);
3262
+ if (pendingError === void 0) {
3263
+ pendingError = err;
3264
+ controller.error(err);
3265
+ disposeHook();
3266
+ }
3267
+ if (windowReject) {
3268
+ windowReject(err);
3269
+ windowResolve = void 0;
3270
+ windowReject = void 0;
3271
+ }
3272
+ });
3273
+ if (shouldBlock) {
3274
+ return new Promise((resolve, reject) => {
3275
+ windowResolve = resolve;
3276
+ windowReject = reject;
3277
+ });
3278
+ }
3279
+ }
3280
+ },
3281
+ async close() {
3282
+ if (pendingError !== void 0) {
3283
+ disposeHook();
3284
+ throw pendingError;
3285
+ }
3286
+ const { promise } = hook.stream(["close"], RpcPayload.fromAppParams([]));
3287
+ try {
3288
+ await promise;
3289
+ } catch (err) {
3290
+ throw pendingError ?? err;
3291
+ } finally {
3292
+ disposeHook();
3293
+ }
3294
+ },
3295
+ abort(reason) {
3296
+ if (pendingError !== void 0) {
3297
+ return;
3298
+ }
3299
+ pendingError = reason ?? new Error("WritableStream was aborted");
3300
+ if (windowReject) {
3301
+ windowReject(pendingError);
3302
+ windowResolve = void 0;
3303
+ windowReject = void 0;
3304
+ }
3305
+ const { promise } = hook.stream(["abort"], RpcPayload.fromAppParams([reason]));
3306
+ promise.then(() => disposeHook(), () => disposeHook());
3307
+ }
3308
+ });
3309
+ }
3310
+ var ReadableStreamStubHook = class _ReadableStreamStubHook extends StubHook {
3311
+ state;
3312
+ // undefined when disposed
3313
+ // Creates a new ReadableStreamStubHook.
3314
+ static create(stream) {
3315
+ return new _ReadableStreamStubHook({ refcount: 1, stream, canceled: false });
3316
+ }
3317
+ constructor(state, dupFrom) {
3318
+ super();
3319
+ this.state = state;
3320
+ if (dupFrom) {
3321
+ ++state.refcount;
3322
+ }
3323
+ }
3324
+ call(path, args) {
3325
+ args.dispose();
3326
+ return new ErrorStubHook(new Error("Cannot call methods on a ReadableStream stub"));
3327
+ }
3328
+ map(path, captures, instructions) {
3329
+ for (let cap of captures) {
3330
+ cap.dispose();
3331
+ }
3332
+ return new ErrorStubHook(new Error("Cannot use map() on a ReadableStream"));
3333
+ }
3334
+ get(path) {
3335
+ return new ErrorStubHook(new Error("Cannot access properties on a ReadableStream stub"));
3336
+ }
3337
+ dup() {
3338
+ let state = this.state;
3339
+ if (!state) {
3340
+ throw new Error("Attempted to dup a ReadableStreamStubHook after it was disposed.");
3341
+ }
3342
+ return new _ReadableStreamStubHook(state, this);
3343
+ }
3344
+ pull() {
3345
+ return Promise.reject(new Error("Cannot pull a ReadableStream stub"));
3346
+ }
3347
+ ignoreUnhandledRejections() {
3348
+ }
3349
+ dispose() {
3350
+ let state = this.state;
3351
+ this.state = void 0;
3352
+ if (state) {
3353
+ if (--state.refcount === 0) {
3354
+ if (!state.canceled) {
3355
+ state.canceled = true;
3356
+ if (!state.stream.locked) {
3357
+ state.stream.cancel(
3358
+ new Error("ReadableStream RPC stub was disposed without being consumed")
3359
+ ).catch(() => {
3360
+ });
3361
+ }
3362
+ }
3363
+ }
3364
+ }
3365
+ }
3366
+ onBroken(callback) {
3367
+ }
3368
+ };
3369
+ streamImpl.createWritableStreamHook = WritableStreamStubHook.create;
3370
+ streamImpl.createWritableStreamFromHook = createWritableStreamFromHook;
3371
+ streamImpl.createReadableStreamHook = ReadableStreamStubHook.create;
2599
3372
  var RpcStub2 = RpcStub;
2600
3373
  var RpcPromise2 = RpcPromise;
2601
3374
  var RpcSession2 = RpcSession;