beam.com 0.1.0-rc.3 → 0.1.0-rc.4

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/README.md CHANGED
@@ -219,7 +219,7 @@ jobs:
219
219
 
220
220
  Then turn on Pages one time: Settings, Pages, Source "GitHub Actions". The
221
221
  `GITHUB_TOKEN` cannot do this step. Until the release `v0.1.0`, use the
222
- release candidate `@v0.1.0-rc.3` in place of `@v0.1.0`. With `@main`, the
222
+ release candidate `@v0.1.0-rc.4` in place of `@v0.1.0`. With `@main`, the
223
223
  workflow uses the `edge` build of `beam.com`.
224
224
 
225
225
  The workflow ([`pages-app.yml`](.github/workflows/pages-app.yml)) builds
@@ -286,7 +286,7 @@ Each GitHub release from 0.1.0-rc.2 also holds the package, the same
286
286
  file as on npm. npm installs it with no registry:
287
287
 
288
288
  ```sh
289
- npm install https://github.com/sntran/BEAM.com/releases/download/v0.1.0-rc.3/beam.com-0.1.0-rc.3.tgz
289
+ npm install https://github.com/sntran/BEAM.com/releases/download/v0.1.0-rc.4/beam.com-0.1.0-rc.4.tgz
290
290
  ```
291
291
 
292
292
  - `BEAM_COM`: the path of a `beam.com` to use. Then nothing is
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beam.com",
3
- "version": "0.1.0-rc.3",
3
+ "version": "0.1.0-rc.4",
4
4
  "description": "Erlang/OTP and Elixir in one executable file: npx beam.com, and the WebAssembly runtime for a native app.com",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -1972,8 +1972,10 @@ export class Vm {
1972
1972
  // body of the request, and the messages of a WebSocket. sent: the
1973
1973
  // host tells wasm_tcp what the client took (tcp_sent).
1974
1974
  this.event({ t: 'tcp_accept', id: this.listeners.get(port), conn: id, host: request.headers.get('cf-connecting-ip') ?? '0.0.0.0', port: 0, ack: !!body || upgrade, sent: true });
1975
- this.bridgeSend(c, bytes);
1976
- if (body) this.bridgeUpload(c, body, declared === null, maxBody);
1975
+ // With a body, the head goes in the event of the first part of the
1976
+ // body (bridgeUpload): a small body is one event, as before the parts.
1977
+ if (body) this.bridgeUpload(c, body, declared === null, maxBody, bytes);
1978
+ else this.bridgeSend(c, bytes);
1977
1979
  });
1978
1980
  }
1979
1981
 
@@ -1987,8 +1989,10 @@ export class Vm {
1987
1989
  // (or the rest of the body): one event for each part, not for each small
1988
1990
  // chunk of the client. A part goes when less than UPLOAD_WINDOW bytes are
1989
1991
  // unread in the VM (tcp_read of wasm_tcp): so a large body does not fill
1990
- // the memory of the VM, and a slow app slows the client.
1991
- async bridgeUpload(c, body, chunked, max) {
1992
+ // the memory of the VM, and a slow app slows the client. The head of the
1993
+ // request goes with the bytes of the first read of the client, with no
1994
+ // wait for UPLOAD_PART bytes.
1995
+ async bridgeUpload(c, body, chunked, max, head) {
1992
1996
  // workerd has readAtLeast. The option min of the standard BYOB read is
1993
1997
  // not safe: when the stream closes with fewer bytes than min, the read
1994
1998
  // errors the stream (Node.js 26, Chromium). So the other hosts use the
@@ -1997,7 +2001,7 @@ export class Vm {
1997
2001
  try { byob = body.getReader({ mode: 'byob' }); } catch {}
1998
2002
  if (byob && !byob.readAtLeast) { byob.releaseLock(); byob = null; }
1999
2003
  const reader = byob ?? body.getReader();
2000
- const read = byob ? () => byob.readAtLeast(UPLOAD_PART, new Uint8Array(UPLOAD_READ)) : () => reader.read();
2004
+ const read = byob ? (min) => byob.readAtLeast(min, new Uint8Array(UPLOAD_READ)) : () => reader.read();
2001
2005
  let total = 0;
2002
2006
  let part = [];
2003
2007
  let size = 0;
@@ -2011,13 +2015,22 @@ export class Vm {
2011
2015
  const bytes = part.length === 1 ? part[0] : join(part, size);
2012
2016
  part = [];
2013
2017
  size = 0;
2014
- this.bridgeSend(c, chunked ? chunk(bytes) : bytes);
2018
+ out(chunked ? chunk(bytes) : bytes);
2015
2019
  return true;
2016
2020
  };
2021
+ // Bytes to the app, after the head when it did not go yet.
2022
+ const out = (bytes) => {
2023
+ clearTimeout(alone);
2024
+ this.bridgeSend(c, head ? join([head, bytes], head.length + bytes.length) : bytes);
2025
+ head = null;
2026
+ };
2027
+ // A client that sends no body yet: the head goes alone after HEAD_WAIT
2028
+ // ms, so the app sees the request (and its own time limits run).
2029
+ const alone = setTimeout(() => { if (head && !c.done && !this.dead) out(new Uint8Array(0)); }, HEAD_WAIT);
2017
2030
  try {
2018
2031
  for (;;) {
2019
2032
  // A read can end the stream with its last bytes (done and a value).
2020
- const { value, done } = await read();
2033
+ const { value, done } = await read(head ? 1 : UPLOAD_PART);
2021
2034
  if (c.done || this.dead) break;
2022
2035
  if (value?.length) {
2023
2036
  total += value.length;
@@ -2035,10 +2048,11 @@ export class Vm {
2035
2048
  size += piece.length;
2036
2049
  if (size >= UPLOAD_PART) ok = await flush();
2037
2050
  }
2038
- if (ok && done && size) ok = await flush();
2051
+ if (ok && (done || head) && size) ok = await flush();
2039
2052
  if (!ok) break;
2040
2053
  if (done) {
2041
- if (chunked) this.bridgeSend(c, CHUNKS_END);
2054
+ if (chunked) out(CHUNKS_END);
2055
+ else if (head) out(new Uint8Array(0));
2042
2056
  this.bridgeClock(c, c.timeout * 1000);
2043
2057
  return;
2044
2058
  }
@@ -2759,7 +2773,10 @@ const LATE = Symbol('late');
2759
2773
  // read in workerd and of the largest piece of a client chunk. A client chunk can be 2 KB (workerd), and each event
2760
2774
  // costs a turn of the VM.
2761
2775
  const UPLOAD_WINDOW = 256 * 1024;
2762
- const UPLOAD_PART = 16 * 1024;
2776
+ const UPLOAD_PART = 32 * 1024;
2777
+ // The time that the head of a request waits for the first bytes of its
2778
+ // body (bridgeUpload), in ms.
2779
+ const HEAD_WAIT = 20;
2763
2780
  const UPLOAD_READ = 64 * 1024;
2764
2781
 
2765
2782
  // The size bytes of the list parts, as one array.
@@ -1972,8 +1972,10 @@ export class Vm {
1972
1972
  // body of the request, and the messages of a WebSocket. sent: the
1973
1973
  // host tells wasm_tcp what the client took (tcp_sent).
1974
1974
  this.event({ t: 'tcp_accept', id: this.listeners.get(port), conn: id, host: request.headers.get('cf-connecting-ip') ?? '0.0.0.0', port: 0, ack: !!body || upgrade, sent: true });
1975
- this.bridgeSend(c, bytes);
1976
- if (body) this.bridgeUpload(c, body, declared === null, maxBody);
1975
+ // With a body, the head goes in the event of the first part of the
1976
+ // body (bridgeUpload): a small body is one event, as before the parts.
1977
+ if (body) this.bridgeUpload(c, body, declared === null, maxBody, bytes);
1978
+ else this.bridgeSend(c, bytes);
1977
1979
  });
1978
1980
  }
1979
1981
 
@@ -1987,8 +1989,10 @@ export class Vm {
1987
1989
  // (or the rest of the body): one event for each part, not for each small
1988
1990
  // chunk of the client. A part goes when less than UPLOAD_WINDOW bytes are
1989
1991
  // unread in the VM (tcp_read of wasm_tcp): so a large body does not fill
1990
- // the memory of the VM, and a slow app slows the client.
1991
- async bridgeUpload(c, body, chunked, max) {
1992
+ // the memory of the VM, and a slow app slows the client. The head of the
1993
+ // request goes with the bytes of the first read of the client, with no
1994
+ // wait for UPLOAD_PART bytes.
1995
+ async bridgeUpload(c, body, chunked, max, head) {
1992
1996
  // workerd has readAtLeast. The option min of the standard BYOB read is
1993
1997
  // not safe: when the stream closes with fewer bytes than min, the read
1994
1998
  // errors the stream (Node.js 26, Chromium). So the other hosts use the
@@ -1997,7 +2001,7 @@ export class Vm {
1997
2001
  try { byob = body.getReader({ mode: 'byob' }); } catch {}
1998
2002
  if (byob && !byob.readAtLeast) { byob.releaseLock(); byob = null; }
1999
2003
  const reader = byob ?? body.getReader();
2000
- const read = byob ? () => byob.readAtLeast(UPLOAD_PART, new Uint8Array(UPLOAD_READ)) : () => reader.read();
2004
+ const read = byob ? (min) => byob.readAtLeast(min, new Uint8Array(UPLOAD_READ)) : () => reader.read();
2001
2005
  let total = 0;
2002
2006
  let part = [];
2003
2007
  let size = 0;
@@ -2011,13 +2015,22 @@ export class Vm {
2011
2015
  const bytes = part.length === 1 ? part[0] : join(part, size);
2012
2016
  part = [];
2013
2017
  size = 0;
2014
- this.bridgeSend(c, chunked ? chunk(bytes) : bytes);
2018
+ out(chunked ? chunk(bytes) : bytes);
2015
2019
  return true;
2016
2020
  };
2021
+ // Bytes to the app, after the head when it did not go yet.
2022
+ const out = (bytes) => {
2023
+ clearTimeout(alone);
2024
+ this.bridgeSend(c, head ? join([head, bytes], head.length + bytes.length) : bytes);
2025
+ head = null;
2026
+ };
2027
+ // A client that sends no body yet: the head goes alone after HEAD_WAIT
2028
+ // ms, so the app sees the request (and its own time limits run).
2029
+ const alone = setTimeout(() => { if (head && !c.done && !this.dead) out(new Uint8Array(0)); }, HEAD_WAIT);
2017
2030
  try {
2018
2031
  for (;;) {
2019
2032
  // A read can end the stream with its last bytes (done and a value).
2020
- const { value, done } = await read();
2033
+ const { value, done } = await read(head ? 1 : UPLOAD_PART);
2021
2034
  if (c.done || this.dead) break;
2022
2035
  if (value?.length) {
2023
2036
  total += value.length;
@@ -2035,10 +2048,11 @@ export class Vm {
2035
2048
  size += piece.length;
2036
2049
  if (size >= UPLOAD_PART) ok = await flush();
2037
2050
  }
2038
- if (ok && done && size) ok = await flush();
2051
+ if (ok && (done || head) && size) ok = await flush();
2039
2052
  if (!ok) break;
2040
2053
  if (done) {
2041
- if (chunked) this.bridgeSend(c, CHUNKS_END);
2054
+ if (chunked) out(CHUNKS_END);
2055
+ else if (head) out(new Uint8Array(0));
2042
2056
  this.bridgeClock(c, c.timeout * 1000);
2043
2057
  return;
2044
2058
  }
@@ -2759,7 +2773,10 @@ const LATE = Symbol('late');
2759
2773
  // read in workerd and of the largest piece of a client chunk. A client chunk can be 2 KB (workerd), and each event
2760
2774
  // costs a turn of the VM.
2761
2775
  const UPLOAD_WINDOW = 256 * 1024;
2762
- const UPLOAD_PART = 16 * 1024;
2776
+ const UPLOAD_PART = 32 * 1024;
2777
+ // The time that the head of a request waits for the first bytes of its
2778
+ // body (bridgeUpload), in ms.
2779
+ const HEAD_WAIT = 20;
2763
2780
  const UPLOAD_READ = 64 * 1024;
2764
2781
 
2765
2782
  // The size bytes of the list parts, as one array.
@@ -1,3 +1,3 @@
1
1
  // The identity of this runtime (beam_com_wasm:runtime_id/1): the SHA-256 of
2
2
  // the output of "sha256sum app-com.js beam.mjs beam.wasm worker.js".
3
- export default '6f6148b7c684c1118b9033a0e895df5091e4e0d063aef93417fa882c5fe3ceee';
3
+ export default '3ef0d2bdd150e2ab64517e29e6e85fbf70382d878b9ccbca998a22f15b566dc0';
@@ -1972,8 +1972,10 @@ export class Vm {
1972
1972
  // body of the request, and the messages of a WebSocket. sent: the
1973
1973
  // host tells wasm_tcp what the client took (tcp_sent).
1974
1974
  this.event({ t: 'tcp_accept', id: this.listeners.get(port), conn: id, host: request.headers.get('cf-connecting-ip') ?? '0.0.0.0', port: 0, ack: !!body || upgrade, sent: true });
1975
- this.bridgeSend(c, bytes);
1976
- if (body) this.bridgeUpload(c, body, declared === null, maxBody);
1975
+ // With a body, the head goes in the event of the first part of the
1976
+ // body (bridgeUpload): a small body is one event, as before the parts.
1977
+ if (body) this.bridgeUpload(c, body, declared === null, maxBody, bytes);
1978
+ else this.bridgeSend(c, bytes);
1977
1979
  });
1978
1980
  }
1979
1981
 
@@ -1987,8 +1989,10 @@ export class Vm {
1987
1989
  // (or the rest of the body): one event for each part, not for each small
1988
1990
  // chunk of the client. A part goes when less than UPLOAD_WINDOW bytes are
1989
1991
  // unread in the VM (tcp_read of wasm_tcp): so a large body does not fill
1990
- // the memory of the VM, and a slow app slows the client.
1991
- async bridgeUpload(c, body, chunked, max) {
1992
+ // the memory of the VM, and a slow app slows the client. The head of the
1993
+ // request goes with the bytes of the first read of the client, with no
1994
+ // wait for UPLOAD_PART bytes.
1995
+ async bridgeUpload(c, body, chunked, max, head) {
1992
1996
  // workerd has readAtLeast. The option min of the standard BYOB read is
1993
1997
  // not safe: when the stream closes with fewer bytes than min, the read
1994
1998
  // errors the stream (Node.js 26, Chromium). So the other hosts use the
@@ -1997,7 +2001,7 @@ export class Vm {
1997
2001
  try { byob = body.getReader({ mode: 'byob' }); } catch {}
1998
2002
  if (byob && !byob.readAtLeast) { byob.releaseLock(); byob = null; }
1999
2003
  const reader = byob ?? body.getReader();
2000
- const read = byob ? () => byob.readAtLeast(UPLOAD_PART, new Uint8Array(UPLOAD_READ)) : () => reader.read();
2004
+ const read = byob ? (min) => byob.readAtLeast(min, new Uint8Array(UPLOAD_READ)) : () => reader.read();
2001
2005
  let total = 0;
2002
2006
  let part = [];
2003
2007
  let size = 0;
@@ -2011,13 +2015,22 @@ export class Vm {
2011
2015
  const bytes = part.length === 1 ? part[0] : join(part, size);
2012
2016
  part = [];
2013
2017
  size = 0;
2014
- this.bridgeSend(c, chunked ? chunk(bytes) : bytes);
2018
+ out(chunked ? chunk(bytes) : bytes);
2015
2019
  return true;
2016
2020
  };
2021
+ // Bytes to the app, after the head when it did not go yet.
2022
+ const out = (bytes) => {
2023
+ clearTimeout(alone);
2024
+ this.bridgeSend(c, head ? join([head, bytes], head.length + bytes.length) : bytes);
2025
+ head = null;
2026
+ };
2027
+ // A client that sends no body yet: the head goes alone after HEAD_WAIT
2028
+ // ms, so the app sees the request (and its own time limits run).
2029
+ const alone = setTimeout(() => { if (head && !c.done && !this.dead) out(new Uint8Array(0)); }, HEAD_WAIT);
2017
2030
  try {
2018
2031
  for (;;) {
2019
2032
  // A read can end the stream with its last bytes (done and a value).
2020
- const { value, done } = await read();
2033
+ const { value, done } = await read(head ? 1 : UPLOAD_PART);
2021
2034
  if (c.done || this.dead) break;
2022
2035
  if (value?.length) {
2023
2036
  total += value.length;
@@ -2035,10 +2048,11 @@ export class Vm {
2035
2048
  size += piece.length;
2036
2049
  if (size >= UPLOAD_PART) ok = await flush();
2037
2050
  }
2038
- if (ok && done && size) ok = await flush();
2051
+ if (ok && (done || head) && size) ok = await flush();
2039
2052
  if (!ok) break;
2040
2053
  if (done) {
2041
- if (chunked) this.bridgeSend(c, CHUNKS_END);
2054
+ if (chunked) out(CHUNKS_END);
2055
+ else if (head) out(new Uint8Array(0));
2042
2056
  this.bridgeClock(c, c.timeout * 1000);
2043
2057
  return;
2044
2058
  }
@@ -2759,7 +2773,10 @@ const LATE = Symbol('late');
2759
2773
  // read in workerd and of the largest piece of a client chunk. A client chunk can be 2 KB (workerd), and each event
2760
2774
  // costs a turn of the VM.
2761
2775
  const UPLOAD_WINDOW = 256 * 1024;
2762
- const UPLOAD_PART = 16 * 1024;
2776
+ const UPLOAD_PART = 32 * 1024;
2777
+ // The time that the head of a request waits for the first bytes of its
2778
+ // body (bridgeUpload), in ms.
2779
+ const HEAD_WAIT = 20;
2763
2780
  const UPLOAD_READ = 64 * 1024;
2764
2781
 
2765
2782
  // The size bytes of the list parts, as one array.
@@ -1 +1 @@
1
- {"version": "0.1.0-rc.3", "sha256": "c9877e7ba31c2304aab52def9c0eee54ecf1f558d685a7acb4243c7fe2d0764e"}
1
+ {"version": "0.1.0-rc.4", "sha256": "686c7f660def2d824d0580717d02736b2add06db7f06becbd3d6ff66cd5d2711"}
@@ -1,3 +1,3 @@
1
1
  // The identity of this runtime (beam_com_wasm:runtime_id/1): the SHA-256 of
2
2
  // the output of "sha256sum app-com.js beam.mjs beam.wasm worker.js".
3
- export default '6f6148b7c684c1118b9033a0e895df5091e4e0d063aef93417fa882c5fe3ceee';
3
+ export default '3ef0d2bdd150e2ab64517e29e6e85fbf70382d878b9ccbca998a22f15b566dc0';
package/runtime/worker.js CHANGED
@@ -1972,8 +1972,10 @@ export class Vm {
1972
1972
  // body of the request, and the messages of a WebSocket. sent: the
1973
1973
  // host tells wasm_tcp what the client took (tcp_sent).
1974
1974
  this.event({ t: 'tcp_accept', id: this.listeners.get(port), conn: id, host: request.headers.get('cf-connecting-ip') ?? '0.0.0.0', port: 0, ack: !!body || upgrade, sent: true });
1975
- this.bridgeSend(c, bytes);
1976
- if (body) this.bridgeUpload(c, body, declared === null, maxBody);
1975
+ // With a body, the head goes in the event of the first part of the
1976
+ // body (bridgeUpload): a small body is one event, as before the parts.
1977
+ if (body) this.bridgeUpload(c, body, declared === null, maxBody, bytes);
1978
+ else this.bridgeSend(c, bytes);
1977
1979
  });
1978
1980
  }
1979
1981
 
@@ -1987,8 +1989,10 @@ export class Vm {
1987
1989
  // (or the rest of the body): one event for each part, not for each small
1988
1990
  // chunk of the client. A part goes when less than UPLOAD_WINDOW bytes are
1989
1991
  // unread in the VM (tcp_read of wasm_tcp): so a large body does not fill
1990
- // the memory of the VM, and a slow app slows the client.
1991
- async bridgeUpload(c, body, chunked, max) {
1992
+ // the memory of the VM, and a slow app slows the client. The head of the
1993
+ // request goes with the bytes of the first read of the client, with no
1994
+ // wait for UPLOAD_PART bytes.
1995
+ async bridgeUpload(c, body, chunked, max, head) {
1992
1996
  // workerd has readAtLeast. The option min of the standard BYOB read is
1993
1997
  // not safe: when the stream closes with fewer bytes than min, the read
1994
1998
  // errors the stream (Node.js 26, Chromium). So the other hosts use the
@@ -1997,7 +2001,7 @@ export class Vm {
1997
2001
  try { byob = body.getReader({ mode: 'byob' }); } catch {}
1998
2002
  if (byob && !byob.readAtLeast) { byob.releaseLock(); byob = null; }
1999
2003
  const reader = byob ?? body.getReader();
2000
- const read = byob ? () => byob.readAtLeast(UPLOAD_PART, new Uint8Array(UPLOAD_READ)) : () => reader.read();
2004
+ const read = byob ? (min) => byob.readAtLeast(min, new Uint8Array(UPLOAD_READ)) : () => reader.read();
2001
2005
  let total = 0;
2002
2006
  let part = [];
2003
2007
  let size = 0;
@@ -2011,13 +2015,22 @@ export class Vm {
2011
2015
  const bytes = part.length === 1 ? part[0] : join(part, size);
2012
2016
  part = [];
2013
2017
  size = 0;
2014
- this.bridgeSend(c, chunked ? chunk(bytes) : bytes);
2018
+ out(chunked ? chunk(bytes) : bytes);
2015
2019
  return true;
2016
2020
  };
2021
+ // Bytes to the app, after the head when it did not go yet.
2022
+ const out = (bytes) => {
2023
+ clearTimeout(alone);
2024
+ this.bridgeSend(c, head ? join([head, bytes], head.length + bytes.length) : bytes);
2025
+ head = null;
2026
+ };
2027
+ // A client that sends no body yet: the head goes alone after HEAD_WAIT
2028
+ // ms, so the app sees the request (and its own time limits run).
2029
+ const alone = setTimeout(() => { if (head && !c.done && !this.dead) out(new Uint8Array(0)); }, HEAD_WAIT);
2017
2030
  try {
2018
2031
  for (;;) {
2019
2032
  // A read can end the stream with its last bytes (done and a value).
2020
- const { value, done } = await read();
2033
+ const { value, done } = await read(head ? 1 : UPLOAD_PART);
2021
2034
  if (c.done || this.dead) break;
2022
2035
  if (value?.length) {
2023
2036
  total += value.length;
@@ -2035,10 +2048,11 @@ export class Vm {
2035
2048
  size += piece.length;
2036
2049
  if (size >= UPLOAD_PART) ok = await flush();
2037
2050
  }
2038
- if (ok && done && size) ok = await flush();
2051
+ if (ok && (done || head) && size) ok = await flush();
2039
2052
  if (!ok) break;
2040
2053
  if (done) {
2041
- if (chunked) this.bridgeSend(c, CHUNKS_END);
2054
+ if (chunked) out(CHUNKS_END);
2055
+ else if (head) out(new Uint8Array(0));
2042
2056
  this.bridgeClock(c, c.timeout * 1000);
2043
2057
  return;
2044
2058
  }
@@ -2759,7 +2773,10 @@ const LATE = Symbol('late');
2759
2773
  // read in workerd and of the largest piece of a client chunk. A client chunk can be 2 KB (workerd), and each event
2760
2774
  // costs a turn of the VM.
2761
2775
  const UPLOAD_WINDOW = 256 * 1024;
2762
- const UPLOAD_PART = 16 * 1024;
2776
+ const UPLOAD_PART = 32 * 1024;
2777
+ // The time that the head of a request waits for the first bytes of its
2778
+ // body (bridgeUpload), in ms.
2779
+ const HEAD_WAIT = 20;
2763
2780
  const UPLOAD_READ = 64 * 1024;
2764
2781
 
2765
2782
  // The size bytes of the list parts, as one array.