web-serial-rxjs API Documentation
    Preparing search index...

    Migrating to v3

    v3 introduces two TypeScript-facing breaking changes:

    1. SerialErrorCodeenum → const object + union type (runtime values unchanged).
    2. state$ payload — flat string → discriminated union with per-status detail.

    This guide covers both. Runtime string values for error codes are unchanged (SerialErrorCode.READ_FAILED is still '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);
    }
    });

    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 (and any other member)
    • error.code === SerialErrorCode.WRITE_FAILED
    • error.is(SerialErrorCode.LINE_BUFFER_OVERFLOW) with narrowed context
    • switch (error.code) { case SerialErrorCode.READ_FAILED: ... }
    • Type-only imports — continue using import type { SerialErrorCode } from '@gurezo/web-serial-rxjs'.
    • TypeDoc deep links — update bookmarks from enums/SerialErrorCode.html to variables/SerialErrorCode.html.
    • Tools parsing .d.ts — declaration shape changes from enum to const + type alias.

    v2 v3
    state$: Observable<'idle' | 'connected' | ...> state$: Observable<SerialSessionState> (discriminated union)
    SerialSessionState const (string literals) SerialSessionStatus const (string literals)
    Compare state === SerialSessionState.Connected Compare state.status === SerialSessionStatus.Connected
    Correlate state$ + portInfo$ / errors$ manually connected carries portInfo; error carries SerialError
    import { SerialSessionState } from '@gurezo/web-serial-rxjs';

    session.state$.subscribe((state) => {
    if (state === SerialSessionState.Connected) {
    session.getPortInfo(); // separate call
    }
    });
    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 };
    • [ ] Replace import { SerialSessionState } used as constants with SerialSessionStatus.
    • [ ] Replace state === SerialSessionState.X with state.status === SerialSessionStatus.X.
    • [ ] Replace switch (state) with switch (state.status) (or compare state.status in if).
    • [ ] Use state.portInfo when state.status === SerialSessionStatus.Connected (recommended — portInfo$ and getPortInfo() are deprecated).
    • [ ] Use state.error when state.status === 'error' (same instance as errors$ for fatal errors).
    • errors$ remains available.
    • portInfo$ and getPortInfo() remain available in v3.x but are deprecated (see §5).
    • isConnected$ remains available in v3.x but is deprecated (see §6).

    v3.0.0 introduced typed SerialError.context. For cause-bearing error codes, context.cause is the canonical source for the underlying failure.

    SerialError.originalError and the legacy constructor third argument remain in v3.x for backward compatibility but are deprecated and scheduled for removal in the next 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 is unknown — non-Error throws are preserved
    console.error(error.context.cause);
    }
    });
    • [ ] Replace error.originalError with error.context.cause (narrow with error.is(code) first).
    • [ ] If you construct errors with new SerialError(code, message, cause), switch to new SerialError(code, message, undefined, { cause }).
    • [ ] Address TypeScript @deprecated warnings by migrating to the patterns above.
    • originalError remains available in v3.x.
    • When context.cause is an Error instance, originalError is kept in sync for legacy callers.
    • context.cause is typed as unknown because JavaScript allows throwing non-Error values.

    SerialSession exposes both dispose$() and destroy$(). They are the same function — destroy$ is a legacy alias. Lifecycle terminology (dispose, disposed, SESSION_DISPOSED) already uses dispose$ as the canonical API.

    destroy$() remains in v3.x for backward compatibility but is deprecated and scheduled for removal in the next major version.

    session.destroy$().subscribe({
    complete: () => console.log('session destroyed'),
    });
    session.dispose$().subscribe({
    complete: () => console.log('session disposed'),
    });
    • [ ] Replace session.destroy$() with session.dispose$().
    • [ ] Address TypeScript @deprecated warnings by migrating to dispose$.
    • [ ] Prefer dispose$ in new code and documentation.
    • destroy$ remains available in v3.x and delegates to the same implementation as dispose$.
    • Runtime behavior is unchanged; only the alias is deprecated.

    v3.0.0 made state$ a discriminated union. When state.status is SerialSessionStatus.Connected, state.portInfo is the canonical source for the active port's SerialPort.getInfo() snapshot — TypeScript narrowing guarantees it is present.

    portInfo$ and getPortInfo() remain in v3.x for backward compatibility but are deprecated and scheduled for removal in the next major version. They expose SerialPortInfo | null, which does not encode the relationship between connection state and port information.

    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);
    }
    });
    • [ ] Replace portInfo$ subscriptions with state$ and read state.portInfo when state.status === SerialSessionStatus.Connected.
    • [ ] Replace getPortInfo() with state$ narrowing and state.portInfo.
    • [ ] Address TypeScript @deprecated warnings by migrating to the pattern above.
    • [ ] Prefer state.portInfo in new code and documentation.
    • portInfo$ and getPortInfo() remain available in v3.x.
    • Runtime behavior is unchanged; values stay in sync with state.portInfo while connected.
    • errors$ is not deprecated — it is an independent error event channel, not a duplicate of lifecycle state.

    v3.0.0 made state$ a discriminated union. When state.status is SerialSessionStatus.Connected, TypeScript narrowing gives type-safe access to state.portInfo and other state-specific fields.

    isConnected$ is an Observable<boolean> that only projects whether the session is connected, so it loses the type information carried by the discriminated union. It remains in v3.x for backward compatibility but is deprecated and scheduled for removal in the next major version.

    session.isConnected$.subscribe((isConnected) => {
    if (isConnected) {
    // session state is not narrowed
    }
    });
    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(),
    );

    When you need portInfo or other connected-only fields inside a pipeline, use isConnectedSessionState with filter(). Inline filter((s) => s.status === SerialSessionStatus.Connected) does not narrow types in TypeScript.

    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,
    );
    • [ ] Replace isConnected$ subscriptions with state$ and narrow on state.status === SerialSessionStatus.Connected.
    • [ ] When you only need a boolean for UI, derive it from state$ with map or computed.
    • [ ] Address TypeScript @deprecated warnings by migrating to the patterns above.
    • [ ] Prefer state$ narrowing in new code and documentation.
    • isConnected$ remains available in v3.x.
    • Runtime behavior is unchanged; values stay in sync with state.status === SerialSessionStatus.Connected.
    • Framework-specific convenience state should be derived from state$ in framework adapters and examples.

    SerialSession.getCurrentPort() was a raw SerialPort escape hatch. Calling port.close() or writable.getWriter() on the returned port could conflict with the session lifecycle and break internal runtime invariants.

    A usage audit (#437) found no production callers in this repository. Device identification is covered by state.portInfo, so getCurrentPort() has been removed from the public API.

    Area Finding
    Library production code No getCurrentPort() callers
    Example apps Test mocks only
    Device identification alternative state.portInfo after state$ narrowing (canonical)
    Signals (DTR/RTS, etc.) No replacement API yet (future 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);
    }
    });

    Operations such as getSignals() / setSignals() that previously required a raw port have no SerialSession replacement yet. If you need them, open a separate issue to propose first-class APIs.

    • [ ] Remove all getCurrentPort() calls.
    • [ ] Use state$ narrowed on SerialSessionStatus.Connected and read state.portInfo for device identification.
    • [ ] If you depend on signals or other native operations, request a dedicated API via an issue.

    Some members of the public SerialErrorCode contract were not emitted by the v3.x runtime. To prevent unreachable error-handling branches, all 19 codes were audited (#438) and the results are recorded here and in the API Reference.

    Category Count Description
    Implemented 17 Emitted at runtime in v3.x (or thrown at factory time)
    Reserved 2 Present in the public API but not emitted in v3.x; scheduled for removal in the next major version
    Code Reason Alternative
    PORT_NOT_AVAILABLE Current implementation uses only navigator.serial.requestPort; no getPorts API path exists Use PORT_OPEN_FAILED or OPERATION_CANCELLED for port acquisition failures
    OPERATION_TIMEOUT No timeout / prompt detection / transaction API yet None (revisit when a future API is added)

    v3.x adds @deprecated annotations only; runtime values and exports are unchanged. Removal is deferred to the next major version.

    Code Emit location fatal / non-fatal context Tests
    BROWSER_NOT_SUPPORTED connect$ (no navigator.serial) non-fatal undefined integration
    PORT_OPEN_FAILED connect$ (port.open() reject) fatal { cause } integration
    PORT_ALREADY_OPEN connect$ (not in 'idle' / 'error') non-fatal undefined integration
    PORT_NOT_OPEN send$ / disconnect$ (invalid state) non-fatal undefined integration
    READ_FAILED read pump error fatal { cause } integration
    WRITE_FAILED send$ write failure non-fatal { cause } integration
    CONNECTION_LOST port.close() failure / stream drop fatal { cause } integration
    INVALID_FILTER_OPTIONS createSerialSession factory throw ValidationErrorContext unit + integration
    OPERATION_CANCELLED requestPort dialog cancelled fatal { cause } integration
    LINE_BUFFER_OVERFLOW lines$ tail overflow non-fatal { maxChars } integration
    INVALID_RECEIVE_REPLAY_OPTIONS factory throw ValidationErrorContext unit + integration
    INVALID_TERMINAL_BUFFER_OPTIONS factory throw ValidationErrorContext unit
    INVALID_LINE_BUFFER_OPTIONS factory throw ValidationErrorContext unit
    INVALID_CONNECTION_OPTIONS factory throw ValidationErrorContext unit + integration
    RECEIVE_REPLAY_BUFFER_OVERFLOW receiveReplay$ overflow non-fatal { maxChars, bufferSize } integration
    SESSION_DISPOSED connect$ / send$ after dispose$ fatal undefined integration
    UNKNOWN unclassified dispose / disconnect fallback fatal { cause } unit

    Fatal vs non-fatal follows ERROR_SEVERITY inside reportError. Factory-thrown INVALID_* codes bypass reportError and throw directly to the caller.

    • [ ] Remove error handling for PORT_NOT_AVAILABLE / OPERATION_TIMEOUT (unreachable in v3.x).
    • [ ] Handle port acquisition failures with PORT_OPEN_FAILED / OPERATION_CANCELLED.
    • [ ] See API Reference – SerialError / SerialErrorCode for per-code emit conditions.

    Structured context for validation errors (INVALID_*) was added in #439. Use ValidationErrorContext (field, value, constraint, optional filterIndex) instead of parsing message.


    assertNever is a TypeScript utility for exhaustive switch checking. It was added as an internal exhaustiveness helper (#394 / PR #410) but was also exposed as a public export. Because it is not part of the Web Serial / SerialSession domain API, usage was audited (#440) and the results are recorded here and in the API Reference.

    Check Result
    package internal usage session-runtime.ts only (via assertNeverRuntime)
    examples usage none in apps/ or libs/
    documentation usage not listed in canonical exports (API_REFERENCE); not mentioned in migration docs
    export history added in Phase A (#394) as src/internal/assert-never.ts, re-exported from index.ts

    assertNever is an internal implementation utility, not a canonical public API. For exhaustive handling of SerialSessionState, prefer switch (state.status) with SerialSessionStatus, or narrowing with isConnectedSessionState.

    v3.x adds @deprecated annotations only; the public export is retained. Removal is deferred to the next 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);
    }
    });

    Cover all switch (state.status) cases, or use filter(isConnectedSessionState) in RxJS pipelines. If you need an exhaustiveness helper, define one locally in application code.

    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);
    }
    });
    • [ ] Remove assertNever imports from @gurezo/web-serial-rxjs.
    • [ ] Define a local helper if you still need exhaustiveness checking.
    • [ ] Prefer switch (state.status) with SerialSessionStatus for SerialSessionState branches.
    • [ ] Migrate when TypeScript shows @deprecated warnings.

    assertNever remains available from the public export in v3.x. It is scheduled for removal in the next major version.


    SerialSessionOptions exposes W3C SerialOptions-derived connection fields and library-specific session feature options in a single type. As part of the TypeScript-first domain model consolidation, the public type surface and generated documentation were audited (#441).

    Check Result
    existing assignability Existing createSerialSession({ ... }) calls work without changes
    generated .d.ts public SerialConnectionOptions duplicated the internal SerialSessionConnectionFields Pick
    TypeDoc readability connection and feature fields appeared in one flat list; hierarchy showed an internal type name
    readonly input compatibility mutable arrays retained; readonly input assignability verified by regression tests
    examples libs/examples-shared already uses SerialConnectionOptions['baudRate']; example apps unchanged
    W3C SerialOptions drift detection connection fields remain a Pick from W3C types via SerialConnectionOptions

    Type safety was already sound, but conceptual separation improves documentation clarity. The canonical model is:

    SerialConnectionOptions     = W3C connection parameters for port.open
    SerialSessionFeatureOptions = library-specific session features
    SerialSessionOptions        = Partial<SerialConnectionOptions> & SerialSessionFeatureOptions
    
    • SerialConnectionOptionsbaudRate, dataBits, stopBits, parity, bufferSize, flowControl (passed to port.open)
    • SerialSessionFeatureOptionsfilters, receiveReplay, terminalBuffer, lineBuffer (library-specific)
    • SerialSessionOptions — composition of the two (factory argument)

    See API Reference – SerialSessionOptions for details.

    The createSerialSession(options?) signature and existing options object literals remain unchanged. SerialSessionFeatureOptions is added as a new public export.