動画で読む
結論 — 今回の v26.2.0 は post-quantum 暗号と Temporal 移行が同時に来た日
結論を先に書きます。2026-05-20 13 UTC(日本時間 5/20 夜 22)公開の Node.js v26.2.0 で実務的に効くのは、stream.compose の stable 化、fs.Stats への Temporal.Instant 追加、BoringSSL 経由で Web Cryptography に ML-DSA(旧 CRYSTALS-Dilithium)と ML-KEM(旧 CRYSTALS-Kyber)が配線されたことの 3 つ です。残りの目玉は HTTP の writeInformation で 1xx 系ステータスを任意に返せるようになった穴埋めです。
私は今朝、近所のドトールで MacBook Air M2 を開いてリリースノートを読み、まず ML-DSA / ML-KEM の行で手が止まりました。NIST が PQC(Post-Quantum Cryptography、量子計算機耐性のある暗号)標準を確定したのが 2024-08、それから 2 年弱で Web Crypto の標準 API に降りてきたわけで、サーバサイド JS 側の足回りが想像より早く整いつつあるな、というのが第一印象です。Node を nvm で 26.2.0 に上げ、4 つの変更を順に触ったメモを書きます。
stream.compose stable — 「もう experimental ではない」が意外と重い
要点を先に書きます。stream.compose が experimental から stable に格上げされました。これまで本番コードで Promise pipeline と組み合わせて使うのは少しだけ緊張感がありましたが、deprecation 心配なしで合成パイプラインを書ける状態になります。
stream.compose は、複数の Readable / Writable / Duplex / Transform をひと続きの Duplex に合成する API です。pipeline が「実行する」関数なのに対し、compose は「合成して返す」関数で、その返り値をさらに別の pipeline に渡したり、for await で消費したりできます。
私が直近で書いた CSV 変換バッチでは、compose(csvParse(), filterRow(), toNdjson()) のような形で 3 つの Transform を 1 つの Duplex に詰めて、Sidekiq 相当の worker から呼んでいました。experimental 印が外れたので、内部レビュー用の README から「stream.compose は experimental につき将来変更の可能性あり」の注意書きを 1 行消せました。地味ですが、stable バッジが付くと社内コードレビューの説明コストが落ちます。
ちなみに本変更は docs PR のみで実装変更は無く、コミット 189d43a193 だけです。挙動が変わるわけではないので、上げた瞬間のリグレッションリスクはほぼゼロでした(こういう「docs だけ更新で stable 宣言」のリリースは、内部実装の安定度を観測してから打つ性質のもので、Matteo Collina の判断が背景にあると思うと安心感があります)。
fs.Stats への Temporal.Instant — Date 依存コードを書き換える前哨
要点を先に書きます。fs.statSync などが返す Stats / BigIntStats に、Temporal.Instant 版のタイムスタンプアクセサ(mtimeInstant、atimeInstant、ctimeInstant、birthtimeInstant)が追加されました。既存の mtime (Date) は残るので破壊変更ではありませんが、Temporal 時代に向けた前哨戦という位置付けです。
これまで Stats.mtime は JavaScript の Date を返していました。Date はミリ秒精度しか持たず、タイムゾーンの扱いも雑なので、ファイルシステムが秒以下まで持つ環境ではミリ秒丸めで精度を失っていました。Temporal.Instant はナノ秒精度なので、Linux の statx 等が返す tv_nsec までそのまま受け取れます。
私のユースケースだと、mtime をキーに RSS / Atom フィードの ETag を生成しているスクリプトがあって、同一秒内に 2 回書き換えた時の重複検出が Date 比較だと壊れていました。Temporal.Instant に切り替えると Instant.compare でナノ秒比較できるので、その手の競合検出を素朴に書けるようになります。
実装は PR #60789 で、semver-minor 扱いです。Temporal 自体は別途 Stage 4 を進行中ですが、Node 側のサポートは部分的にこれが先行する形になります。ここで先回りして mtime を mtimeInstant に書き換えるかは判断が分かれます。私の方針は「新規コードのみ Instant、既存は Date のまま動かす」で、来年あたり Date accessor が deprecated になる気配が出たらまとめて switch、と決めました。一度に全部書き換える派の人は、テストで Date をモックしている箇所が散らばっていないかを先に grep しておく方が事故が少ないです。
writeInformation で 1xx ステータスコード — Early Hints の隣のおはなし
要点を先に書きます。http.ServerResponse#writeInformation(statusCode, headers) が追加され、1xx 系の任意ステータスコードをレスポンス本体の前に送れるようになりました。これまで writeEarlyHints で 103(Early Hints)は送れていましたが、102(Processing)を含む任意の 1xx を送る公式 API が無かった穴を埋めるものです。
具体的なユースケースは、HTTP/2 + Express 上のオリジンサーバで Early Hints と並行して 102 Processing を link ヘッダ無しで返したい時とか、上流の CDN との中継で 1xx をそのまま通したい時とかです。私自身は今のところ 103 Early Hints しか使っていないので恩恵は薄いですが、CDN 側で 102 を解釈する仕様が増えてきたら触る場面が出そうです。
実装は PR #63155 で、既存の writeEarlyHints は内部的に writeInformation(103, hints) に置き換わっています。後方互換は維持されているので、既存コードを書き換える必要は無いです。semver-minor 扱いなので、minor 上げのタイミングで自動的に手に入ります。
Web Cryptography に ML-DSA / ML-KEM — Post-Quantum の足回りが Node に来た
要点を先に書きます。BoringSSL ビルド限定で、Web Cryptography API に ML-DSA(FIPS 204、署名)と ML-KEM(FIPS 203、鍵交換)、それから ChaCha20-Poly1305 と AES-KW が配線されました。Node.js 公式バイナリは OpenSSL ベースなので公式ビルドには入りませんが、自前 BoringSSL ビルドや、将来的に OpenSSL 4.x が PQC を載せた時の前哨として読むのが正しい温度感です。
NIST が ML-DSA(FIPS 204)と ML-KEM(FIPS 203)を確定したのが 2024-08-13 で、それから約 1 年 9 ヶ月で Web Crypto の API としてサーバサイド JS に降りてきた、というスピード感です。BoringSSL は Google 側が PQC を先に取り込んでいるので、Node 側はその API を Web Crypto の subtle.generateKey({ name: 'ML-DSA-65' }) 等に橋渡しした、という形になります。
私の運用では BoringSSL ビルドを常用していないので、今すぐ触れるわけではありません。でも「サーバサイド JS の標準 API で PQC が呼べる将来」が現実的な視野に入ってきた点で、暗号方式の移行計画を頭の片隅に置く理由になります。具体的には、TLS 層は 2025-2027 で順次 hybrid(X25519MLKEM768 等)に切り替わっていく見通しなので、アプリ層の長期保管データ(暗号化バックアップ等)について「今 AES-256-GCM で寝かしている暗号文を、いつ ML-KEM ハイブリッドに再封緘するか」の議論を始める時期です。
実装は PR #63255 と、ChaCha20-Poly1305 / AES-KW を含む関連コミット群です。OpenSSL ビルドの公式 Node では SubtleCrypto.supports('ML-DSA-65') が false を返すので、機能チェックを先に書いてから使う形になります(ところで、私は SubtleCrypto.supports という API が v22 で入ったことを今回のリリースノートを辿って初めて知り、ドキュメントを読まずに API を使い続ける癖を反省しました)。
もう 1 つ地味に効くのが、同じ minor で CryptoKey と KeyObject の内部スロットが harden された(PR #63111)ことと、raw key import の invalid 入力が reject されるようになった(PR #63134)ことです。これらは PQC 移行の地ならしとして、Web Crypto 周辺の防御線を一段引き締める変更で、依存しているライブラリが古い API を叩いていた場合に例外が増える可能性があります。アップデート後の起動時に CryptoKey 周りの例外ログが急増したら、依存ライブラリ側の API 使い方を見直すサインです。
まとめ — 「Temporal」と「PQC」がそろっと姿を見せた v26.2.0
要点を先に書きます。v26.2.0 は派手な単機能リリースではなく、Temporal 移行と post-quantum 暗号という 2 つの大潮流の前哨戦が同じ minor に入った地味な転換点です。stream.compose stable は実用的、HTTP の writeInformation は穴埋め、その背後で標準 API 越しに PQC が静かに歩み寄ってきた、という構図でした。
私の運用での当面の対応はこんな順序です。第一に nvm でローカルを 26.2.0 に上げる、第二に CSV 系のバッチで stream.compose の experimental 注意書きを消す、第三に新規スクリプトの mtime を mtimeInstant で書き始める、です。BoringSSL ビルドはまだ手を出しません(脱線ですが、ドトールで読み終わってクロワッサンを取りに行ったら半額時間帯が始まっていて、リリースノートと半額クロワッサンが揃った週末でした)。Temporal も PQC も「来た瞬間に全部書き換える」性質の変更ではないので、minor の節目に少しずつ取り込んでいくのが現実的だと思います。
Tags
よくある質問
- stream.compose の stable 化で挙動は変わりますか?
- 実装変更は無く、docs だけのリリース(コミット 189d43a193)です。これまでと同じ API で動きますが、experimental の注意書きが外れたので本番コードに採用する社内合意が取りやすくなります。アップデートに伴うリグレッションリスクは実質ゼロです。
- fs.Stats の mtimeInstant にすぐ書き換えるべきですか?
- 既存の mtime(Date)アクセサは残るので、急いで書き換える必要はありません。私の方針は「新規コードのみ Instant、既存は Date のまま」で、来年 Date accessor が deprecated になる気配が出てからまとめて switch する予定です。一度に書き換える派なら、Date をモックしている箇所を先に grep するのを勧めます。
- ML-DSA / ML-KEM は OpenSSL ビルドの Node でも使えますか?
- 使えません。今回の配線は BoringSSL ビルド限定で、公式バイナリは OpenSSL ベースなので SubtleCrypto.supports('ML-DSA-65') は false を返します。実コードでは機能チェックを先に書く形になります。将来的に OpenSSL 4.x が PQC を載せた段階で公式ビルドにも降りてくる見通しです。