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

    API concepts and design notes

    For exhaustive public API specifications, see the English TypeDoc API Reference. This page is a Guide supplement (tables and design notes)—not a TypeDoc substitute.

    The public surface consists of a single factory (createSerialSession), the runtime SerialSession interface, one options type, one state union, and two error types.

    This library is UTF-8 text–first. The internal read pump always decodes with a streaming TextDecoder (UTF-8, fatal: false, stream: true). There is no public encoding option.

    Item Current support
    UTF-8 text send / receive Supported
    Chunk-oriented string receive receive$decoded chunks (unframed text, not wire bytes)
    Newline-delimited string receive lines$
    Terminal display with \r redraws receive$ / terminalText$
    Binary send send$(Uint8Array) — bytes passed through unchanged
    Binary receive Not supported — no receiveBytes$ or raw Uint8Array receive stream
    Non-UTF-8 charsets (e.g. Shift_JIS) Not supported
    Protocol framing (Modbus RTU, COBS, SLIP, custom binary frames) Application-side — compose on decoded text or handle outside this library

    To choose among receive$, lines$, and terminalText$ by use case, see Choosing receive$ / lines$ / terminalText$.

    In docs and JSDoc, raw means unframed decoded text chunks (not line-split; \r and other control characters preserved). It does not mean raw wire bytes or a Uint8Array stream.

    send$(string) encodes with a shared TextEncoder (UTF-8). send$(Uint8Array) writes the bytes as-is. Receive remains UTF-8 text only, so send/receive are asymmetric for binary payloads.

    A possible future API (for example receiveBytes$) is not implemented in this release. The design review recommends deferring addition until concrete demand justifies the complexity.

    Summary:

    • Prefer an additive receiveBytes$: Observable<Uint8Array> (parallel to text streams), not a replacement for receive$
    • Fan out wire bytes from the same read loop before TextDecoder
    • Chunk boundaries are not protocol frames; no unbounded backpressure promise for slow subscribers
    • Re-encoding decoded text does not restore original wire bytes

    Full go / no-go criteria and sketch: Binary receive API — design decision (#545, parent #555). Current limits were documented in #540.

    import {
    createSerialSession,
    isWebSerialSupported,
    createTerminalBuffer,
    DEFAULT_TERMINAL_BUFFER_OPTIONS,
    SerialError,
    SerialErrorCode,
    SerialSessionStatus,
    type SerialSession,
    type SerialSessionState,
    type SerialSessionOptions,
    type SerialSessionFeatureOptions,
    type SerialConnectionOptions,
    type TerminalBufferOptions,
    } from '@gurezo/web-serial-rxjs';

    The following remain available from the public export in v4 but are not part of the canonical API. They are deprecated and scheduled for removal in a future major (v5+). See Migrating to v3 – §9 assertNever public export audit for the audit history. APIs already removed in v4 (destroy$, isConnected$, portInfo$, getPortInfo(), getCurrentPort(), receiveReplay$, isBrowserSupported(), …) are documented in Migrating to v4.

    Export Status Migration
    assertNever @deprecated in v4 Define a local helper in application code, or use switch (state.status) with SerialSessionStatus
    // Deprecated (still available in v4 but triggers warnings)
    import { assertNever } from '@gurezo/web-serial-rxjs';

    // Recommended: local helper
    function assertNever(value: never): never {
    throw new Error(`Unexpected value: ${String(value)}`);
    }

    Factory that returns a new SerialSession. Safe to call when navigator.serial is unavailable; in that case state$ is seeded with { status: 'unsupported' } and connect$ rejects with SerialErrorCode.BROWSER_NOT_SUPPORTED.

    function createSerialSession(options?: SerialSessionOptions): SerialSession;
    

    SerialSessionOptions composes W3C connection parameters (SerialConnectionOptions) with library-specific session features (SerialSessionFeatureOptions). It is the factory argument for createSerialSession.

    SerialSessionOptions = Partial<SerialConnectionOptions> & SerialSessionFeatureOptions
    

    Minimal callers typically only set baudRate; other fields keep safe defaults.

    const session = createSerialSession({ baudRate: 115200 });
    

    See Migrating to v3 – §10 Session options type responsibility audit for the audit rationale. See also Issue #488 for the Phase 2 options responsibility cleanup.

    Derived from W3C SerialOptions and passed to port.open. All fields are optional at factory time; omitted values fall back to the defaults below.

    Field Type Default Description
    baudRate number 9600 Bits per second. Must be a safe integer > 0.
    dataBits 7 | 8 8 Data bits per frame.
    stopBits 1 | 2 1 Stop bits per frame.
    parity 'none' | 'even' | 'odd' 'none' Parity checking mode.
    bufferSize number 255 Read-stream buffer size in bytes. Must be a safe integer > 0.
    flowControl 'none' | 'hardware' 'none' Flow control mode.

    Library-specific session features. Not passed to W3C port.open.

    Field Type Default Description
    filters SerialPortFilter[] | undefined Forwarded to navigator.serial.requestPort when selecting a port.
    terminalBuffer TerminalBufferOptions { maxLines: 10000, maxChars: 1048576, stripAnsi: true } Memory limits and ANSI stripping for terminalText$; see createTerminalBuffer.
    lineBuffer LineBufferOptions { maxChars: 1048576 } Memory limit for the incomplete line tail used by lines$; see below.

    At createSerialSession time (factory), resolveSerialSessionOptions validates the following. Invalid values throw SerialError:

    Target Validation Error code
    baudRate safe integer and > 0 INVALID_CONNECTION_OPTIONS
    bufferSize safe integer and > 0 INVALID_CONNECTION_OPTIONS
    filters USB vendor/product ID ranges INVALID_FILTER_OPTIONS
    terminalBuffer maxLines / maxChars are safe integers and >= 0 INVALID_TERMINAL_BUFFER_OPTIONS
    lineBuffer maxChars is a safe integer and >= 0 INVALID_LINE_BUFFER_OPTIONS
    Value Connection (baudRate / bufferSize) Buffer limits (terminalBuffer / lineBuffer)
    undefined Apply default Apply nested default
    0 Rejected Unlimited (disable that limit)
    negative / non-integer / NaN / Infinity Rejected Rejected

    Do not mix meanings: 0 is never “unlimited” for connection fields.

    Used by createTerminalBuffer and SerialSessionOptions.terminalBuffer. When a limit is exceeded, the oldest completed lines or leading characters are dropped so long-running terminal views do not grow without bound. Pass 0 for either field to disable that constraint. Character counts use UTF-16 string length (JavaScript .length).

    Field Type Default Description
    maxLines number 10000 Max number of completed lines retained in the cumulative display text.
    maxChars number 1048576 Max total characters in the display text (completed + current line).
    stripAnsi boolean true When true, removes ANSI escape sequences before folding \r redraws. Set false to preserve raw escape codes in terminalText$. receive$ is always unchanged.

    Invalid maxLines or maxChars values cause createSerialSession and standalone createTerminalBuffer to throw SerialError with INVALID_TERMINAL_BUFFER_OPTIONS.

    Used by SerialSessionOptions.lineBuffer for the incomplete line tail held while framing lines$. When maxChars is exceeded, leading characters of the tail are discarded and a non-fatal SerialError with SerialErrorCode.LINE_BUFFER_OVERFLOW is emitted on errors$. Completed lines are emitted in full before the tail is trimmed. Pass 0 to disable the limit. Character counts use UTF-16 string length.

    Field Type Default Description
    maxChars number 1048576 Max characters retained in the incomplete line tail (no line terminator yet).

    Invalid maxChars values cause createSerialSession to throw SerialError with INVALID_LINE_BUFFER_OPTIONS.

    Builds a terminal-oriented cumulative text stream from any Observable<string> of decoded chunks (typically SerialSession.receive$). Folds \r redraws while preserving normal newline behavior. Defaults match DEFAULT_TERMINAL_BUFFER_OPTIONS. Invalid maxLines / maxChars throw the same INVALID_TERMINAL_BUFFER_OPTIONS error as session factory validation.

    function createTerminalBuffer(
    receive$: Observable<string>,
    options?: TerminalBufferOptions,
    ): TerminalBuffer;

    v3 exposes SerialSessionStatus as lifecycle string constants (e.g. SerialSessionStatus.Connected is 'connected') and SerialSessionState as the discriminated union type emitted by state$.

    state$ emits objects such as:

    • { status: 'idle' } — no active port; initial state when Web Serial is supported.
    • { status: 'connecting' }connect$ is in flight.
    • { status: 'connected', portInfo } — port is open and the read pump is running.
    • { status: 'disconnecting' }disconnect$ is in flight.
    • { status: 'unsupported' }navigator.serial was not available at session creation time.
    • { status: 'error', error } — fatal failure; error is the same SerialError instance on errors$.
    • { status: 'disposed' } — session permanently torn down via dispose$.

    Example:

    import { filter } from 'rxjs';
    import { isConnectedSessionState, SerialSessionStatus } from '@gurezo/web-serial-rxjs';

    session.state$.subscribe((state) => {
    if (state.status === SerialSessionStatus.Connected) {
    console.log(state.portInfo);
    }
    });

    // RxJS pipelines: use the type predicate so TypeScript keeps ConnectedSessionState
    session.state$
    .pipe(filter(isConnectedSessionState))
    .subscribe((state) => {
    console.log(state.portInfo);
    });

    Type predicate for ConnectedSessionState. Use with RxJS filter() to preserve discriminated union narrowing in pipelines. 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);
    });

    See Migrating to v3 for the v2 string migration.

    interface SerialSession {
    connect$(): Observable<void>;
    disconnect$(): Observable<void>;
    dispose$(): Observable<void>;

    readonly state$: Observable<SerialSessionState>;
    readonly errors$: Observable<SerialError>;
    readonly receive$: Observable<string>;
    readonly terminalText$: Observable<string>;
    readonly lines$: Observable<string>;

    send$(data: string | Uint8Array): Observable<void>;
    }

    SerialSession is an exported public interface, not a class. Prefer typing application code against this type instead of coupling to the concrete createSerialSession() return site. Create real sessions only at DI boundaries, factories, or composition roots.

    Decision: Do not add a separate SerialSessionLike (or similar) alias.

    Reason Detail
    Existing contract is enough SerialSession is already the swappable public contract
    Structural typing Any same-shaped fake is assignable to SerialSession
    Avoid dual maintenance A second interface only adds long-term compatibility and docs cost
    Parent #535 Do not casually extend the core API

    For a controllable Fake, Vitest examples, Angular / React injection, and the npm packaging decision (Fake is not published), see Hardware-free testing with a Fake SerialSession (#537).

    import {
    createSerialSession,
    type SerialSession,
    } from '@gurezo/web-serial-rxjs';

    // App layer: depend on SerialSession only
    function createSerialUi(session: SerialSession) {
    return session.state$.subscribe((state) => {
    // update UI
    });
    }

    // Create the concrete session at the boundary
    const session = createSerialSession({ baudRate: 115200 });
    createSerialUi(session);
    Environment Typical typing
    Angular Inject InjectionToken<SerialSession>; production uses createSerialSession(), tests use a fake — see testing
    React Type props / Context as SerialSession — see testing
    Vue Type provide / inject values as SerialSession
    Svelte Type setContext / getContext as SerialSession
    Vanilla TS Take SerialSession in constructors or factories

    Synchronous feature detection. Returns true when navigator.serial is available. Prefer this before creating a session. After the session exists, drive unsupported UI from state$ with SerialSessionStatus.Unsupported.

    This check is not a compatibility or official-support guarantee — see Browser support and support policy. Secure context is a separate requirement. Migration notes: Migrating to v4 – browser support.

    Opens a user-selected serial port and starts the internal read pump. Completes on success; errors via errors$ and the subscriber on failure. Transitions idle → connecting → connected. Runs when subscribed.

    Stops the read pump and closes the port. Safe to call when already idle or while a disconnect is already in progress. When called during 'connecting', cancels the in-flight connect$() (closes any opened port) and returns to 'idle' without reaching 'connected'. Transitions connected → disconnecting → idle. When called from 'error' it still tears the port down and returns to idle. The session remains reusable after disconnect$; use dispose$ for permanent teardown. Runs when subscribed.

    Permanently tears down the session. Closes any active connection (same port/pump cleanup as disconnect$), emits 'disposed' on state$, and completes every session observable (state$, errors$, receive$, lines$, terminalText$). Safe to call multiple times; subsequent calls complete immediately. Runs when subscribed.

    After disposal, connect$ and send$ fail with SerialErrorCode.SESSION_DISPOSED. disconnect$ completes immediately. Create a new SerialSession instead of reusing a disposed instance (for example when replacing a session after a baud-rate change).

    Replays the current state on subscribe. Prefer driving your UI from this stream instead of rebuilding a BehaviorSubject. When state.status is SerialSessionStatus.Connected, read state.portInfo for device identification. There is no separate portInfo$ / getPortInfo() / isConnected$ / destroy$() / getCurrentPort() / receiveReplay$ / isBrowserSupported() on the public API — see Migrating to v4.

    Primary error channel. Every connect / read / write / close failure is normalised to SerialError and pushed here. Fatal failures additionally drive state$ to { status: 'error', error } and tear down the live pump and port.

    UTF-8 decoded text pushed by the internal read pump as decoder chunks (not line-oriented, and not wire bytes). Not subscription-lazy — the pump is started by connect$ and chunks are multicast. Late subscribers see only new data. Carriage returns and other control characters are preserved. Use receive$ for terminal-like mirrors and any output that depends on \r (for example interactive shells or progress lines). Use lines$ for newline-framed logs and line-by-line parsing. See Supported data.

    Terminal-display oriented cumulative text derived from receive$. Collapses \r redraws while keeping normal newline behavior. By default strips ANSI escape sequences for plain-text views (for example <textarea>). Raw escape codes remain available on receive$. Equivalent to createTerminalBuffer(receive$, options.terminalBuffer).text$. By default retains at most 10,000 completed lines and 1,048,576 characters; configure via SerialSessionOptions.terminalBuffer or pass { maxLines: 0, maxChars: 0 } for unlimited growth.

    The same UTF-8 stream split into complete lines using \n, \r\n, and a lone interior \r (see library implementation). Trailing data without a line ending is buffered; incomplete tails are not emitted. By default the incomplete tail is capped at 1,048,576 characters via SerialSessionOptions.lineBuffer; overflow discards leading tail data and emits LINE_BUFFER_OVERFLOW on errors$ without disconnecting. Not subscription-lazy with respect to the read pump, like receive$. Choose lines$ for logs and parsers; for unframed terminal display where \r redraw semantics matter, subscribe to receive$ instead.

    Enqueues a payload for ordered transmission. Strings are UTF-8 encoded through a shared TextEncoder. Uint8Array values are written unchanged (binary send only — there is no matching binary receive API). Concurrent send$ calls are serialised in call order by an internal FIFO queue. Write failures are normalised to SerialError with SerialErrorCode.WRITE_FAILED, multiplexed on errors$, and surfaced to the subscriber. Calling send$ while not 'connected' fails fast with SerialErrorCode.PORT_NOT_OPEN. Runs when subscribed. See Supported data.

    SerialError extends Error with a code: SerialErrorCode and structured per-code metadata on context. is(code) narrows both code and context to the literal types for that code.

    For recommended next actions after each code (reconnect vs dispose), see Troubleshooting – Error Recovery Matrix.

    For cause-bearing error codes, context.cause (unknown) is the canonical source for the underlying failure. originalError remains in v4 for backward compatibility but is deprecated and scheduled for removal in a future major (v5+). See Migrating to v3 – originalError deprecation.

    session.errors$.subscribe((error) => {
    if (error.is(SerialErrorCode.READ_FAILED)) {
    console.error(error.context.cause);
    }
    });

    try {
    createSerialSession({ baudRate: 0 });
    } catch (error) {
    if (error instanceof SerialError && error.is(SerialErrorCode.INVALID_CONNECTION_OPTIONS)) {
    console.error(error.context.field, error.context.value, error.context.constraint);
    }
    }

    The same union is available as a const object SerialErrorCode (e.g. SerialErrorCode.READ_FAILED is 'READ_FAILED') for IDE completion and to avoid string typos. String literals stay valid for types and runtime comparisons. See Migrating to v3 for the enum-to-const declaration change.

    Runtime emission coverage for the implemented codes is audited in Migrating to v3 §8. Receive-replay codes were removed in Migrating to v4 – Phase 2.

    Code context shape When it is emitted
    LINE_BUFFER_OVERFLOW { maxChars: number } lines$ incomplete tail exceeded lineBuffer.maxChars; leading data discarded (non-fatal).
    INVALID_* validation codes ValidationErrorContext Factory-time options validation; see below. Narrow with error.is(code).
    Cause-bearing codes (e.g. PORT_OPEN_FAILED) { cause: unknown } See table below. Narrow with error.is(code) before reading context.cause.
    Other codes undefined See table below.

    ValidationErrorContext is { field: string; value: unknown; constraint: ValidationErrorConstraint; filterIndex?: number }. message stays human-readable; use context for programmatic handling.

    Code When it is emitted
    BROWSER_NOT_SUPPORTED connect$ without navigator.serial.
    PORT_OPEN_FAILED port.open() rejected.
    PORT_ALREADY_OPEN connect$ called while not in 'idle' / 'error'.
    PORT_NOT_OPEN send$ or disconnect$ called in an invalid session state.
    READ_FAILED Internal read pump errored.
    WRITE_FAILED port.writable.getWriter().write() rejected.
    CONNECTION_LOST port.close() failed or the port dropped mid-session.
    INVALID_FILTER_OPTIONS filters contained an invalid entry (at session creation).
    INVALID_TERMINAL_BUFFER_OPTIONS terminalBuffer.maxLines or terminalBuffer.maxChars was out of range at session creation.
    INVALID_LINE_BUFFER_OPTIONS lineBuffer.maxChars was out of range at session creation.
    INVALID_CONNECTION_OPTIONS baudRate was out of range at session creation.
    OPERATION_CANCELLED User cancelled the port picker.
    SESSION_DISPOSED connect$ or send$ called after dispose$.
    UNKNOWN Unclassified dispose / disconnect fallback; see context.cause.
    Code Notes
    PORT_NOT_AVAILABLE Deprecated. Unreachable without a getPorts API. Use PORT_OPEN_FAILED / OPERATION_CANCELLED for port acquisition failures.
    OPERATION_TIMEOUT Deprecated. Unreachable without a timeout / transaction API.