v3 では TypeScript 向けに次の 2 つの破壊的変更があります。
SerialErrorCode — enum から const object + union type へ(ランタイム値は不変)。state$ の payload — フラットな文字列から、状態ごとの詳細を持つ discriminated union へ。本ガイドでは両方を説明します。エラーコードのランタイム文字列は変わりません(SerialErrorCode.READ_FAILED は引き続き 'READ_FAILED' です)。
import {
SerialError,
SerialErrorCode,
SerialSessionStatus,
type SerialSessionState,
} from '@gurezo/web-serial-rxjs';
session.state$.subscribe((state: SerialSessionState) => {
switch (state.status) {
case SerialSessionStatus.Connected:
console.log(state.portInfo);
break;
case SerialSessionStatus.Error:
console.error(state.error);
break;
}
});
session.errors$.subscribe((error) => {
if (error.is(SerialErrorCode.READ_FAILED)) {
console.error(error.context.cause);
}
});
SerialErrorCode const object| v2 | v3 |
|---|---|
export enum SerialErrorCode { ... } |
export const SerialErrorCode = { ... } as const + export type SerialErrorCode |
TypeDoc: enums/SerialErrorCode.html |
TypeDoc: variables/SerialErrorCode.html |
SerialErrorCode.BROWSER_NOT_SUPPORTED(他のメンバーも同様)error.code === SerialErrorCode.WRITE_FAILEDerror.is(SerialErrorCode.LINE_BUFFER_OVERFLOW) による context の narrowingswitch (error.code) { case SerialErrorCode.READ_FAILED: ... }import type { SerialErrorCode } from '@gurezo/web-serial-rxjs' のまま利用可能。enums/SerialErrorCode.html から variables/SerialErrorCode.html へ更新。.d.ts を解析するツール — 宣言形が enum から const + type alias に変わります。state$| v2 | v3 |
|---|---|
state$: Observable<'idle' | 'connected' | ...> |
state$: Observable<SerialSessionState>(discriminated union) |
SerialSessionState const(文字列リテラル) |
SerialSessionStatus const(文字列リテラル) |
state === SerialSessionState.Connected |
state.status === SerialSessionStatus.Connected |
state$ と portInfo$ / errors$ を手動で相関 |
connected に portInfo、error に SerialError を同梱 |
import { SerialSessionState } from '@gurezo/web-serial-rxjs';
session.state$.subscribe((state) => {
if (state === SerialSessionState.Connected) {
session.getPortInfo(); // 別途取得
}
});
import { SerialSessionStatus } from '@gurezo/web-serial-rxjs';
session.state$.subscribe((state) => {
switch (state.status) {
case SerialSessionStatus.Connected:
console.log(state.portInfo);
break;
case SerialSessionStatus.Error:
console.error(state.error);
break;
}
});
export const SerialSessionStatus = {
Idle: 'idle',
Connecting: 'connecting',
Connected: 'connected',
Disconnecting: 'disconnecting',
Unsupported: 'unsupported',
Error: 'error',
Disposed: 'disposed',
} as const;
export type SerialSessionState =
| { readonly status: typeof SerialSessionStatus.Idle }
| { readonly status: typeof SerialSessionStatus.Connecting }
| { readonly status: typeof SerialSessionStatus.Connected; readonly portInfo: SerialPortInfo }
| { readonly status: typeof SerialSessionStatus.Disconnecting }
| { readonly status: typeof SerialSessionStatus.Unsupported }
| { readonly status: typeof SerialSessionStatus.Error; readonly error: SerialError }
| { readonly status: typeof SerialSessionStatus.Disposed };
SerialSessionState を SerialSessionStatus に置き換える。state === SerialSessionState.X を state.status === SerialSessionStatus.X に置き換える。switch (state) を switch (state.status) に置き換える(または if で state.status を比較)。connected 時は state.portInfo を利用する(推奨 — portInfo$ と getPortInfo() は非推奨)。error 時は state.error を利用(fatal error は errors$ と同一インスタンス)。errors$ は引き続き利用可能です。portInfo$ と getPortInfo() は v3.x では引き続き利用可能ですが、非推奨です(§5 を参照)。isConnected$ は v3.x では引き続き利用可能ですが、非推奨です(§6 を参照)。originalError の非推奨化v3.0.0 では typed SerialError.context を導入しました。cause 系 error code では context.cause が原因エラーの canonical source です。
後方互換のため SerialError.originalError と constructor の legacy 第 3 引数は v3.x で残っていますが、非推奨です。次回 major version で削除予定です。
session.errors$.subscribe((error) => {
if (error.code === SerialErrorCode.READ_FAILED) {
console.error(error.originalError);
}
});
session.errors$.subscribe((error) => {
if (error.is(SerialErrorCode.READ_FAILED)) {
// error.context.cause は unknown — Error 以外の throw も保持
console.error(error.context.cause);
}
});
error.originalError を error.context.cause に置き換える(error.is(code) で narrowing してからアクセス)。new SerialError(code, message, cause) としていた場合は new SerialError(code, message, undefined, { cause }) に変更する。@deprecated 警告が出たら、上記パターンへ移行する。originalError は v3.x では引き続き利用可能です。context.cause が Error インスタンスの場合、originalError も同期して設定されます(legacy 利用者向け)。context.cause の型は unknown です(JavaScript では Error 以外も throw 可能なため)。destroy$ の非推奨化SerialSession は dispose$() と destroy$() の両方を公開しています。これらは同一関数であり、destroy$ は legacy エイリアスです。lifecycle terminology(dispose、disposed、SESSION_DISPOSED)はすでに dispose$ を canonical API として使用しています。
後方互換のため destroy$() は v3.x に残っていますが、非推奨です。次回 major version で削除予定です。
session.destroy$().subscribe({
complete: () => console.log('session destroyed'),
});
session.dispose$().subscribe({
complete: () => console.log('session disposed'),
});
session.destroy$() を session.dispose$() に置き換える。@deprecated 警告が出たら dispose$ へ移行する。dispose$ を使用する。destroy$ は v3.x では引き続き利用可能で、dispose$ と同じ実装に委譲します。portInfo$ / getPortInfo() の非推奨化v3.0.0 では state$ が discriminated union になりました。state.status が SerialSessionStatus.Connected のとき、state.portInfo がアクティブポートの SerialPort.getInfo() スナップショットの canonical source です。TypeScript の narrowing により、存在が型で保証されます。
portInfo$ と getPortInfo() は後方互換のため v3.x に残っていますが、非推奨で、次回 major version で削除予定です。これらは SerialPortInfo | null を返すため、接続状態とポート情報の関係を型で表現できません。
session.portInfo$.subscribe((portInfo) => {
if (portInfo) {
console.log(portInfo);
}
});
const snapshot = session.getPortInfo();
import { SerialSessionStatus } from '@gurezo/web-serial-rxjs';
session.state$.subscribe((state) => {
if (state.status === SerialSessionStatus.Connected) {
console.log(state.portInfo);
}
});
portInfo$ の購読を state$ に置き換え、state.status === SerialSessionStatus.Connected のとき state.portInfo を参照する。getPortInfo() を state$ の narrowing と state.portInfo に置き換える。@deprecated 警告が出たら、上記パターンへ移行する。state.portInfo を使用する。portInfo$ と getPortInfo() は v3.x では引き続き利用可能です。state.portInfo と値が同期します。errors$ は非推奨化の対象ではありません。lifecycle state ではなく、独立した error event channel です。isConnected$ の非推奨化v3.0.0 では state$ が discriminated union になりました。state.status が SerialSessionStatus.Connected のとき、TypeScript の narrowing により接続状態と state.portInfo などの state-specific データへ型安全にアクセスできます。
isConnected$ は Observable<boolean> として接続の真偽値だけを返すため、discriminated union が持つ型情報を失います。後方互換のため v3.x に残っていますが、非推奨で、次回 major version で削除予定です。
session.isConnected$.subscribe((isConnected) => {
if (isConnected) {
// session state is not narrowed
}
});
state$ narrowing)import { SerialSessionStatus } from '@gurezo/web-serial-rxjs';
session.state$.subscribe((state) => {
if (state.status === SerialSessionStatus.Connected) {
// state.portInfo and other connected fields are available
}
});
import { distinctUntilChanged, map } from 'rxjs';
import { SerialSessionStatus } from '@gurezo/web-serial-rxjs';
const isConnected$ = session.state$.pipe(
map((state) => state.status === SerialSessionStatus.Connected),
distinctUntilChanged(),
);
filter で connected state を narrowing する場合pipeline 内で portInfo など connected 専用フィールドにアクセスするには、filter() と isConnectedSessionState を組み合わせます。inline の filter((s) => s.status === SerialSessionStatus.Connected) では TypeScript の narrowing は行われません。
import { filter } from 'rxjs';
import { isConnectedSessionState } from '@gurezo/web-serial-rxjs';
session.state$
.pipe(filter(isConnectedSessionState))
.subscribe((state) => {
console.log(state.portInfo);
});
import { computed } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { SerialSessionStatus } from '@gurezo/web-serial-rxjs';
const sessionState = toSignal(session.state$);
const isConnected = computed(
() => sessionState().status === SerialSessionStatus.Connected,
);
isConnected$ の購読を state$ に置き換え、state.status === SerialSessionStatus.Connected で narrowing する。state$ から map / computed で derive する。@deprecated 警告が出たら、上記パターンへ移行する。state$ narrowing を使用する。isConnected$ は v3.x では引き続き利用可能です。state.status === SerialSessionStatus.Connected と値が同期します。state$ から derive してください。getCurrentPort() の削除SerialSession.getCurrentPort() は raw SerialPort を返す escape hatch でした。利用者が port.close() や writable.getWriter() を直接呼び出すと、session が管理する lifecycle と競合し、internal runtime invariant を破壊する可能性がありました。
利用状況監査(#437)の結果、本リポジトリ内のライブラリ・example コードに getCurrentPort() の実利用はなく、デバイス識別は state.portInfo で代替可能と判断し、public API から削除しました。
| 区分 | 結果 |
|---|---|
| ライブラリ本番コード | getCurrentPort() の呼び出しなし |
| example アプリ | テスト mock のみ |
| デバイス識別の代替 | state$ narrowing 後の state.portInfo(canonical) |
| signals(DTR/RTS 等) | 現時点で代替 API なし(将来の feature addition として検討) |
const port = session.getCurrentPort();
if (port) {
console.log(port.getInfo());
}
import { SerialSessionStatus } from '@gurezo/web-serial-rxjs';
session.state$.subscribe((state) => {
if (state.status === SerialSessionStatus.Connected) {
console.log(state.portInfo);
}
});
getSignals() / setSignals() など、raw port 経由でのみ可能だった操作には現時点で SerialSession 上の代替 API がありません。必要になった場合は別 Issue で first-class API の追加を検討します。
getCurrentPort() の呼び出しを削除する。state$ を SerialSessionStatus.Connected で narrowing し state.portInfo を使用する。SerialErrorCode runtime emission 監査public API contract として定義されている SerialErrorCode のうち、一部は v3.x の runtime implementation から emit されていませんでした。到達不能な error handling を防ぐため、全 19 code の emission coverage を監査し(#438)、結果を本セクションと 概念と設計メモ に反映しました。
| 分類 | 件数 | 説明 |
|---|---|---|
| Implemented | 17 | v3.x で runtime から emit される(または factory 時に throw される) |
| Reserved | 2 | public API に存在するが v3.x では emit されない。次回 major version で削除予定 |
| Code | 理由 | 代替 |
|---|---|---|
PORT_NOT_AVAILABLE |
現行実装は navigator.serial.requestPort のみ使用。getPorts 系 API 未実装のため emit 経路がない |
ポート取得失敗は PORT_OPEN_FAILED または OPERATION_CANCELLED を参照 |
OPERATION_TIMEOUT |
timeout / prompt detection / transaction API が未実装 | 該当なし(将来 API 追加時に再評価) |
v3.x では @deprecated 注記のみ付与し、runtime 値と export は維持します。削除は次回 major version に集約します。
| Code | emit 箇所 | fatal / non-fatal | context |
テスト |
|---|---|---|---|---|
BROWSER_NOT_SUPPORTED |
connect$(navigator.serial なし) |
non-fatal | undefined |
統合 |
PORT_OPEN_FAILED |
connect$(port.open() reject) |
fatal | { cause } |
統合 |
PORT_ALREADY_OPEN |
connect$('idle' / 'error' 以外) |
non-fatal | undefined |
統合 |
PORT_NOT_OPEN |
send$ / disconnect$(不正状態) |
non-fatal | undefined |
統合 |
READ_FAILED |
read pump エラー | fatal | { cause } |
統合 |
WRITE_FAILED |
send$ 書き込み失敗 |
non-fatal | { cause } |
統合 |
CONNECTION_LOST |
port.close() 失敗 / ストリーム切断 |
fatal | { cause } |
統合 |
INVALID_FILTER_OPTIONS |
createSerialSession factory |
throw | ValidationErrorContext |
単体 + 統合 |
OPERATION_CANCELLED |
requestPort ダイアログキャンセル |
fatal | { cause } |
統合 |
LINE_BUFFER_OVERFLOW |
lines$ tail 超過 |
non-fatal | { maxChars } |
統合 |
INVALID_RECEIVE_REPLAY_OPTIONS |
factory | throw | ValidationErrorContext |
単体 + 統合 |
INVALID_TERMINAL_BUFFER_OPTIONS |
factory | throw | ValidationErrorContext |
単体 |
INVALID_LINE_BUFFER_OPTIONS |
factory | throw | ValidationErrorContext |
単体 |
INVALID_CONNECTION_OPTIONS |
factory | throw | ValidationErrorContext |
単体 + 統合 |
RECEIVE_REPLAY_BUFFER_OVERFLOW |
receiveReplay$ 超過 |
non-fatal | { maxChars, bufferSize } |
統合 |
SESSION_DISPOSED |
dispose$ 後の connect$ / send$ |
fatal | undefined |
統合 |
UNKNOWN |
dispose / disconnect の分類不能 fallback | fatal | { cause } |
単体 |
fatal / non-fatal の判定は reportError 経由の ERROR_SEVERITY に従います。factory throw の INVALID_* code は reportError を通らず、呼び出し元に直接 throw されます。
PORT_NOT_AVAILABLE / OPERATION_TIMEOUT 向けの error handling を削除する(v3.x では到達しない)。PORT_OPEN_FAILED / OPERATION_CANCELLED で処理する。validation error(INVALID_*)への structured context 追加は #439 で実施済みです。message のパースではなく ValidationErrorContext(field、value、constraint、任意の filterIndex)を利用してください。
assertNever public export 監査assertNever は exhaustive switch checking 用の TypeScript utility です。package internal の exhaustiveness helper として追加されましたが(#394 / PR #410)、public export としても公開されていました。Web Serial / SerialSession domain API ではないため、利用状況を監査し(#440)、結果を本セクションと 概念と設計メモ に反映しました。
| 確認項目 | 結果 |
|---|---|
| package internal usage | session-runtime.ts のみ(assertNeverRuntime 経由) |
| examples usage | apps/ / libs/ に利用なし |
| documentation usage | canonical export 一覧(API_REFERENCE)に未掲載。MIGRATION ドキュメントにも未記載 |
| 公開履歴 | Phase A(#394)で src/internal/assert-never.ts として追加、index.ts から re-export |
assertNever は内部実装用 utility であり、canonical public API ではありません。SerialSessionState の exhaustive handling は switch (state.status) + SerialSessionStatus、または isConnectedSessionState による narrowing が推奨パターンです。
v3.x では @deprecated 注記のみ付与し、public export は維持します。削除は次回 major version に集約します。
import { assertNever } from '@gurezo/web-serial-rxjs';
session.state$.subscribe((state) => {
switch (state.status) {
case SerialSessionStatus.Connected:
console.log(state.portInfo);
break;
default:
assertNever(state);
}
});
switch (state.status) で全 case を網羅するか、RxJS では filter(isConnectedSessionState) で narrowing してください。exhaustiveness helper が必要な場合はアプリケーション側でローカル helper を定義します。
import {
SerialSessionStatus,
isConnectedSessionState,
type SerialSessionState,
} from '@gurezo/web-serial-rxjs';
function assertNever(value: never): never {
throw new Error(`Unexpected value: ${String(value)}`);
}
session.state$.subscribe((state: SerialSessionState) => {
switch (state.status) {
case SerialSessionStatus.Connected:
console.log(state.portInfo);
break;
case SerialSessionStatus.Idle:
case SerialSessionStatus.Connecting:
case SerialSessionStatus.Disconnecting:
case SerialSessionStatus.Unsupported:
case SerialSessionStatus.Error:
case SerialSessionStatus.Disposed:
break;
default:
assertNever(state);
}
});
@gurezo/web-serial-rxjs からの assertNever import を削除する。SerialSessionState の分岐は switch (state.status) + SerialSessionStatus を優先する。@deprecated 警告が出たら、上記パターンへ移行する。assertNever は v3.x では引き続き public export から利用可能です。次回 major version で削除予定です。
SerialSessionOptions は W3C SerialOptions 由来の connection fields と、web-serial-rxjs 固有の session feature options を 1 つの型として公開しています。TypeScript-first の domain model 整理の一環として、public type と generated documentation の表示を監査しました(#441)。
| 確認項目 | 結果 |
|---|---|
| existing assignability | 既存の createSerialSession({ ... }) 呼び出しは問題なし |
generated .d.ts |
public SerialConnectionOptions と internal SerialSessionConnectionFields が同一 Pick で重複 |
| TypeDoc readability | connection / feature fields が 1 つの flat list に混在し、hierarchy が internal 型名を表示 |
| readonly input compatibility | mutable 配列のまま維持。readonly 入力の assignability は regression test で確認 |
| examples | libs/examples-shared は SerialConnectionOptions['baudRate'] を既に利用。example apps の変更は不要 |
W3C SerialOptions drift detection |
connection fields は SerialConnectionOptions 経由で W3C 型から Pick。分離後も維持 |
型安全性に問題はありませんが、責務分離と TypeDoc 可読性の改善のため、以下の型モデルを canonical とします。
SerialConnectionOptions = port.open 用 W3C connection parameters
SerialSessionFeatureOptions = library-specific session features
SerialSessionOptions = Partial<SerialConnectionOptions> & SerialSessionFeatureOptions
SerialConnectionOptions — baudRate, dataBits, stopBits, parity, bufferSize, flowControl(port.open に渡される)SerialSessionFeatureOptions — filters, receiveReplay, terminalBuffer, lineBuffer(library-specific)SerialSessionOptions — 上記 2 つの composition(factory 引数)詳細は 概念と設計メモ – SerialSessionOptions を参照してください。
createSerialSession(options?) のシグネチャと、既存の options オブジェクトリテラルは 変更不要 です。SerialSessionFeatureOptions は新規 public export として追加されます。