# ferridriver-jsstd
Vendored subset of [awslabs/llrt](https://github.com/awslabs/llrt) (Apache
License 2.0), providing the WHATWG Streams implementation, the `node:os`
module, and the pieces they depend on for the ferridriver QuickJS runtime.
Upstream: `0.9.0-beta`, re-synced against `awslabs/llrt@0a10758` (main,
2026-09-06). Every module is taken from that one commit.
| `llrt_utils` | `utils` |
| `llrt_context` | `context` |
| `llrt_exceptions` | `exceptions` |
| `llrt_events` | `events` |
| `llrt_abort` | `abort` |
| `llrt_encoding` | `encoding` |
| `llrt_buffer` | `buffer` |
| `llrt_json` | `json` |
| `llrt_crypto` | `crypto` |
| `llrt_os` | `os` |
| `llrt_fs` | `fs` |
| `llrt_path` | `pathutil` |
| `llrt_stream_web` | `stream_web` |
| `llrt_zlib` | `zlib` |
| `llrt_compression` | `compression`|
| `llrt_string_decoder` | `string_decoder` |
| `llrt_perf_hooks` | `perf_hooks` |
| `llrt_tty` | `tty` |
| `llrt_navigator` | `navigator` |
| `llrt_url` | `url` |
| `llrt_util` | `text` |
| `llrt_test` | `test` (dev) |
`llrt_stream` is NOT vendored, and not because we chose against it: llrt has
no node `stream` module. `llrt_stream` registers no `ModuleDef` and answers
no specifier — it is an internal trait library (`Readable` / `Writable` /
`SteamEvents`) that llrt's own `fs`, `net` and `child_process` implement.
`llrt_stream_web` is the module, and it is `stream/web`. There is likewise
no `querystring` anywhere in llrt.
The rest of llrt — its hyper/fetch stack, timers, console — is deliberately
not vendored: ferridriver has its own, over `reqwest`. `fs` is vendored
because it IS Node's `fs`, which is what a suite expects; only the Rust
path helpers of `llrt_path` come with it (`pathutil`) — the `path` MODULE
stays ferridriver's. `os` is vendored
because ferridriver has nothing equivalent and the module is pure host
introspection with no overlap with the automation stack. From `llrt_util`
only the four text codecs are taken (`TextEncoder`, `TextDecoder` and their
stream forms); its `format` / `inherits` / `inspect` are `node::util`'s,
which are richer.
## What a host installs
Two entry points, and nothing else to remember:
- `jsstd::init(ctx)` — every global this crate provides: `DOMException`,
`Event` / `EventTarget`, `AbortController` / `AbortSignal`, the Streams
surface, `Buffer` / `Blob` / `File`, `crypto`, `TextEncoder` /
`TextDecoder` (+ their stream forms), `URL` / `URLSearchParams`, `atob` /
`btoa`, `structuredClone` and `performance`.
- `jsstd::modules::modules()` — every Node / web MODULE it serves, each
entry carrying its specifiers, the `ModuleDef` the ES loader declares,
and the object `require()` returns: `path`, `buffer`, `os`, `util`,
`events`, `assert` (+ `/strict`), `url`, `process`, `timers` (+
`/promises`), `crypto`, `zlib`, `string_decoder`, `perf_hooks`, `tty`,
`stream/web`. The host merges that list into its own loader,
its `require` table and its bundler's external list, so the three
cannot drift apart.
## `src/node/` — ferridriver-authored
Not everything Node exposes has a usable upstream in llrt. `llrt_util` is
`TextEncoder`/`TextDecoder` plus `format` and `inherits` (no `promisify`, no
`inspect`, no `types`), and `llrt_assert` is a single `ok`. Those modules are
written here instead, under `src/node/`, so the runtime still has exactly one
implementation of each surface:
| `node::inspect` | The `util.inspect` / `util.format` renderer, moved out of `ferridriver-script`'s `console` so `console.log`, `util.format` and `util.inspect` cannot drift apart |
| `node::deep_equal` | Structural equality for `util.isDeepStrictEqual` (and `assert.deepStrictEqual` when it lands) |
| `node::util` | The `util` module |
| `node::assert` | The `assert` module (upstream `llrt_assert` is a single `ok`) |
| `node::process` | A sandbox-safe `process` — inert identity and timing, with `env` and `cwd()` supplied by the host — and its module form |
| `node::timers` | The module form of `web::timers`, plus `timers/promises` |
| `node::path` | The `path` module, moved out of `ferridriver-script`'s `node_compat` |
| `node::bytes` | The one JS-value-to-`Vec<u8>` walk: `BufferSource`, `Buffer`, byte arrays, encoded strings. `crypto`, the compression streams, `Buffer.from` and `setInputFiles` all read through it — there were three separate walks before |
`src/node/` carries its own `rustfmt.toml` re-enabling formatting (the crate
disables it for the vendored subtree) and follows the repo's house style. It
is compiled under this crate's relaxed lints because pedantic's
`needless_pass_by_value` cannot be satisfied by an rquickjs callback, which
must take owned JS values.
## `src/web/` — ferridriver-authored
Web-platform globals llrt has no upstream for. Same formatting rules as
`src/node/`.
| `web` (`mod.rs`) | `atob` / `btoa` (the WHATWG forgiving-base64 algorithm, which `base64::STANDARD` does not implement), `structuredClone`, and `performance.now()` / `timeOrigin` over a monotonic base. `llrt_buffer`'s module form reads `atob` / `btoa` off the globals, so installing them here is what makes `require('buffer').atob` resolve |
| `web::form_data` | `FormData`. It holds entries and nothing else: a host serializes them with its own multipart writer and hands parsed bodies back through `from_entries` |
| `web::compression` | `CompressionStream` / `DecompressionStream` (gzip, deflate, deflate-raw) over the vendored `TransformStream` |
| `web::timers` | `setTimeout` / `setInterval` / `clearTimeout` / `clearInterval` / `setImmediate` / `queueMicrotask`. NOT installed by `init`: a host supplies a `CallbackPolicy` so ambient state (ferridriver carries an `allow.net` grant) survives from arming the timer to running the callback |
| `web::blob_bytes` | The bytes-and-type read of a `Blob` / `File` value |
| `web::js_iterator` | The live-iterator protocol object `FormData`'s `entries` / `keys` / `values` return |
## Keeping it re-syncable
Sources are kept byte-close to upstream, including upstream's 4-space
formatting, so a re-sync against a newer llrt stays a mechanical diff. The
crate therefore does **not** inherit the workspace lints (see its
`Cargo.toml`), and `cargo fmt` must not be pointed at it.
Re-sync recipe (from a checkout of llrt):
```sh
for m in utils:libs/llrt_utils context:libs/llrt_context \
zlib:modules/llrt_zlib string_decoder:modules/llrt_string_decoder \
perf_hooks:modules/llrt_perf_hooks tty:modules/llrt_tty \
navigator:modules/llrt_navigator compression:libs/llrt_compression \
encoding:libs/llrt_encoding exceptions:modules/llrt_exceptions \
events:modules/llrt_events abort:modules/llrt_abort \
os:modules/llrt_os buffer:modules/llrt_buffer \
fs:modules/llrt_fs \
json:libs/llrt_json crypto:modules/llrt_crypto \
url:modules/llrt_url \
stream_web:modules/llrt_stream_web; do
name="${m%%:*}"; path="${m##*:}"
cp -R "$LLRT/$path/src" "src/$name" && mv "src/$name/lib.rs" "src/$name/mod.rs"
done
# `text` takes only llrt_util's four codec files; its own mod.rs stays.
for f in text_encoder text_decoder text_encoder_stream text_decoder_stream; do
cp "$LLRT/modules/llrt_util/src/$f.rs" "src/text/$f.rs"
done
# per-module first, then the cross-crate rewrite (BSD sed has no \b — use perl)
for name in utils context encoding exceptions events abort os buffer json crypto url text stream_web \
zlib string_decoder perf_hooks tty navigator compression; do
find "src/$name" -name '*.rs' | while read -r f; do
perl -pi -e "s/\bcrate::/crate::${name}::/g" "$f"
done
done
find src -name '*.rs' | while read -r f; do
perl -pi -e 's/\bllrt_([a-z_]+)/crate::$1/g' "$f"
done
```
Then re-apply the local deltas below.
## Local deltas
Everything here is a fix or a visibility widening, never a behaviour change
for ferridriver's convenience. Upstream candidates.
0. **Upstream regressions we do NOT take.** Still true at the 2026-09-06 main sync:
upstream still ships the two transform-stream bugs listed in deltas 2
and 3 below — and has since changed
`transform_stream_error_writable_and_unblock_write` to take `_e` and
ignore it, moving further from the spec. A future re-sync must keep
OUR versions of `stream_web/transform/{controller,stream}.rs`,
`stream_web/writable/mod.rs` and the visibility widenings; taking
upstream wholesale reintroduces a hung `read()` and a `JS_FreeRuntime`
assertion at teardown.
1. **`abort/abort_signal.rs`** — the `sleep-tokio` arm imports
`CtxExtension` from `llrt_utils::ctx`, where it does not exist; it lives
in `llrt_context`. Repointed at `crate::context`. (Upstream only builds
the default `sleep-timers` arm, which is why this never surfaced there.)
2. **`stream_web/transform/controller.rs`** —
`TransformStreamDefaultControllerPerformTransform` was missing spec step
3: reacting to the transform promise's rejection by erroring the stream.
A `transform()` that threw left both sides live, so a pending
`reader.read()` never settled.
3. **`stream_web/transform/controller.rs`** —
`TransformStreamErrorWritableAndUnblockWrite` was missing
`WritableStreamDefaultControllerErrorIfNeeded`, so an errored transform
left its writable in the `"writable"` state with an unresolved write
request. Added `stream_web::writable::writable_stream_error_if_needed`
for it.
4. **Visibility widenings only** — `SizeAlgorithm` / `SizeValue` /
`SizeFunction` / `NativeSizeFunction` from `pub(super)` to `pub(crate)`,
and `writable_stream_default_controller_error` to `pub(crate)`. Upstream
these were crate-visible because each module was its own crate; nesting
them under one crate narrowed them below what their own public API needs.
5. **Feature gates** — `sleep-tokio` is on by default. `sleep-timers` is
deliberately *not* a Cargo feature (the timers module is not vendored, so
`--all-features` would otherwise enable an arm that cannot compile); it is
declared to rustc as a known-but-never-set cfg via `check-cfg` in
`Cargo.toml`, which keeps the upstream `cfg` arms compiling out silently.
7. **`rquickjs` `half` feature** — enabled because the synced
`utils/bytes.rs` and `stream_web/readable/byob_reader.rs` handle
`Float16Array`. Without it `PredefinedAtom::Float16Array` does not
exist and `f16` has no `TypedArrayItem` impl.
6. **Tests** — `abort::abort_signal::tests::test_abort_signal` is no longer
gated on `sleep-timers`, so it covers the `sleep-tokio` path we build.
Two regression tests were added to `stream_web/transform/tests.rs` for
deltas 2 and 3.
8. **`os/mod.rs` — no Windows arm.** `llrt_os`'s `windows.rs` is not
vendored: ferridriver targets macOS and Linux, and that arm needs four
Windows-only dependencies (`whoami`, `windows-registry`,
`windows-result`, `windows-version`).
9. **`os/unix.rs` — `getpwuid_r` instead of the `users` crate.** Upstream
read the login name and shell through `users` 0.11, which has been
unmaintained since 2021, and at the 2026-09-06 sync moved to `uzers`
0.12, the maintained fork. The replacement calls `getpwuid_r` directly —
the same call either crate makes — including its ERANGE grow-the-buffer
protocol, so the one-line upstream swap is not taken and neither crate
is a dependency here.
10. **`os/statistics.rs` — real CPU times.** Upstream returns
`times: { user: 0, nice: 0, sys: 0, idle: 0, irq: 0 }` for every CPU,
with the comment "cannot be obtained at this time". sysinfo does not
expose them, but the kernel does: `/proc/stat` on Linux and
`host_processor_info` on macOS, which is where libuv reads them for
Node. Ticks are converted to milliseconds through `_SC_CLK_TCK`.
Darwin does not account interrupt time, so `irq` stays 0 there — as
it does in libuv.
11. **`os/mod.rs` — `fill()` / `os_object()` split.** Upstream fills the
module's default export inline inside `evaluate`. ferridriver serves
every native module twice, as an ES module and as a synchronous
`require()` namespace, and its loader requires both to read from one
place, so the body moved into a function.
12. **`os` feature gates.** Upstream's `system` / `statistics` / `network`
features are declared here too (all on by default) so the `#[cfg]`
arms stay exactly as upstream wrote them.
## Known gaps against Node
- `os.constants` (signal, errno, priority and dlopen tables) is not
implemented upstream and is not added here.
- `networkInterfaces()` marks link-local and multicast addresses
`internal: true`; Node marks only loopback interfaces internal.
13. **`buffer/blob.rs` and `buffer/file.rs` — three fixes.** Both files
ARE vendored and `init` defines both classes (an earlier version of
this note said neither was taken). `Blob::stream`
copies the bytes out before building the pull closure: upstream
captures the JS `ArrayBuffer` in a native closure, a cycle the
collector cannot see, which trips `JS_FreeRuntime`'s
`list_empty(&rt->gc_obj_list)` assertion at teardown.
`File::from_bytes` goes through `Blob::from_bytes` rather than
`into_js`, which made a JS array of numbers that `new Blob([...])`
stringified (`File.from_bytes(b"hi")` read back as `"104105"`).
And `buffer/mod.rs` chains `File.prototype` to `Blob.prototype` after
defining both classes, since rquickjs classes do not inherit and
upstream leaves `file instanceof Blob` false.
14. **`buffer/class.rs`** is upstream's `buffer.rs`, renamed. A `buffer`
module inside a `buffer` module trips `clippy::module_inception`,
which is on by default and the repo's gate runs `-D warnings`.
15. **`buffer/mod.rs` — `equals` and `toJSON`.** Node defines both on
`Buffer.prototype`; upstream defines neither, and the hand-written
class this vendoring replaced had both, so not adding them would be a
regression. Added after `set_prototype` rather than inside the
vendored file, so `class.rs` stays a mechanical diff. Upstream
candidates.
16. **`llrt_encoding`'s build script is not vendored.** It only calls
`llrt_build::set_nightly_cfg()`; this repo pins stable. As of the
2026-09-06 sync upstream has dropped its last `rust_nightly` arm
(`bytes_to_utf16_string` now uses the stable `as_chunks`), so no
vendored file reads either cfg; `rust_nightly` and `nightly` stay
declared as known-but-never-set cfgs in `Cargo.toml` so a future
upstream arm compiles out silently rather than warning.
## Known gaps against Node — `Buffer`
`Buffer` is a real `Uint8Array` subclass, so every typed-array method
works and index access reads bytes. Missing against Node: the
string-aware overrides of `includes` / `indexOf` / `lastIndexOf` / `fill`
(the `Uint8Array` versions are inherited, so they take byte values, not
strings), `swap16` / `swap32` / `swap64`, `compare`, and `Buffer.poolSize`.
17. **`crypto/provider/{ring,openssl,graviola}.rs` are not vendored.**
Only the pure-Rust provider (`crypto-rust`, upstream's own default) is
taken; the other three back-ends would each add a system dependency.
Their feature names are declared as known-but-unset cfgs. Upstream's
`_modern-webcrypto` marker (ML-DSA, ML-KEM, the hybrid KEMs,
ChaCha20-Poly1305, SHA-3 / cSHAKE / TurboSHAKE, `supports`,
`getPublicKey`, `encapsulate*` / `decapsulate*`) is declared and on:
every upstream provider enables it, and its back-ends (`ml-dsa`,
`ml-kem`, `chacha20poly1305`, `sha3`, `shake`, `cshake`, `keccak`,
`sponge-cursor`, `ctutils`) are all pure Rust. `provider/modern.rs`
is provider-independent upstream and is vendored as is.
18. **`crypto` / `json` macro imports.** `iterable_enum` and `str_enum` are
`#[macro_export]`ed, so they live at the crate root rather than under
`utils` — the import lines are repointed at `crate::`.
19. **Hash crates keep their `oid` feature.** `sha1` / `sha2` / `md-5` are
taken with `oid` (and `aes-gcm` with `hazmat`): PKCS#1 v1.5 signing
needs `AssociatedOid`, and WebCrypto allows 32- and 64-bit GCM tags,
which are gated behind those features in the 0.11 releases.
20. **`url/url_search_params.rs` — a non-string, non-object init.**
Upstream ignores it, so `new URLSearchParams(null)` and
`new URLSearchParams(42)` both build an EMPTY query. WebIDL's init
union is not nullable, so anything that is neither a sequence nor a
record converts to USVString: the queries are `null` and `42`, which
is what every browser engine produces. Only `undefined` (the argument
omitted) means empty.
23. **`encoding/mod.rs` — `windows-1252` / `latin1` / `ascii` are real.**
Upstream folds `Windows1252` into the UTF-8 arm in every direction,
so `new TextDecoder('windows-1252').decode([0xE9])` answered U+FFFD
and `Buffer.from('é', 'latin1')` produced the two UTF-8 bytes rather
than one. Worse, one label map served both consumers, which cannot
be right: Node's `latin1` is ISO-8859-1 and its `ascii` masks the
high bit, while the WHATWG Encoding Standard maps BOTH labels to
`windows-1252`. There are now two maps — `Encoder::from_str` for
Buffer, `Encoder::from_web_label` for `TextDecoder` — and three
single-byte variants (`Windows1252` with the real 0x80-0x9F index,
`Latin1`, `Ascii`) implemented in both directions.
22. **`url/mod.rs` — `fileURLToPath` decodes and validates.** Upstream
strips the `file://` prefix and hands the rest to `PathBuf`: the
scheme is never checked, a host is silently swallowed, a query or
fragment stays in the path, and percent-escapes are NOT decoded, so
`file:///tmp/a%20b.txt` names a file whose name literally contains
`%20`. Node checks the scheme, refuses a host it cannot address
locally, drops query and fragment, decodes the escapes and refuses
an ENCODED separator (which would otherwise change which file is
named).
21. **`url/url_class.rs` — `urlToHttpOptions` matches Node's shape.**
Upstream reports `port` as a STRING, omits `search` / `hash` when
they are empty, keeps the brackets on an IPv6 `hostname`, and joins
the raw percent-encoded credentials into `auth`. Node reports a
numeric port, always sets `search` and `hash`, hands over a bare IPv6
host (what a socket connect takes) and `decodeURIComponent`s the
credentials. `URL::inner_url` was upstream's only reader for the
omitted-hash branch and goes with it; the `percent-encoding` dep is
for the credential decode (`url` keeps that crate private).
24. **`fs/mod.rs` — `existsSync`.** Node has it and a large share of real
code calls it; upstream ships neither it nor a callback API, so
without it the only way to ask whether a file is there is to catch a
`stat` rejection.
25. **`fs/mod.rs` — one namespace per VM.** Upstream builds a fresh
exports object per module evaluation. Node answers the SAME object
for `require("fs")`, `require("node:fs")` and `fs.promises` vs
`require("fs/promises")`, so the namespaces are built once per
context and remembered; the `fs` global is that object too. Without
it, identity comparisons are false and a caller who patches a method
patches a copy nobody else sees.
26. **`zlib` — the codec back-ends taken.** Upstream's default is
`compression-c`, which links a system zlib-ng, brotli and zstd. This
crate takes the pure-Rust back-end wherever one exists
(`flate2/rust_backend`, which is also what `CompressionStream`
already used, and the `brotli` crate) and `zstd-c` for zstd, which
upstream's own manifest says has no pure-Rust implementation. The
`zstd` crate vendors and compiles the C rather than needing one
installed, so the build still has no system dependency — the same
posture as `rquickjs-sys` compiling QuickJS. All six upstream feature
names are declared, so its `#[cfg]` arms stay exactly as written.
27. **`compression/streaming.rs` is not vendored.** Its only consumer
upstream is `llrt_fetch`'s response decoder, and this crate does not
vendor `llrt_fetch` (ferridriver's `fetch` is its own, over
`reqwest`). Carrying it would be dead code that also trips
`clippy::large_enum_variant` — `StreamingDecoder`'s zstd variant is
~336 bytes larger than the next — and boxing a variant to satisfy a
lint in code nothing calls is worse than not taking the file. Same
reasoning as `buffer/blob.rs`, `crypto/provider/ring.rs` and
`os/windows.rs`.
28. **`zlib/codec.rs` and `string_decoder/decoder.rs`** are upstream's
`zlib.rs` and `string_decoder.rs`, renamed. A module with its
parent's name trips `clippy::module_inception`, which is on by
default and the repo's gate runs `-D warnings`. Same fix as delta 14.
29. **`zlib/{brotli,codec,zstd}.rs` — macro imports.** `define_sync_function`
and `define_cb_function` are `#[macro_export]`ed, so they live at the
crate root rather than under `zlib`. The `use super::{...}` lines are
split: the two macros come from `crate::`, the plain items still from
`super::`. Same cause as delta 18.
30. **`perf_hooks` — the module only, not the global.** Upstream's `init`
installs its own `Performance` class on `globalThis`, and taking it
would be a regression: `Performance::now` reads
`llrt_utils::time::now_nanos`, which is `SystemTime::now()` — the
WALL clock. High Resolution Time exists to give a monotonic reading,
so upstream's `now()` steps backwards whenever the system clock
does, and `saturating_sub` clamps that to `0` rather than surfacing
it. `web::init`'s reads `Instant::elapsed`, which is monotonic by
construction. Upstream also leaves `origin_nanos()` at `0` until a
host calls `time::init()`, and an unset origin makes `now()` return
the whole Unix epoch in milliseconds.
Upstream's MODULE body only reads `globalThis.performance` and
re-exports it, so dropping `init` (and `performance.rs` with it)
leaves `perf_hooks` serving ferridriver's own.
The two things upstream's class had over the old plain object —
`toJSON()` and being a real class instance — are in `web::performance`
now, along with the User Timing and Performance Timeline surface
neither side had. See "`performance`" below.
31. **`navigator` — this runtime's name.** Upstream hardcodes
`userAgent` to `llrt <version>`. Shipping that verbatim would answer
every user-agent sniffer with the wrong runtime. It reads
`ferridriver/<version>` here, which is the shape Node 21+ uses.
Worth knowing before relying on it: a `navigator` global is still how
some libraries decide they are in a browser, the mirror image of the
`process.versions.node` check. It is Node parity, not a browser
claim, but a package that misroutes on it is misrouting for this
reason.
32. **`stream/web` registered as a specifier.** The implementation was
vendored from the start but only ever installed as globals, so
`import { ReadableStream } from 'node:stream/web'` — Node's own name
for it, and how a library reaches it without assuming a browser —
resolved to nothing. `modules.rs` now serves it, reading the classes
back off the globals so there is still one implementation.
## `performance`
`web::performance` is ferridriver's, not vendored, and covers three
specs rather than the `now()` / `timeOrigin` pair it started as:
- **High Resolution Time** — `now()`, `timeOrigin`, `toJSON()`.
`now()` reads `Instant::elapsed`; the wall clock appears exactly once,
as `timeOrigin`, which is what the monotonic readings are relative to.
- **User Timing** — `mark()`, `measure()`, `clearMarks()`,
`clearMeasures()`, and the `PerformanceMark` / `PerformanceMeasure`
classes with their `detail`. `measure` implements all three overloads
(bare name, start-mark, options bag) and refuses the two combinations
the spec calls out: an options bag together with a trailing `endMark`,
and `start` + `end` + `duration` all at once.
- **Performance Timeline** — `getEntries()`, `getEntriesByName()`,
`getEntriesByType()`, sorted chronologically by `startTime` rather
than by insertion, because `mark(name, { startTime })` can backdate an
entry. The sort is stable, so entries sharing a `startTime` keep the
order they were recorded in.
`PerformanceMark` and `PerformanceMeasure` chain their prototype to
`PerformanceEntry`, so `mark instanceof PerformanceEntry` holds.
Constructibility follows the IDL: `PerformanceMark` takes
`(name, options)` and does NOT buffer (only `performance.mark()`
records), while `PerformanceEntry`, `PerformanceMeasure` and
`Performance` throw `Illegal constructor`.
`performance.now()` and `process.hrtime()` count from ONE base
(`performance::monotonic_base`), so the two are correlatable the way
Node's are — it derives both from a single libuv hrtime. Two separate
`Instant::now()` calls would put a constant, invisible skew between
them; a test asserts they agree within a millisecond.
Not implemented: `PerformanceObserver` (it needs a task-queue hook this
runtime has no equivalent of), the resource and navigation entry types
(no document), Node's `eventLoopUtilization` / `nodeTiming`, and a
buffer size limit — nothing evicts, so a program marking in a hot loop
grows the buffer until it calls `clearMarks`.
## `Intl` is absent, and llrt cannot fill it
QuickJS-ng as vendored by `rquickjs-sys` contains no ECMA-402 at all —
no `Intl` object, and no build flag that would add one. `react-intl` and
anything like it fails with `Intl is not defined`.
`llrt_intl` does not close this. It is `Intl.DateTimeFormat` plus
`supportedValuesOf` and `Date.prototype.toLocaleString`, aimed at
timezone support, and it carries a `jiff` dependency and ~3k lines of
bundled CLDR data. There is no `NumberFormat`, `PluralRules` or
`Collator` — which is the part a formatting library actually reaches
for.
Until that changes, a suite that needs `Intl` supplies a JS polyfill
through `[bundler.alias]`, which keeps the choice (and its weight) in
the extension that needs it.
33. **`crypto/subtle/digest.rs` — a synchronous validation failure is
kept.** Upstream validates the algorithm before returning the
future, so the exception is thrown while `subtle_digest` is still
returning `Ok(future)`; by the time the future runs, the pending
exception is gone and the promise rejects with an uninitialized
value (`typeof e === "unknown"`). The thrown value is now taken with
`ctx.catch()` at that point and re-thrown inside the future, where
the rejection is built. Upstream candidate.