@gurezo/web-serial-rxjs 4.0.3 → 4.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.ja.md CHANGED
@@ -10,18 +10,28 @@ Web Serial API を最小限の Session 指向 RxJS 表面でラップする Type
10
10
 
11
11
  ## ブラウザサポート
12
12
 
13
- Web Serial API は**デスクトップ**ブラウザでのみサポートされています。スマートフォンなどのモバイルブラウザには対応していません。
13
+ この節では、**Web Serial API の実装状況**(ブラウザが提供するもの)と、本プロジェクトの**公式サポート方針**(動作確認・保証の範囲)を分けて記載します。
14
14
 
15
- 対応しているデスクトップブラウザ:
15
+ ### Web Serial API の実装状況
16
+
17
+ `navigator.serial` が存在する環境では、本ライブラリは Web Serial API を利用できます。デスクトップでの典型的な対応は次のとおりです。
16
18
 
17
19
  - **Chrome** 89+
18
20
  - **Edge** 89+
19
21
  - **Opera** 75+
20
22
  - **Firefox** 151+
21
23
 
22
- **Safari** は現時点で Web Serial API をサポートしていません。
24
+ **Safari** は現時点で Web Serial API を**実装していません**。多くの**モバイル**ブラウザにも `navigator.serial` がなく、API が無い場合は `isWebSerialSupported()` が `false` を返します。
25
+
26
+ ### プロジェクトの公式サポート方針
27
+
28
+ **公式サポート**の対象は、上記のデスクトップブラウザ(Chrome 89+、Edge 89+、Opera 75+、Firefox 151+)です。
23
29
 
24
- `connect$` の前の feature detection には `isWebSerialSupported()`(同期的に `boolean`)を使います。
30
+ **モバイル**ブラウザは**未検証**であり、**公式サポート対象外**です。未検証は「ライブラリが拒否する」ことと同一ではありません。モバイルで Web Serial が公開され、セキュアコンテキストであれば feature detection が成功する場合もありますが、動作は保証しません。
31
+
32
+ ### `isWebSerialSupported()`
33
+
34
+ `connect$` の前の **feature detection**(`navigator.serial` の有無)には `isWebSerialSupported()`(同期的に `boolean`)を使います。これは**互換性や公式サポートの保証ではありません**。セキュアコンテキスト(HTTPS または localhost)は別条件です。
25
35
 
26
36
  ## 接続状態(ライフサイクル UI)
27
37
 
@@ -98,16 +108,23 @@ npm install rxjs
98
108
  pnpm add rxjs
99
109
  ```
100
110
 
111
+ パッケージは **ESM のみ**です(`package.json` の `exports` 経由の `import`)。
112
+
101
113
  ## 次に読むもの
102
114
 
103
115
  - **API の全体像**(機能一覧、`SerialSession` 早見表、`SerialSessionState`、最小サンプル): [SerialSession の概要](https://github.com/gurezo/web-serial-rxjs/blob/main/packages/web-serial-rxjs/docs/guide/ja/overview.md)([English](https://github.com/gurezo/web-serial-rxjs/blob/main/packages/web-serial-rxjs/docs/guide/en/overview.md))
104
116
  - 最短でポートを開く手順: [クイックスタート](https://github.com/gurezo/web-serial-rxjs/blob/main/packages/web-serial-rxjs/docs/guide/ja/quick-start.md)
105
117
  - よくある問題と自己解決: [トラブルシューティング](https://github.com/gurezo/web-serial-rxjs/blob/main/packages/web-serial-rxjs/docs/guide/ja/troubleshooting.md)
118
+ - 公開ドキュメントサイト: [web-serial-rxjs Documentation](https://gurezo.net/web-serial-rxjs/)
119
+ - API Reference(TypeDoc): [web-serial-rxjs API Documentation](https://gurezo.net/web-serial-rxjs/api/)
106
120
 
107
121
  ## ドキュメント
108
122
 
109
123
  | ドキュメント | 用途 |
110
124
  | --- | --- |
125
+ | [ドキュメントホーム](https://gurezo.net/web-serial-rxjs/) | Guide(ja/en)と API Reference へのサイトランディング |
126
+ | [日本語 Guide(公開サイト)](https://gurezo.net/web-serial-rxjs/guide/ja/README.html) | Getting Started の読み順と一覧(公開サイト) |
127
+ | [API Reference(公開サイト)](https://gurezo.net/web-serial-rxjs/api/index.html) | 英語 TypeDoc API Reference |
111
128
  | [日本語 Guide 索引](https://github.com/gurezo/web-serial-rxjs/blob/main/packages/web-serial-rxjs/docs/guide/ja/README.md) | Getting Started の読み順と一覧 |
112
129
  | [English Guide 索引](https://github.com/gurezo/web-serial-rxjs/blob/main/packages/web-serial-rxjs/docs/guide/en/README.md) | Getting Started reading order and full index |
113
130
  | [全体像](https://github.com/gurezo/web-serial-rxjs/blob/main/packages/web-serial-rxjs/docs/guide/ja/overview.md) | 機能と `SerialSession` / `SerialSessionState` の対応表 |
package/README.md CHANGED
@@ -10,18 +10,28 @@ A TypeScript library that wraps the Web Serial API with a minimal, session-orien
10
10
 
11
11
  ## Browser support
12
12
 
13
- The Web Serial API is supported on **desktop** browsers only. Smartphones and other mobile browsers are not supported.
13
+ This section separates **Web Serial API availability** (what the browser implements) from this project's **official support policy** (what we test and guarantee).
14
14
 
15
- Supported desktop browsers:
15
+ ### Web Serial API availability
16
+
17
+ Where `navigator.serial` exists, this library can talk to the Web Serial API. Typical desktop availability:
16
18
 
17
19
  - **Chrome** 89+
18
20
  - **Edge** 89+
19
21
  - **Opera** 75+
20
22
  - **Firefox** 151+
21
23
 
22
- **Safari** does not currently support the Web Serial API.
24
+ **Safari** does not currently implement the Web Serial API. Many **mobile** browsers also lack `navigator.serial`; when the API is missing, `isWebSerialSupported()` returns `false`.
25
+
26
+ ### Project support policy
27
+
28
+ **Official support** covers the desktop browsers listed above (Chrome 89+, Edge 89+, Opera 75+, Firefox 151+).
23
29
 
24
- `isWebSerialSupported()` returns a synchronous `boolean` for feature detection before `connect$`.
30
+ **Mobile** browsers are **untested** and **out of official support**. Untested does **not** mean the library rejects them — if a mobile browser exposes Web Serial and the page is in a secure context, feature detection may succeed, but we do not guarantee behavior.
31
+
32
+ ### `isWebSerialSupported()`
33
+
34
+ `isWebSerialSupported()` returns a synchronous `boolean` for **feature detection** (`navigator.serial` present) before `connect$`. It is **not** a compatibility or official-support guarantee. Secure context (HTTPS or localhost) is a separate requirement.
25
35
 
26
36
  ## Connection state (lifecycle UI)
27
37
 
@@ -98,6 +108,8 @@ npm install rxjs
98
108
  pnpm add rxjs
99
109
  ```
100
110
 
111
+ The package is **ESM-only** (`import` via `package.json` `exports`).
112
+
101
113
  ## Where to go next
102
114
 
103
115
  - Full **API map** (features, `SerialSession` table, `SerialSessionState`, minimal example): [SerialSession overview](https://github.com/gurezo/web-serial-rxjs/blob/main/packages/web-serial-rxjs/docs/guide/en/overview.md) ([日本語](https://github.com/gurezo/web-serial-rxjs/blob/main/packages/web-serial-rxjs/docs/guide/ja/overview.md))
@@ -38,15 +38,15 @@ export declare const SerialErrorCode: {
38
38
  /**
39
39
  * Serial port is not available.
40
40
  *
41
- * **Reserved — not emitted in v3.x.** The current implementation uses only
42
- * `navigator.serial.requestPort`; there is no `getPorts` API path. Scheduled
43
- * for removal in the next major version.
41
+ * **Reserved — not emitted in v4.** The current implementation uses only
42
+ * `navigator.serial.requestPort`; there is no `getPorts` API path. Still
43
+ * present in v4 (deprecated); scheduled for removal in a future major (v5+).
44
44
  *
45
45
  * **Suggested action**: Handle port acquisition failures with
46
46
  * {@link SerialErrorCode.PORT_OPEN_FAILED} or
47
47
  * {@link SerialErrorCode.OPERATION_CANCELLED} instead.
48
48
  *
49
- * @deprecated Not emitted at runtime in v3.x. Will be removed in the next major version.
49
+ * @deprecated Not emitted at runtime in v4. Will be removed in a future major (v5+).
50
50
  * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/438 | Issue #438}
51
51
  */
52
52
  readonly PORT_NOT_AVAILABLE: "PORT_NOT_AVAILABLE";
@@ -126,11 +126,11 @@ export declare const SerialErrorCode: {
126
126
  /**
127
127
  * Operation timed out before completion.
128
128
  *
129
- * **Reserved — not emitted in v3.x.** No timeout / prompt detection /
130
- * transaction API exists yet. Scheduled for removal in the next major version
131
- * unless a future API adopts this code.
129
+ * **Reserved — not emitted in v4.** No timeout / prompt detection /
130
+ * transaction API exists yet. Still present in v4 (deprecated); scheduled for
131
+ * removal in a future major (v5+) unless a future API adopts this code.
132
132
  *
133
- * @deprecated Not emitted at runtime in v3.x. Will be removed in the next major version.
133
+ * @deprecated Not emitted at runtime in v4. Will be removed in a future major (v5+).
134
134
  * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/438 | Issue #438}
135
135
  */
136
136
  readonly OPERATION_TIMEOUT: "OPERATION_TIMEOUT";
@@ -50,9 +50,9 @@ export declare class SerialError<TCode extends SerialErrorCode = SerialErrorCode
50
50
  * This property contains the underlying error (e.g., DOMException, TypeError)
51
51
  * that was wrapped in this SerialError. It may be undefined if no original error exists.
52
52
  *
53
- * @deprecated Prefer {@link context} for cause-bearing codes. This property is
54
- * retained for backward compatibility and is kept in sync when a cause is
55
- * provided.
53
+ * @deprecated Prefer {@link context} for cause-bearing codes. Still present in
54
+ * v4 for backward compatibility and kept in sync when a cause is provided.
55
+ * Removal is deferred to a future major (v5+).
56
56
  */
57
57
  readonly originalError?: Error;
58
58
  /**
@@ -61,9 +61,9 @@ export declare class SerialError<TCode extends SerialErrorCode = SerialErrorCode
61
61
  * @param code - The error code identifying the type of error
62
62
  * @param message - A human-readable error message
63
63
  * @param originalError - The original error that caused this SerialError, if any.
64
- * @deprecated Prefer passing `{ cause }` via {@link context}. When omitted,
65
- * cause-bearing codes derive `{ cause }` from this argument for backward
66
- * compatibility.
64
+ * @deprecated Prefer passing `{ cause }` via {@link context}. Still accepted
65
+ * in v4 for backward compatibility; when omitted, cause-bearing codes derive
66
+ * `{ cause }` from this argument. Removal is deferred to a future major (v5+).
67
67
  * @param context - Structured metadata for the error code. For cause-bearing
68
68
  * codes, pass `{ cause }` here. When omitted, cause-bearing codes derive
69
69
  * `{ cause }` from the legacy {@link originalError} argument.
package/dist/index.d.ts CHANGED
@@ -33,20 +33,28 @@
33
33
  *
34
34
  * ## Browser Support
35
35
  *
36
- * The Web Serial API is supported on **desktop** browsers only. Smartphones and
37
- * other mobile browsers are not supported.
36
+ * Separates **Web Serial API availability** (what the browser implements) from
37
+ * this project's **official support policy** (what we test and guarantee).
38
38
  *
39
- * Supported desktop browsers:
39
+ * ### Web Serial API availability
40
40
  *
41
- * - Chrome 89+
42
- * - Edge 89+
43
- * - Opera 75+
44
- * - Firefox 151+
41
+ * Where `navigator.serial` exists, this library can use the Web Serial API.
42
+ * Typical desktop availability: Chrome 89+, Edge 89+, Opera 75+, Firefox 151+.
43
+ * **Safari** does not currently implement the Web Serial API. Many mobile
44
+ * browsers also lack `navigator.serial`; when missing,
45
+ * {@link isWebSerialSupported} returns `false`.
45
46
  *
46
- * **Safari** does not currently support the Web Serial API.
47
+ * ### Project support policy
47
48
  *
48
- * Use {@link isWebSerialSupported} for a synchronous feature
49
- * check before calling {@link SerialSession.connect$}.
49
+ * **Official support** covers the desktop browsers listed above. **Mobile**
50
+ * browsers are **untested** and **out of official support**. Untested does
51
+ * **not** mean the library rejects them — behavior is not guaranteed.
52
+ *
53
+ * ### {@link isWebSerialSupported}
54
+ *
55
+ * Synchronous **feature detection** (`navigator.serial` present) before
56
+ * {@link SerialSession.connect$}. It is **not** a compatibility or
57
+ * official-support guarantee. Secure context is a separate requirement.
50
58
  *
51
59
  * @example
52
60
  * ```typescript
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuFG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,2BAA2B,EAAE,2BAA2B,EAAE,MAAM,WAAW,CAAC;AAC9K,YAAY,EACV,aAAa,EACb,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,EACrB,yBAAyB,EACzB,uBAAuB,EACvB,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,2BAA2B,EAC3B,4BAA4B,EAC5B,aAAa,EACb,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,YAAY,EACV,uBAAuB,EACvB,qBAAqB,EACrB,yBAAyB,EACzB,sBAAsB,GACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE7D,OAAO,EAAE,oBAAoB,EAAE,+BAA+B,EAAE,MAAM,mCAAmC,CAAC;AAC1G,YAAY,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+FG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,2BAA2B,EAAE,2BAA2B,EAAE,MAAM,WAAW,CAAC;AAC9K,YAAY,EACV,aAAa,EACb,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,EACrB,yBAAyB,EACzB,uBAAuB,EACvB,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,2BAA2B,EAC3B,4BAA4B,EAC5B,aAAa,EACb,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,YAAY,EACV,uBAAuB,EACvB,qBAAqB,EACrB,yBAAyB,EACzB,sBAAsB,GACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE7D,OAAO,EAAE,oBAAoB,EAAE,+BAA+B,EAAE,MAAM,mCAAmC,CAAC;AAC1G,YAAY,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC"}
package/dist/index.mjs CHANGED
@@ -21,15 +21,15 @@ var SerialErrorCode = {
21
21
  /**
22
22
  * Serial port is not available.
23
23
  *
24
- * **Reserved — not emitted in v3.x.** The current implementation uses only
25
- * `navigator.serial.requestPort`; there is no `getPorts` API path. Scheduled
26
- * for removal in the next major version.
24
+ * **Reserved — not emitted in v4.** The current implementation uses only
25
+ * `navigator.serial.requestPort`; there is no `getPorts` API path. Still
26
+ * present in v4 (deprecated); scheduled for removal in a future major (v5+).
27
27
  *
28
28
  * **Suggested action**: Handle port acquisition failures with
29
29
  * {@link SerialErrorCode.PORT_OPEN_FAILED} or
30
30
  * {@link SerialErrorCode.OPERATION_CANCELLED} instead.
31
31
  *
32
- * @deprecated Not emitted at runtime in v3.x. Will be removed in the next major version.
32
+ * @deprecated Not emitted at runtime in v4. Will be removed in a future major (v5+).
33
33
  * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/438 | Issue #438}
34
34
  */
35
35
  PORT_NOT_AVAILABLE: "PORT_NOT_AVAILABLE",
@@ -109,11 +109,11 @@ var SerialErrorCode = {
109
109
  /**
110
110
  * Operation timed out before completion.
111
111
  *
112
- * **Reserved — not emitted in v3.x.** No timeout / prompt detection /
113
- * transaction API exists yet. Scheduled for removal in the next major version
114
- * unless a future API adopts this code.
112
+ * **Reserved — not emitted in v4.** No timeout / prompt detection /
113
+ * transaction API exists yet. Still present in v4 (deprecated); scheduled for
114
+ * removal in a future major (v5+) unless a future API adopts this code.
115
115
  *
116
- * @deprecated Not emitted at runtime in v3.x. Will be removed in the next major version.
116
+ * @deprecated Not emitted at runtime in v4. Will be removed in a future major (v5+).
117
117
  * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/438 | Issue #438}
118
118
  */
119
119
  OPERATION_TIMEOUT: "OPERATION_TIMEOUT",
@@ -207,9 +207,9 @@ var SerialError = class _SerialError extends Error {
207
207
  * @param code - The error code identifying the type of error
208
208
  * @param message - A human-readable error message
209
209
  * @param originalError - The original error that caused this SerialError, if any.
210
- * @deprecated Prefer passing `{ cause }` via {@link context}. When omitted,
211
- * cause-bearing codes derive `{ cause }` from this argument for backward
212
- * compatibility.
210
+ * @deprecated Prefer passing `{ cause }` via {@link context}. Still accepted
211
+ * in v4 for backward compatibility; when omitted, cause-bearing codes derive
212
+ * `{ cause }` from this argument. Removal is deferred to a future major (v5+).
213
213
  * @param context - Structured metadata for the error code. For cause-bearing
214
214
  * codes, pass `{ cause }` here. When omitted, cause-bearing codes derive
215
215
  * `{ cause }` from the legacy {@link originalError} argument.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/internal/assert-never.ts", "../src/session/create-serial-session.ts", "../src/errors/serial-error-code.ts", "../src/terminal/create-terminal-buffer.ts", "../src/errors/serial-error-context.ts", "../src/errors/serial-error.ts", "../src/internal/branded-numbers.ts", "../src/internal/newline-tokenizer.ts", "../src/internal/strip-ansi-sequences.ts", "../src/session/internal/has-web-serial-support.ts", "../src/session/internal/port-teardown.ts", "../src/session/internal/receive-pipeline.ts", "../src/session/internal/line-buffer.ts", "../src/session/internal/error-severity.ts", "../src/session/normalize-serial-error.ts", "../src/session/session-runtime.ts", "../src/session/serial-session-state.ts", "../src/session/internal/session-error-reporter.ts", "../src/session/internal/session-lifecycle.ts", "../src/session/internal/build-request-options.ts", "../src/session/read-pump.ts", "../src/session/send-queue.ts", "../src/session/internal/validate-serial-port-filters.ts", "../src/session/serial-session-options.ts", "../src/session/is-connected-session-state.ts"],
4
- "sourcesContent": ["/**\n * Exhaustiveness helper for internal switch statements.\n *\n * @deprecated Not part of the canonical public API. Define a local helper in\n * application code, or use `switch (state.status)` with\n * {@link SerialSessionStatus}. Will be removed from public exports in the\n * next major version.\n * @internal\n */\nexport function assertNever(value: never): never {\n throw new Error(`Unexpected value: ${String(value)}`);\n}\n", "import { Observable, Subject } from 'rxjs';\nimport { SerialError } from '../errors/serial-error';\nimport { SerialErrorCode } from '../errors/serial-error-code';\nimport { createTerminalBuffer } from '../terminal/create-terminal-buffer';\nimport type { SerialPayload } from '../types';\nimport { isWebSerialSupported } from './internal/has-web-serial-support';\nimport { createPortTeardown } from './internal/port-teardown';\nimport { createReceivePipeline } from './internal/receive-pipeline';\nimport { createSessionErrorReporter } from './internal/session-error-reporter';\nimport { createSessionLifecycle } from './internal/session-lifecycle';\nimport { createSendQueue } from './send-queue';\nimport type { SerialSession } from './serial-session';\nimport {\n resolveSerialSessionOptions,\n type SerialSessionOptions,\n} from './serial-session-options';\nimport { SerialSessionStatus } from './serial-session-state';\nimport {\n createInitialRuntime,\n createSessionRuntimeController,\n} from './session-runtime';\n\n/**\n * Create a {@link SerialSession}.\n *\n * This release wires the internal read pump (#202) and the internal send\n * queue (#203) into the session so that `connect$`, `disconnect$`,\n * `receive$`, and `send$` all operate end-to-end. Error handling is\n * centralised through a single `reportError` helper (#204) so every\n * failure path normalises through {@link normalizeSerialError} and emits\n * on the one `errors$` channel.\n *\n * Key behaviors:\n *\n * - `state$` replays the current lifecycle state driven by the internal\n * {@link SessionRuntime} controller.\n * - `connect$()` opens a user-selected port, starts the internal read pump,\n * and transitions `idle -> connecting -> connected`.\n * - `disconnect$()` stops the read pump, closes the port, and transitions\n * `connected -> disconnecting -> idle`. When called during `connecting`,\n * it cancels the in-flight `connect$()` and returns to `idle`.\n * - `receive$` emits UTF-8 decoded text chunks pushed by the pump. It is\n * **not** subscription-lazy - the pump is started by `connect$` and\n * decoded text is multicast to all subscribers; late subscribers see only\n * new data.\n * - `lines$` emits the same decoded stream split into line-terminated\n * segments (`\\n`, `\\r\\n`); a trailing line without a terminator is\n * buffered. It is also not subscription-lazy relative to the pump.\n * - `send$` enqueues each payload on an internal FIFO queue so concurrent\n * subscribers are written to the port in call order. String payloads are\n * UTF-8 encoded through a shared `TextEncoder`.\n * - `errors$` multiplexes every {@link SerialError} produced by the\n * session. Connect / read / close failures are treated as fatal and\n * also drive `state$` to `'error'`; write failures are non-fatal and\n * do not mutate `state$` because a real connection loss will be\n * observed by the read pump on the next tick anyway.\n *\n * @param options - Session options. W3C connection fields (`baudRate`,\n * `dataBits`, \u2026) are passed to `port.open`. `filters` is forwarded to\n * `navigator.serial.requestPort`. `lineBuffer` / `terminalBuffer` configure\n * {@link SerialSession.lines$} and {@link SerialSession.terminalText$}.\n * Omitted fields use {@link resolveSerialSessionOptions} defaults; minimal\n * callers typically only set `baudRate`.\n * @returns A {@link SerialSession} instance.\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/202 | Issue #202}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/203 | Issue #203}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/204 | Issue #204}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/397 | Issue #397}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/399 | Issue #399}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/401 | Issue #401}\n */\nexport function createSerialSession(\n options?: SerialSessionOptions,\n): SerialSession {\n const resolvedOptions = resolveSerialSessionOptions(options);\n\n const controller = createSessionRuntimeController(\n createInitialRuntime(isWebSerialSupported()),\n );\n const errorsSubject = new Subject<SerialError>();\n const sendQueue = createSendQueue();\n const textEncoder = new TextEncoder();\n\n const isDisposed = (): boolean =>\n controller.status === SerialSessionStatus.Disposed;\n\n const receivePipeline = createReceivePipeline({ resolvedOptions });\n\n const { teardownPump, closePortSafely } = createPortTeardown({\n receivePipeline,\n });\n\n const { reportError, createDisposedError } = createSessionErrorReporter({\n controller,\n errorsSubject,\n sendQueue,\n isDisposed,\n teardownPump,\n closePortSafely,\n });\n\n // Route receive-side buffer overflows through the single reportError entry\n // point. The subscription completes when the pipeline completes on dispose.\n receivePipeline.bufferErrors$.subscribe((error) =>\n reportError(error, { fallbackCode: error.code }),\n );\n\n const lifecycle = createSessionLifecycle({\n controller,\n resolvedOptions,\n sendQueue,\n receivePipeline,\n errorsSubject,\n isDisposed,\n teardownPump,\n closePortSafely,\n reportError,\n createDisposedError,\n });\n\n const { receive$, lines$ } = receivePipeline;\n const errors$ = errorsSubject.asObservable();\n const terminalText$ = createTerminalBuffer(\n receive$,\n resolvedOptions.terminalBuffer,\n ).text$;\n\n return {\n connect$: lifecycle.connect$,\n disconnect$: lifecycle.disconnect$,\n dispose$: lifecycle.dispose$,\n send$(data: SerialPayload): Observable<void> {\n if (isDisposed()) {\n return new Observable<void>((subscriber) => {\n subscriber.error(createDisposedError());\n });\n }\n\n return sendQueue.enqueue(async () => {\n const payload =\n typeof data === 'string' ? textEncoder.encode(data) : data;\n try {\n await lifecycle.writeToPort(payload);\n } catch (error) {\n throw reportError(error, {\n fallbackCode: SerialErrorCode.WRITE_FAILED,\n messagePrefix: 'Failed to write data',\n });\n }\n });\n },\n state$: controller.state$,\n errors$,\n receive$,\n terminalText$,\n lines$,\n } satisfies SerialSession;\n}\n", "/**\n * Error codes for serial port operations.\n *\n * These codes identify specific error conditions that can occur when working with\n * serial ports. Each error code corresponds to a specific failure scenario, making\n * it easier to handle errors programmatically.\n *\n * @example\n * ```typescript\n * try {\n * await client.connect().toPromise();\n * } catch (error) {\n * if (error instanceof SerialError) {\n * switch (error.code) {\n * case SerialErrorCode.BROWSER_NOT_SUPPORTED:\n * console.error('Please use a supported desktop browser (Chrome, Edge, Opera, or Firefox 151+)');\n * break;\n * case SerialErrorCode.OPERATION_CANCELLED:\n * console.log('User cancelled port selection');\n * break;\n * // ... handle other error codes\n * }\n * }\n * }\n * ```\n */\nexport const SerialErrorCode = {\n /**\n * Browser does not support the Web Serial API.\n *\n * Emitted on `connect$` when `navigator.serial` is unavailable.\n *\n * **Suggested action**: Inform the user to use a supported browser.\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/438 | Issue #438}\n */\n BROWSER_NOT_SUPPORTED: 'BROWSER_NOT_SUPPORTED',\n\n /**\n * Serial port is not available.\n *\n * **Reserved \u2014 not emitted in v3.x.** The current implementation uses only\n * `navigator.serial.requestPort`; there is no `getPorts` API path. Scheduled\n * for removal in the next major version.\n *\n * **Suggested action**: Handle port acquisition failures with\n * {@link SerialErrorCode.PORT_OPEN_FAILED} or\n * {@link SerialErrorCode.OPERATION_CANCELLED} instead.\n *\n * @deprecated Not emitted at runtime in v3.x. Will be removed in the next major version.\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/438 | Issue #438}\n */\n PORT_NOT_AVAILABLE: 'PORT_NOT_AVAILABLE',\n\n /**\n * Failed to open the serial port.\n *\n * Emitted on `connect$` when `port.open()` rejects.\n *\n * **Suggested action**: Verify connection parameters and check hardware connections.\n */\n PORT_OPEN_FAILED: 'PORT_OPEN_FAILED',\n\n /**\n * Serial port is already open.\n *\n * Emitted on `connect$` when the session is not in `'idle'` or `'error'`\n * (non-fatal; session state is unchanged).\n *\n * **Suggested action**: Disconnect the current port before connecting a new one.\n */\n PORT_ALREADY_OPEN: 'PORT_ALREADY_OPEN',\n\n /**\n * Serial port is not open.\n *\n * Emitted when `send$` or `disconnect$` is called in an invalid session\n * state (for example before `connect$` completes).\n *\n * **Suggested action**: Call {@link SerialSession.connect$} before sending data.\n */\n PORT_NOT_OPEN: 'PORT_NOT_OPEN',\n\n /**\n * Failed to read from the serial port.\n *\n * This error occurs when reading data from the port fails, typically due to\n * connection loss, hardware issues, or stream errors.\n *\n * **Suggested action**: Check the connection and hardware, then retry the read operation.\n */\n READ_FAILED: 'READ_FAILED',\n\n /**\n * Failed to write to the serial port.\n *\n * This error occurs when writing data to the port fails, typically due to\n * connection loss, hardware issues, or stream errors.\n *\n * **Suggested action**: Check the connection and hardware, then retry the write operation.\n */\n WRITE_FAILED: 'WRITE_FAILED',\n\n /**\n * Serial port connection was lost.\n *\n * This error occurs when the connection to the serial port is unexpectedly\n * terminated, such as when the device is disconnected or the port is closed\n * by another process.\n *\n * **Suggested action**: Check the physical connection and reconnect if needed.\n */\n CONNECTION_LOST: 'CONNECTION_LOST',\n\n /**\n * Invalid filter options provided.\n *\n * This error occurs when port filter options are invalid, such as when\n * filter values are out of range or missing required fields.\n *\n * **Suggested action**: Verify filter options match the expected format and value ranges.\n */\n INVALID_FILTER_OPTIONS: 'INVALID_FILTER_OPTIONS',\n\n /**\n * Operation was cancelled by the user.\n *\n * This error occurs when the user cancels a port selection dialog or aborts\n * an operation before it completes.\n *\n * **Suggested action**: This is a normal condition - no action required, but you may want\n * to inform the user that the operation was cancelled.\n */\n OPERATION_CANCELLED: 'OPERATION_CANCELLED',\n\n /**\n * Operation timed out before completion.\n *\n * **Reserved \u2014 not emitted in v3.x.** No timeout / prompt detection /\n * transaction API exists yet. Scheduled for removal in the next major version\n * unless a future API adopts this code.\n *\n * @deprecated Not emitted at runtime in v3.x. Will be removed in the next major version.\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/438 | Issue #438}\n */\n OPERATION_TIMEOUT: 'OPERATION_TIMEOUT',\n\n /**\n * Internal line buffer exceeded its configured size limit.\n *\n * This error occurs when the incomplete line tail held by the {@link SerialSession.lines$}\n * framing buffer grows beyond {@link SerialSessionOptions.lineBuffer} `maxChars`.\n * Leading characters are discarded to bound memory; the session remains connected.\n *\n * **Suggested action**: Increase `lineBuffer.maxChars`, handle framing on `receive$`,\n * or ensure the device sends line terminators.\n */\n LINE_BUFFER_OVERFLOW: 'LINE_BUFFER_OVERFLOW',\n\n /**\n * Invalid terminal buffer options provided.\n *\n * This error occurs when {@link SerialSessionOptions.terminalBuffer} values are\n * out of range, such as a negative or non-integer `maxLines` or `maxChars`.\n *\n * **Suggested action**: Verify `terminalBuffer` options match the documented\n * ranges and value types.\n */\n INVALID_TERMINAL_BUFFER_OPTIONS: 'INVALID_TERMINAL_BUFFER_OPTIONS',\n\n /**\n * Invalid line buffer options provided.\n *\n * This error occurs when {@link SerialSessionOptions.lineBuffer} values are\n * out of range, such as a negative or non-integer `maxChars`.\n *\n * **Suggested action**: Verify `lineBuffer` options match the documented\n * ranges and value types.\n */\n INVALID_LINE_BUFFER_OPTIONS: 'INVALID_LINE_BUFFER_OPTIONS',\n\n /**\n * Invalid connection options provided.\n *\n * This error occurs when connection fields such as `baudRate` are out of\n * range at session creation time.\n *\n * **Suggested action**: Verify connection options match the documented\n * ranges and value types.\n */\n INVALID_CONNECTION_OPTIONS: 'INVALID_CONNECTION_OPTIONS',\n\n /**\n * Session has been disposed and can no longer be used.\n *\n * This error occurs when calling {@link SerialSession.connect$} or\n * {@link SerialSession.send$} after {@link SerialSession.dispose$} has\n * completed. Create a new session instead of reusing a disposed instance.\n *\n * **Suggested action**: Call {@link SerialSession.dispose$} only when\n * permanently tearing down a session, then create a new one if needed.\n */\n SESSION_DISPOSED: 'SESSION_DISPOSED',\n\n /**\n * Unknown error occurred.\n *\n * Emitted as a fallback when dispose or disconnect encounters an error that\n * cannot be classified more specifically. The underlying failure is on\n * {@link SerialError.context | context.cause}.\n *\n * **Suggested action**: Check the error message and `context.cause` for more details.\n */\n UNKNOWN: 'UNKNOWN',\n} as const;\n\n/**\n * String union of allowed {@link SerialErrorCode} runtime values\n * (same set as the values on the {@link SerialErrorCode} object).\n */\nexport type SerialErrorCode =\n (typeof SerialErrorCode)[keyof typeof SerialErrorCode];\n", "import { type Observable, map, scan, shareReplay } from 'rxjs';\nimport { SerialError } from '../errors/serial-error';\nimport { SerialErrorCode } from '../errors/serial-error-code';\nimport {\n brandMaxChars,\n brandMaxLines,\n type MaxChars,\n type MaxLines,\n} from '../internal/branded-numbers';\nimport { createNewlineTokenizer } from '../internal/newline-tokenizer';\nimport { createAnsiStripper } from '../internal/strip-ansi-sequences';\n\n/** @internal Folded state between {@link createTerminalBuffer} emissions. */\nexport interface TerminalBufferState {\n completed: string;\n currentLine: string;\n}\n\n/**\n * Applies one raw decoder chunk to terminal display state.\n * Handles `\\r\\n` and lone `\\n` as line endings, and lone `\\r` as\n * carriage return (clear current line for redraw). When\n * {@link TerminalBufferOptions.stripAnsi} is enabled (default), ANSI escape\n * sequences are removed before line folding.\n *\n * @internal Exported for unit tests.\n */\nexport function applyTerminalChunk(\n state: TerminalBufferState,\n chunk: string,\n): TerminalBufferState {\n const tokenizer = createNewlineTokenizer('terminal');\n tokenizer.restorePending(state.currentLine);\n const events = tokenizer.feed(chunk);\n\n let { completed } = state;\n for (const event of events) {\n if (event.type === 'line') {\n completed += event.content + '\\n';\n }\n }\n\n return {\n completed,\n currentLine: tokenizer.getPendingText(),\n };\n}\n\n/** @internal */\nexport function terminalDisplayText(state: TerminalBufferState): string {\n return state.completed + state.currentLine;\n}\n\n/** Resolved limits for {@link trimTerminalState}. `0` means unlimited. */\nexport interface TerminalBufferLimits {\n maxLines: MaxLines;\n maxChars: MaxChars;\n}\n\n/** @internal Count newline-terminated rows in `completed`. */\nexport function countCompletedLines(completed: string): number {\n if (completed.length === 0) {\n return 0;\n }\n let count = 0;\n for (let i = 0; i < completed.length; i++) {\n if (completed.charAt(i) === '\\n') {\n count++;\n }\n }\n return count;\n}\n\n/**\n * Drops oldest completed lines when `maxLines` is exceeded.\n *\n * @internal Exported for unit tests.\n */\nexport function trimCompletedByMaxLines(\n completed: string,\n maxLines: number,\n): string {\n if (maxLines <= 0) {\n return completed;\n }\n\n let trimmed = completed;\n while (countCompletedLines(trimmed) > maxLines) {\n const firstNewline = trimmed.indexOf('\\n');\n if (firstNewline < 0) {\n break;\n }\n trimmed = trimmed.slice(firstNewline + 1);\n }\n return trimmed;\n}\n\n/**\n * Trims {@link TerminalBufferState} to respect memory limits. Oldest\n * `completed` content is removed first; `currentLine` is trimmed only when\n * the display text still exceeds `maxChars` after `completed` is empty.\n *\n * @internal Exported for unit tests.\n */\nexport function trimTerminalState(\n state: TerminalBufferState,\n limits: TerminalBufferLimits,\n): TerminalBufferState {\n let { completed, currentLine } = state;\n\n if (limits.maxLines > 0) {\n completed = trimCompletedByMaxLines(completed, limits.maxLines);\n }\n\n if (limits.maxChars > 0) {\n let total = completed.length + currentLine.length;\n while (total > limits.maxChars) {\n const excess = total - limits.maxChars;\n if (completed.length >= excess) {\n completed = completed.slice(excess);\n break;\n }\n const removeFromCurrent = excess - completed.length;\n completed = '';\n currentLine = currentLine.slice(removeFromCurrent);\n total = completed.length + currentLine.length;\n }\n }\n\n return { completed, currentLine };\n}\n\nexport interface TerminalBuffer {\n /**\n * Cumulative text suitable for terminal-style display: completed lines plus\n * the current line, with `\\r` redraws collapsed per Issue #275.\n */\n readonly text$: Observable<string>;\n}\n\n/**\n * Options for {@link createTerminalBuffer} memory limits.\n *\n * Character counts use UTF-16 string length (JavaScript `.length`).\n * Pass `0` for `maxLines` or `maxChars` to disable that limit (unlimited).\n */\nexport interface TerminalBufferOptions {\n /**\n * Maximum number of completed lines to retain in the display buffer.\n * `0` means unlimited. Must be a safe integer `>= 0` when validated.\n *\n * @default 10000\n */\n maxLines?: number;\n\n /**\n * Maximum total characters in the cumulative display text\n * (`completed` + `currentLine`). Oldest content is dropped first.\n * `0` means unlimited. Must be a safe integer `>= 0` when validated.\n *\n * @default 1048576\n */\n maxChars?: number;\n\n /**\n * When `true`, strips ANSI escape sequences from incoming chunks before\n * folding carriage-return redraws. Use `false` to preserve raw escape\n * codes in {@link TerminalBuffer.text$}.\n *\n * @default true\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/428 | Issue #428}\n */\n stripAnsi?: boolean;\n}\n\n/** Default limits applied when options are omitted. */\nexport const DEFAULT_TERMINAL_BUFFER_OPTIONS: Required<TerminalBufferOptions> =\n {\n maxLines: 10_000,\n maxChars: 1_048_576,\n stripAnsi: true,\n };\n\n/** Resolved terminal buffer options with validated branded numeric fields. */\nexport type ResolvedTerminalBufferOptions = {\n maxLines: MaxLines;\n maxChars: MaxChars;\n stripAnsi: boolean;\n};\n\n/**\n * Merge and validate {@link TerminalBufferOptions}.\n *\n * This is the single normalization path for terminal buffer defaults and\n * boundary checks (session factory and standalone {@link createTerminalBuffer}).\n *\n * @throws {@link SerialError} with {@link SerialErrorCode.INVALID_TERMINAL_BUFFER_OPTIONS}\n * when `maxLines` or `maxChars` are out of range.\n */\nexport function resolveTerminalBufferOptions(\n options?: TerminalBufferOptions,\n): ResolvedTerminalBufferOptions {\n const merged: Required<TerminalBufferOptions> = {\n ...DEFAULT_TERMINAL_BUFFER_OPTIONS,\n ...options,\n };\n\n const { maxLines, maxChars } = merged;\n\n if (!Number.isSafeInteger(maxLines) || maxLines < 0) {\n throw new SerialError(\n SerialErrorCode.INVALID_TERMINAL_BUFFER_OPTIONS,\n `Invalid terminalBuffer.maxLines: ${maxLines}. Must be a safe integer >= 0.`,\n undefined,\n {\n field: 'terminalBuffer.maxLines',\n value: maxLines,\n constraint: 'non-negative-safe-integer',\n },\n );\n }\n\n if (!Number.isSafeInteger(maxChars) || maxChars < 0) {\n throw new SerialError(\n SerialErrorCode.INVALID_TERMINAL_BUFFER_OPTIONS,\n `Invalid terminalBuffer.maxChars: ${maxChars}. Must be a safe integer >= 0.`,\n undefined,\n {\n field: 'terminalBuffer.maxChars',\n value: maxChars,\n constraint: 'non-negative-safe-integer',\n },\n );\n }\n\n return {\n maxLines: brandMaxLines(maxLines),\n maxChars: brandMaxChars(maxChars),\n stripAnsi: merged.stripAnsi,\n };\n}\n\nconst initialTerminalState: TerminalBufferState = {\n completed: '',\n currentLine: '',\n};\n\n/**\n * Builds a terminal-oriented text stream from {@link SerialSession.receive$} (or any\n * `Observable<string>` of decoded chunks). Uses internal buffering so callers need not\n * implement carriage-return collapse themselves.\n *\n * By default, retains at most {@link DEFAULT_TERMINAL_BUFFER_OPTIONS.maxLines}\n * completed lines and {@link DEFAULT_TERMINAL_BUFFER_OPTIONS.maxChars} characters\n * so long-running sessions do not grow memory without bound. Pass `0` for either\n * limit to disable that constraint.\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/370 | Issue #370}\n */\nexport function createTerminalBuffer(\n receive$: Observable<string>,\n options?: TerminalBufferOptions,\n): TerminalBuffer {\n const resolved = resolveTerminalBufferOptions(options);\n const limits: TerminalBufferLimits = {\n maxLines: resolved.maxLines,\n maxChars: resolved.maxChars,\n };\n const ansiStripper = resolved.stripAnsi ? createAnsiStripper() : null;\n\n const text$ = receive$.pipe(\n scan((state, chunk: string) => {\n const normalized =\n ansiStripper !== null ? ansiStripper.feed(chunk) : chunk;\n const next = applyTerminalChunk(state, normalized);\n return trimTerminalState(next, limits);\n }, initialTerminalState),\n map(terminalDisplayText),\n shareReplay({ bufferSize: 1, refCount: true }),\n );\n\n return { text$ };\n}\n", "import { SerialErrorCode } from './serial-error-code';\n\n/**\n * Context payload for errors that wrap an underlying failure.\n */\nexport type SerialErrorCauseContext = {\n readonly cause: unknown;\n};\n\n/**\n * Machine-readable constraint identifiers for factory-time validation errors.\n */\nexport type ValidationErrorConstraint =\n | 'at-least-one-usb-id'\n | 'usb-id-0-65535'\n | 'positive-safe-integer'\n | 'non-negative-safe-integer';\n\n/**\n * Structured metadata for `INVALID_*` validation errors thrown at session\n * factory time. {@link SerialError.message} remains human-readable.\n */\nexport type ValidationErrorContext = {\n readonly field: string;\n readonly value: unknown;\n readonly constraint: ValidationErrorConstraint;\n readonly filterIndex?: number;\n};\n\n/**\n * Maps each {@link SerialErrorCode} to its structured context shape.\n *\n * Codes mapped to `undefined` have no machine-readable metadata beyond\n * {@link SerialError.message}. Overflow and validation codes expose structured\n * metadata so callers do not need to parse error messages.\n */\nexport interface SerialErrorContextMap {\n [SerialErrorCode.BROWSER_NOT_SUPPORTED]: undefined;\n [SerialErrorCode.PORT_NOT_AVAILABLE]: SerialErrorCauseContext;\n [SerialErrorCode.PORT_OPEN_FAILED]: SerialErrorCauseContext;\n [SerialErrorCode.PORT_ALREADY_OPEN]: undefined;\n [SerialErrorCode.PORT_NOT_OPEN]: undefined;\n [SerialErrorCode.READ_FAILED]: SerialErrorCauseContext;\n [SerialErrorCode.WRITE_FAILED]: SerialErrorCauseContext;\n [SerialErrorCode.CONNECTION_LOST]: SerialErrorCauseContext;\n [SerialErrorCode.INVALID_FILTER_OPTIONS]: ValidationErrorContext;\n [SerialErrorCode.OPERATION_CANCELLED]: SerialErrorCauseContext;\n [SerialErrorCode.OPERATION_TIMEOUT]: undefined;\n [SerialErrorCode.LINE_BUFFER_OVERFLOW]: {\n readonly maxChars: number;\n };\n [SerialErrorCode.INVALID_TERMINAL_BUFFER_OPTIONS]: ValidationErrorContext;\n [SerialErrorCode.INVALID_LINE_BUFFER_OPTIONS]: ValidationErrorContext;\n [SerialErrorCode.INVALID_CONNECTION_OPTIONS]: ValidationErrorContext;\n [SerialErrorCode.SESSION_DISPOSED]: undefined;\n [SerialErrorCode.UNKNOWN]: SerialErrorCauseContext;\n}\n\nconst CAUSE_CONTEXT_CODES = new Set<SerialErrorCode>([\n SerialErrorCode.PORT_NOT_AVAILABLE,\n SerialErrorCode.PORT_OPEN_FAILED,\n SerialErrorCode.READ_FAILED,\n SerialErrorCode.WRITE_FAILED,\n SerialErrorCode.CONNECTION_LOST,\n SerialErrorCode.OPERATION_CANCELLED,\n SerialErrorCode.UNKNOWN,\n]);\n\n/**\n * @internal\n */\nexport function isCauseContextCode(\n code: SerialErrorCode,\n): code is keyof {\n [K in keyof SerialErrorContextMap as SerialErrorContextMap[K] extends SerialErrorCauseContext\n ? K\n : never]: SerialErrorContextMap[K];\n} {\n return CAUSE_CONTEXT_CODES.has(code);\n}\n", "import { SerialErrorCode } from './serial-error-code';\nimport {\n isCauseContextCode,\n type SerialErrorCauseContext,\n type SerialErrorContextMap,\n type ValidationErrorConstraint,\n type ValidationErrorContext,\n} from './serial-error-context';\n\n// Re-export SerialErrorCode for convenience\nexport { SerialErrorCode };\nexport type {\n SerialErrorCauseContext,\n SerialErrorContextMap,\n ValidationErrorConstraint,\n ValidationErrorContext,\n};\n\ninterface V8ErrorCaptureStackTrace {\n captureStackTrace?(\n targetObject: object,\n constructorOpt?: abstract new (...args: never[]) => unknown,\n ): void;\n}\n\nconst ErrorWithCaptureStackTrace = Error as typeof Error &\n V8ErrorCaptureStackTrace;\n\n/**\n * Custom error class for serial port operations.\n *\n * This error class extends the standard Error class and includes additional information\n * about the type of error that occurred. It provides an error code for programmatic\n * error handling and may include the original error that caused the failure.\n *\n * @example\n * ```typescript\n * session.errors$.subscribe((error) => {\n * if (error.is(SerialErrorCode.READ_FAILED)) {\n * console.error(`Error code: ${error.code}`);\n * console.error(`Message: ${error.message}`);\n * console.error(`Cause:`, error.context.cause);\n * }\n *\n * if (error.is(SerialErrorCode.LINE_BUFFER_OVERFLOW)) {\n * console.error(`maxChars: ${error.context.maxChars}`);\n * }\n *\n * if (error.is(SerialErrorCode.BROWSER_NOT_SUPPORTED)) {\n * // Handle browser not supported\n * }\n * });\n * ```\n */\nexport class SerialError<\n TCode extends SerialErrorCode = SerialErrorCode,\n> extends Error {\n /**\n * The error code identifying the type of error that occurred.\n *\n * Use this code to programmatically handle specific error conditions.\n *\n * @see {@link SerialErrorCode} for all available error codes\n */\n public readonly code: TCode;\n\n /**\n * Structured metadata associated with {@link code}.\n *\n * When {@link is} returns `true`, TypeScript narrows this property to the\n * context shape defined in {@link SerialErrorContextMap} for that code.\n */\n public readonly context: SerialErrorContextMap[TCode];\n\n /**\n * The original error that caused this SerialError, if available.\n *\n * This property contains the underlying error (e.g., DOMException, TypeError)\n * that was wrapped in this SerialError. It may be undefined if no original error exists.\n *\n * @deprecated Prefer {@link context} for cause-bearing codes. This property is\n * retained for backward compatibility and is kept in sync when a cause is\n * provided.\n */\n public readonly originalError?: Error;\n\n /**\n * Creates a new SerialError instance.\n *\n * @param code - The error code identifying the type of error\n * @param message - A human-readable error message\n * @param originalError - The original error that caused this SerialError, if any.\n * @deprecated Prefer passing `{ cause }` via {@link context}. When omitted,\n * cause-bearing codes derive `{ cause }` from this argument for backward\n * compatibility.\n * @param context - Structured metadata for the error code. For cause-bearing\n * codes, pass `{ cause }` here. When omitted, cause-bearing codes derive\n * `{ cause }` from the legacy {@link originalError} argument.\n */\n constructor(\n code: TCode,\n message: string,\n originalError?: Error,\n context?: SerialErrorContextMap[TCode],\n ) {\n super(message);\n this.name = 'SerialError';\n this.code = code;\n\n if (context !== undefined) {\n this.context = context;\n } else if (\n originalError !== undefined &&\n isCauseContextCode(code)\n ) {\n this.context = { cause: originalError } as SerialErrorContextMap[TCode];\n } else {\n this.context = undefined as SerialErrorContextMap[TCode];\n }\n\n if (originalError !== undefined) {\n this.originalError = originalError;\n } else if (isCauseContextCode(code) && this.context !== undefined) {\n const causeContext = this.context as SerialErrorCauseContext;\n if (causeContext.cause instanceof Error) {\n this.originalError = causeContext.cause;\n }\n }\n\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n if (ErrorWithCaptureStackTrace.captureStackTrace) {\n ErrorWithCaptureStackTrace.captureStackTrace(this, SerialError);\n }\n }\n\n /**\n * Check if the error matches a specific error code.\n *\n * This is a convenience method for checking the error code without directly\n * comparing the code property. When this method returns `true`, TypeScript\n * narrows `this.code` and `this.context` to the shapes defined for the\n * provided `code` argument.\n *\n * @param code - The error code to check against\n * @returns Type predicate: `true` if this error's code matches the provided\n * code (and `this.code` / `this.context` are narrowed), `false` otherwise\n *\n * @example\n * ```typescript\n * if (error.is(SerialErrorCode.LINE_BUFFER_OVERFLOW)) {\n * // error.code and error.context.maxChars are narrowed\n * }\n * ```\n */\n public is<C extends SerialErrorCode>(\n code: C,\n ): this is SerialError<C> {\n return (this.code as SerialErrorCode) === code;\n }\n}\n", "declare const baudRateBrand: unique symbol;\n/** Validated serial port baud rate (safe integer > 0). */\nexport type BaudRate = number & { readonly [baudRateBrand]: true };\n\ndeclare const serialPortBufferSizeBrand: unique symbol;\n/** Validated W3C {@link SerialOptions} `bufferSize` (safe integer > 0). */\nexport type SerialPortBufferSize = number & {\n readonly [serialPortBufferSizeBrand]: true;\n};\n\ndeclare const maxCharsBrand: unique symbol;\n/** Validated character limit for buffers (`0` means unlimited). */\nexport type MaxChars = number & { readonly [maxCharsBrand]: true };\n\ndeclare const maxLinesBrand: unique symbol;\n/** Validated line count limit for terminal display (`0` means unlimited). */\nexport type MaxLines = number & { readonly [maxLinesBrand]: true };\n\n/** @internal Brand a validated baud rate. */\nexport function brandBaudRate(value: number): BaudRate {\n return value as BaudRate;\n}\n\n/** @internal Brand a validated serial port buffer size. */\nexport function brandSerialPortBufferSize(value: number): SerialPortBufferSize {\n return value as SerialPortBufferSize;\n}\n\n/** @internal Brand a validated max character limit. */\nexport function brandMaxChars(value: number): MaxChars {\n return value as MaxChars;\n}\n\n/** @internal Brand a validated max line limit. */\nexport function brandMaxLines(value: number): MaxLines {\n return value as MaxLines;\n}\n", "/**\n * Shared CR/LF/CRLF scanner for {@link createLineBuffer} and\n * {@link applyTerminalChunk}. Mode controls intentional semantic differences\n * between line-delimited output and terminal display redraw.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/376 | Issue #376}\n */\nexport type NewlineTokenizerMode = 'line' | 'terminal';\n\nexport type NewlineEvent =\n | { type: 'line'; content: string }\n | { type: 'carriage-return' };\n\nexport interface NewlineTokenizer {\n feed(chunk: string): NewlineEvent[];\n clear(): void;\n /** Replaces the incomplete tail (used when folding external state in). */\n restorePending(text: string): void;\n /** Incomplete tail retained across feeds (line mode may end with `\\r`). */\n getPendingText(): string;\n /**\n * Discards leading characters when pending text exceeds `maxChars`.\n * @returns `true` when trimming occurred.\n */\n trimPending(maxChars: number): boolean;\n}\n\n/**\n * Creates a streaming newline tokenizer.\n *\n * - **line**: interior lone `\\r` completes a line (#237); trailing `\\r` is\n * deferred until the next chunk disambiguates `\\r` vs `\\r\\n`.\n * - **terminal**: lone `\\r` clears the current line (#275); trailing `\\r`\n * applies immediately.\n */\nexport function createNewlineTokenizer(\n mode: NewlineTokenizerMode,\n): NewlineTokenizer {\n let pending = '';\n let deferredTrailingCr = false;\n\n const clear = (): void => {\n pending = '';\n deferredTrailingCr = false;\n };\n\n const restorePending = (text: string): void => {\n pending = text;\n deferredTrailingCr = false;\n };\n\n const feed = (chunk: string): NewlineEvent[] => {\n const events: NewlineEvent[] = [];\n let i = 0;\n\n if (mode === 'line' && deferredTrailingCr) {\n deferredTrailingCr = false;\n if (chunk.length > 0 && chunk.charAt(0) === '\\n') {\n events.push({ type: 'line', content: pending });\n pending = '';\n i = 1;\n } else {\n events.push({ type: 'line', content: pending });\n pending = '';\n }\n }\n\n const len = chunk.length;\n for (; i < len; i++) {\n const c = chunk.charAt(i);\n if (c === '\\r') {\n const next = i + 1 < len ? chunk.charAt(i + 1) : '';\n if (next === '\\n') {\n events.push({ type: 'line', content: pending });\n pending = '';\n i++;\n continue;\n }\n\n if (mode === 'line') {\n if (next !== '') {\n events.push({ type: 'line', content: pending });\n pending = '';\n continue;\n }\n deferredTrailingCr = true;\n break;\n }\n\n events.push({ type: 'carriage-return' });\n pending = '';\n continue;\n }\n\n if (c === '\\n') {\n events.push({ type: 'line', content: pending });\n pending = '';\n continue;\n }\n\n pending += c;\n }\n\n return events;\n };\n\n const trimPending = (maxChars: number): boolean => {\n if (maxChars <= 0) {\n return false;\n }\n\n const text = deferredTrailingCr ? `${pending}\\r` : pending;\n if (text.length <= maxChars) {\n return false;\n }\n\n const trimmed = text.slice(text.length - maxChars);\n if (trimmed.endsWith('\\r')) {\n pending = trimmed.slice(0, -1);\n deferredTrailingCr = true;\n } else {\n pending = trimmed;\n deferredTrailingCr = false;\n }\n return true;\n };\n\n return {\n feed,\n clear,\n restorePending,\n getPendingText: () => (deferredTrailingCr ? `${pending}\\r` : pending),\n trimPending,\n };\n}\n", "const ESC = '\\u001b';\n\n/** CSI final byte range per ECMA-48. */\nconst CSI_FINAL_BYTE = /[@-~]/;\n\nconst BEL = '\\u0007';\nconst OSC_STRING_TERMINATOR = '\\u001b\\\\';\n\nfunction findOscTerminator(\n oscBody: string,\n): { index: number; length: number } | null {\n let earliest: { index: number; length: number } | null = null;\n\n const belIndex = oscBody.indexOf(BEL);\n if (belIndex >= 0) {\n earliest = { index: belIndex, length: 1 };\n }\n\n const stIndex = oscBody.indexOf(OSC_STRING_TERMINATOR);\n if (stIndex >= 0) {\n const candidate = { index: stIndex, length: OSC_STRING_TERMINATOR.length };\n if (!earliest || stIndex < earliest.index) {\n earliest = candidate;\n }\n }\n\n return earliest;\n}\n\n/**\n * Stateful ANSI escape stripper for streaming text chunks.\n *\n * Buffers incomplete sequences that span Web Serial read boundaries so\n * partial ESC bytes are not emitted as visible text.\n *\n * @internal\n */\nexport interface AnsiStripper {\n feed(chunk: string): string;\n flush(): string;\n}\n\n/**\n * Strips ANSI escape sequences from a complete string in one pass.\n *\n * @internal\n */\nexport function stripAnsiSequences(text: string): string {\n const stripper = createAnsiStripper();\n return stripper.feed(text) + stripper.flush();\n}\n\n/**\n * Creates a streaming ANSI escape stripper.\n *\n * @internal\n */\nexport function createAnsiStripper(): AnsiStripper {\n let pending = '';\n\n const feed = (chunk: string): string => {\n const input = pending + chunk;\n pending = '';\n let output = '';\n let i = 0;\n\n while (i < input.length) {\n const escIndex = input.indexOf(ESC, i);\n if (escIndex < 0) {\n output += input.slice(i);\n break;\n }\n\n output += input.slice(i, escIndex);\n const next = escIndex + 1;\n if (next >= input.length) {\n pending = ESC;\n break;\n }\n\n const code = input.charCodeAt(next);\n\n // CSI: ESC [\n if (code === 0x5b) {\n const csiStart = next + 1;\n let j = csiStart;\n while (j < input.length && !CSI_FINAL_BYTE.test(input.charAt(j))) {\n j++;\n }\n if (j < input.length) {\n i = j + 1;\n continue;\n }\n pending = input.slice(escIndex);\n break;\n }\n\n // OSC: ESC ]\n if (code === 0x5d) {\n const oscBody = input.slice(next);\n const terminator = findOscTerminator(oscBody);\n if (terminator) {\n i = escIndex + 1 + terminator.index + terminator.length;\n continue;\n }\n pending = input.slice(escIndex);\n break;\n }\n\n // Two-character ESC sequences (e.g. ESC M, ESC 7, ESC 8)\n if (next < input.length) {\n i = next + 1;\n continue;\n }\n\n pending = input.slice(escIndex);\n break;\n }\n\n return output;\n };\n\n const flush = (): string => {\n if (pending.length === 0) {\n return '';\n }\n const remainder = pending;\n pending = '';\n return remainder;\n };\n\n return { feed, flush };\n}\n", "/**\n * Synchronous feature detection for the Web Serial API.\n *\n * Prefer this helper **before** creating a session. After a session exists,\n * drive unsupported UI from `state$` with `SerialSessionStatus.Unsupported`.\n * This helper is SSR-safe: it returns `false` when `navigator` is not available.\n *\n * @returns `true` when `navigator.serial` is available.\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/490 | Issue #490}\n */\nexport function isWebSerialSupported(): boolean {\n return (\n typeof navigator !== 'undefined' &&\n 'serial' in navigator &&\n navigator.serial !== undefined &&\n navigator.serial !== null\n );\n}\n", "import type { ReadPump } from '../read-pump';\nimport type { ReceivePipeline } from './receive-pipeline';\n\n/**\n * Dependencies for {@link createPortTeardown}.\n *\n * @internal\n */\nexport interface PortTeardownDeps {\n receivePipeline: ReceivePipeline;\n}\n\n/**\n * Port and pump teardown primitives for {@link createSerialSession}.\n *\n * These are extracted from the session lifecycle so that both the lifecycle\n * and the error reporter can depend on them without forming a construction\n * cycle: the error reporter needs `teardownPump` / `closePortSafely` for fatal\n * cleanup, while the lifecycle needs the error reporter for `reportError`.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/476 | Issue #476}\n */\nexport interface PortTeardown {\n teardownPump: (pump: ReadPump | null) => Promise<void>;\n closePortSafely: (port: SerialPort | null) => Promise<void>;\n}\n\n/**\n * @internal\n */\nexport function createPortTeardown(deps: PortTeardownDeps): PortTeardown {\n const { receivePipeline } = deps;\n\n const teardownPump = async (pump: ReadPump | null): Promise<void> => {\n receivePipeline.clearLineBuffer();\n if (pump) {\n await pump.stop();\n }\n };\n\n const closePortSafely = async (port: SerialPort | null): Promise<void> => {\n if (!port) {\n return;\n }\n try {\n await port.close();\n } catch {\n // The read pump may already have errored the stream, which makes\n // close() reject. We ignore it here because disconnect$ has a\n // dedicated error path for close failures initiated by the user.\n }\n };\n\n return { teardownPump, closePortSafely };\n}\n", "import { type Observable, Subject } from 'rxjs';\nimport { SerialError } from '../../errors/serial-error';\nimport { SerialErrorCode } from '../../errors/serial-error-code';\nimport { createLineBuffer, type LineBuffer } from './line-buffer';\nimport type { ResolvedSerialSessionOptions } from '../serial-session-options';\n\n/**\n * Dependencies for {@link createReceivePipeline}.\n *\n * @internal\n */\nexport interface ReceivePipelineDeps {\n resolvedOptions: ResolvedSerialSessionOptions;\n}\n\n/**\n * Receive-side stream wiring for {@link createSerialSession}.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/401 | Issue #401}\n */\nexport interface ReceivePipeline {\n receive$: Observable<string>;\n lines$: Observable<string>;\n /**\n * Buffer-overflow errors produced while decoding chunks. The factory pipes\n * these into the session's single error-reporting entry point so the receive\n * pipeline stays decoupled from error reporting and initialization order.\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/476 | Issue #476}\n */\n bufferErrors$: Observable<SerialError>;\n clearLineBuffer: () => void;\n handleChunk: (text: string) => void;\n complete: () => void;\n}\n\n/**\n * @internal\n */\nexport function createReceivePipeline(\n deps: ReceivePipelineDeps,\n): ReceivePipeline {\n const { resolvedOptions } = deps;\n\n const receiveSubject = new Subject<string>();\n const linesSubject = new Subject<string>();\n const bufferErrorsSubject = new Subject<SerialError>();\n const lineBuffer: LineBuffer = createLineBuffer(resolvedOptions.lineBuffer);\n\n const receive$ = receiveSubject.asObservable();\n const lines$ = linesSubject.asObservable();\n const bufferErrors$ = bufferErrorsSubject.asObservable();\n\n const clearLineBuffer = (): void => {\n lineBuffer.clear();\n };\n\n const handleChunk = (text: string): void => {\n receiveSubject.next(text);\n const { lines, overflowed } = lineBuffer.feed(text);\n if (overflowed) {\n bufferErrorsSubject.next(\n new SerialError(\n SerialErrorCode.LINE_BUFFER_OVERFLOW,\n 'Line buffer exceeded maxChars; leading data was discarded',\n undefined,\n { maxChars: resolvedOptions.lineBuffer.maxChars },\n ),\n );\n }\n for (const line of lines) {\n linesSubject.next(line);\n }\n };\n\n const complete = (): void => {\n receiveSubject.complete();\n linesSubject.complete();\n bufferErrorsSubject.complete();\n };\n\n return {\n receive$,\n lines$,\n bufferErrors$,\n clearLineBuffer,\n handleChunk,\n complete,\n };\n}\n", "import { createNewlineTokenizer } from '../../internal/newline-tokenizer';\nimport type { MaxChars } from '../../internal/branded-numbers';\nimport { brandMaxChars } from '../../internal/branded-numbers';\nimport { SerialError } from '../../errors/serial-error';\nimport { SerialErrorCode } from '../../errors/serial-error-code';\n\n/**\n * Options for {@link createLineBuffer}.\n *\n * Character counts use UTF-16 string length (JavaScript `.length`).\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/371 | Issue #371}\n */\nexport interface LineBufferOptions {\n /**\n * Maximum characters retained in the incomplete line tail (no line terminator yet).\n * When exceeded, leading characters are discarded. `0` means unlimited.\n * Must be a safe integer `>= 0` when validated.\n *\n * @default 1048576\n */\n maxChars?: number;\n}\n\n/** Default limits applied when {@link LineBufferOptions} fields are omitted. */\nexport const DEFAULT_LINE_BUFFER_OPTIONS: Required<LineBufferOptions> = {\n maxChars: 1_048_576,\n};\n\n/** Resolved line buffer options with validated branded numeric fields. */\nexport type ResolvedLineBufferOptions = {\n maxChars: MaxChars;\n};\n\n/**\n * Merge and validate {@link LineBufferOptions}.\n *\n * This is the single normalization path for line buffer defaults and boundary\n * checks used by {@link resolveSerialSessionOptions}.\n *\n * @throws {@link SerialError} with {@link SerialErrorCode.INVALID_LINE_BUFFER_OPTIONS}\n * when `maxChars` is out of range.\n */\nexport function resolveLineBufferOptions(\n options?: LineBufferOptions,\n): ResolvedLineBufferOptions {\n const merged: Required<LineBufferOptions> = {\n ...DEFAULT_LINE_BUFFER_OPTIONS,\n ...options,\n };\n\n const { maxChars } = merged;\n\n if (!Number.isSafeInteger(maxChars) || maxChars < 0) {\n throw new SerialError(\n SerialErrorCode.INVALID_LINE_BUFFER_OPTIONS,\n `Invalid lineBuffer.maxChars: ${maxChars}. Must be a safe integer >= 0.`,\n undefined,\n {\n field: 'lineBuffer.maxChars',\n value: maxChars,\n constraint: 'non-negative-safe-integer',\n },\n );\n }\n\n return {\n maxChars: brandMaxChars(maxChars),\n };\n}\n\n/** Result of {@link createLineBuffer.feed}. */\nexport interface LineBufferFeedResult {\n lines: string[];\n /** `true` when leading characters were discarded due to `maxChars`. */\n overflowed: boolean;\n}\n\n/**\n * Handle returned by {@link createLineBuffer}.\n *\n * @internal\n */\nexport interface LineBuffer {\n feed(chunk: string): LineBufferFeedResult;\n clear(): void;\n}\n\n/** @internal Resolved limits for {@link createLineBuffer}. */\nexport interface LineBufferLimits {\n maxChars: MaxChars;\n}\n\n/**\n * Streaming UTF-16 text to newline-delimited lines for {@link createSerialSession}.\n * Supports `\\r\\n` and `\\n` per #237; a lone `\\r` that is not the last character\n * in the buffer is treated as a line end (compatibility with some devices). A\n * trailing `\\r` is retained until a following chunk disambiguates `\\r` vs\n * `\\r\\n`.\n *\n * @internal\n */\nexport function createLineBuffer(\n options?: LineBufferOptions | LineBufferLimits,\n): LineBuffer {\n const limits: LineBufferLimits = resolveLineBufferOptions(options);\n\n const tokenizer = createNewlineTokenizer('line');\n\n const clear = (): void => {\n tokenizer.clear();\n };\n\n const feed = (chunk: string): LineBufferFeedResult => {\n const events = tokenizer.feed(chunk);\n const out: string[] = [];\n\n for (const event of events) {\n if (event.type === 'line') {\n out.push(event.content);\n }\n }\n\n const overflowed = tokenizer.trimPending(limits.maxChars);\n\n return { lines: out, overflowed };\n };\n\n return { feed, clear };\n}\n", "import { SerialErrorCode } from '../../errors/serial-error-code';\n\n/**\n * Internal error classification used by the single `reportError` entry\n * point. `'fatal'` errors drive `state$` into `'error'` and tear down\n * the live session (pump stop + port close); `'non-fatal'` errors are\n * only multiplexed on `errors$` without mutating session state.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/399 | Issue #399}\n */\nexport type ReportErrorSeverity = 'fatal' | 'non-fatal';\n\n/**\n * Maps each {@link SerialErrorCode} that can surface through session\n * `reportError` to its severity. Unmapped codes fall back to `'fatal'`\n * in {@link resolveErrorSeverity}.\n *\n * @internal\n */\nexport const ERROR_SEVERITY = {\n [SerialErrorCode.READ_FAILED]: 'fatal',\n [SerialErrorCode.CONNECTION_LOST]: 'fatal',\n [SerialErrorCode.PORT_OPEN_FAILED]: 'fatal',\n [SerialErrorCode.OPERATION_CANCELLED]: 'fatal',\n [SerialErrorCode.UNKNOWN]: 'fatal',\n [SerialErrorCode.LINE_BUFFER_OVERFLOW]: 'non-fatal',\n [SerialErrorCode.BROWSER_NOT_SUPPORTED]: 'non-fatal',\n [SerialErrorCode.PORT_ALREADY_OPEN]: 'non-fatal',\n [SerialErrorCode.PORT_NOT_OPEN]: 'non-fatal',\n [SerialErrorCode.WRITE_FAILED]: 'non-fatal',\n} as const satisfies Partial<Record<SerialErrorCode, ReportErrorSeverity>>;\n\n/**\n * Resolve the reporting severity for a normalised {@link SerialErrorCode}.\n *\n * @internal\n */\nexport function resolveErrorSeverity(\n code: SerialErrorCode,\n): ReportErrorSeverity {\n if (code in ERROR_SEVERITY) {\n return ERROR_SEVERITY[code as keyof typeof ERROR_SEVERITY];\n }\n return 'fatal';\n}\n", "import { SerialError } from '../errors/serial-error';\nimport { SerialErrorCode } from '../errors/serial-error-code';\n\n/**\n * Default human-readable prefix attached to normalized errors when the\n * caller does not supply one. Kept short so downstream messages remain\n * readable (e.g. `\"Serial operation failed: <cause>\"`).\n *\n * @internal\n */\nconst DEFAULT_MESSAGE_PREFIX = 'Serial operation failed';\n\n/**\n * Options accepted by {@link normalizeSerialError}.\n *\n * @internal\n */\nexport interface NormalizeSerialErrorOptions {\n /**\n * Error code assigned when the input cannot be classified more\n * specifically (for example a generic `Error` thrown from `port.open`).\n *\n * Most call sites in the session pipeline know which lifecycle phase\n * they are in (connect / read / write / close) and therefore pick a\n * phase-specific fallback such as {@link SerialErrorCode.PORT_OPEN_FAILED}\n * or {@link SerialErrorCode.WRITE_FAILED}.\n */\n fallbackCode: SerialErrorCode;\n /**\n * Prefix used when the input has to be wrapped. Ignored when the input\n * is already a {@link SerialError} so we never rewrite a well-formed\n * message the caller has already chosen.\n */\n messagePrefix?: string;\n}\n\nconst isDomExceptionWithName = (\n error: unknown,\n name: string,\n): error is DOMException =>\n typeof DOMException !== 'undefined' &&\n error instanceof DOMException &&\n error.name === name;\n\n/**\n * Normalize an arbitrary thrown value into a {@link SerialError}.\n *\n * This helper is the single entry point used by every v2 session\n * component (session factory, read pump, send queue) to coerce raw\n * platform errors into the library's error type. Centralising the\n * mapping here satisfies Issue #204's completion criterion that\n * \"error handling lives in one place\" and removes the duplicated\n * `toError` / `normalizeError` helpers previously scattered across\n * session modules.\n *\n * Mapping rules applied, in order:\n *\n * 1. `SerialError` instances pass through unchanged so a caller that has\n * already classified the failure (e.g. `PORT_NOT_OPEN` on a pre-open\n * `send$`) is not rewrapped.\n * 2. `DOMException('NotFoundError')` is mapped to\n * {@link SerialErrorCode.OPERATION_CANCELLED}. Chromium raises this\n * when the user dismisses the `requestPort` dialog; it is a normal\n * control-flow event, not a hard failure.\n * 3. Any other value is wrapped as a {@link SerialError} with\n * {@link NormalizeSerialErrorOptions.fallbackCode}, preserving the\n * underlying failure on {@link SerialError.context | context.cause}.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/204 | Issue #204}\n */\nexport function normalizeSerialError(\n error: unknown,\n options: NormalizeSerialErrorOptions,\n): SerialError {\n if (error instanceof SerialError) {\n return error;\n }\n\n const prefix = options.messagePrefix ?? DEFAULT_MESSAGE_PREFIX;\n\n if (isDomExceptionWithName(error, 'NotFoundError')) {\n return new SerialError(\n SerialErrorCode.OPERATION_CANCELLED,\n 'Port selection was cancelled by the user',\n undefined,\n { cause: error },\n );\n }\n\n const cause = error instanceof Error ? error : new Error(String(error));\n return new SerialError(\n options.fallbackCode,\n `${prefix}: ${cause.message}`,\n undefined,\n { cause },\n );\n}\n", "import { BehaviorSubject, Observable } from 'rxjs';\nimport { assertNever } from '../internal/assert-never';\nimport type { SerialError } from '../errors/serial-error';\nimport type { ReadPump } from './read-pump';\nimport {\n SerialSessionStatus,\n type SerialSessionState,\n type SerialSessionStatus as SerialSessionStatusType,\n} from './serial-session-state';\n\n/**\n * Discriminated union representing the full internal runtime state of a\n * {@link SerialSession}, including lifecycle status and any resources that\n * are valid for that status.\n *\n * This is the single source of truth for session lifecycle + resources.\n * Public consumers observe the mapped {@link SerialSessionState} via\n * `state$`; this union is internal only.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/397 | Issue #397}\n */\nexport type SessionRuntime =\n | IdleRuntime\n | ConnectingRuntime\n | ConnectedRuntime\n | DisconnectingRuntime\n | ErrorRuntime\n | UnsupportedRuntime\n | DisposedRuntime;\n\n/** @internal */\nexport interface IdleRuntime {\n readonly status: typeof SerialSessionStatus.Idle;\n}\n\n/** @internal */\nexport interface ConnectingRuntime {\n readonly status: typeof SerialSessionStatus.Connecting;\n readonly cancel: () => void;\n}\n\n/** @internal */\nexport interface ConnectedRuntime {\n readonly status: typeof SerialSessionStatus.Connected;\n readonly port: SerialPort;\n readonly pump: ReadPump;\n}\n\n/** @internal */\nexport interface DisconnectingRuntime {\n readonly status: typeof SerialSessionStatus.Disconnecting;\n readonly port: SerialPort | null;\n}\n\n/** @internal */\nexport interface ErrorRuntime {\n readonly status: typeof SerialSessionStatus.Error;\n readonly error: SerialError;\n}\n\n/** @internal */\nexport interface UnsupportedRuntime {\n readonly status: typeof SerialSessionStatus.Unsupported;\n}\n\n/** @internal */\nexport interface DisposedRuntime {\n readonly status: typeof SerialSessionStatus.Disposed;\n}\n\nconst S = SerialSessionStatus;\n\n/**\n * Allowed transitions for the internal SerialSession state machine.\n *\n * `as const satisfies` keeps literal transition targets while ensuring every\n * {@link SerialSessionStatus} key is present. Drift from the status union\n * becomes a compile error.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/398 | Issue #398}\n */\nexport const ALLOWED_TRANSITIONS = {\n [S.Idle]: [S.Connecting, S.Error, S.Disposed],\n [S.Connecting]: [S.Connected, S.Error, S.Idle, S.Disposed],\n [S.Connected]: [S.Disconnecting, S.Error, S.Disposed],\n [S.Disconnecting]: [S.Idle, S.Error, S.Disposed],\n [S.Error]: [S.Idle, S.Connecting, S.Disposed],\n [S.Unsupported]: [S.Disposed],\n [S.Disposed]: [],\n} as const satisfies Readonly<\n Record<SerialSessionStatusType, readonly SerialSessionStatusType[]>\n>;\n\n/**\n * Valid target states when transitioning from {@link T}.\n *\n * @internal\n */\nexport type AllowedTransition<T extends SerialSessionStatusType> =\n (typeof ALLOWED_TRANSITIONS)[T][number];\n\n/** @internal */\nexport function runtimeToSessionStatus(\n runtime: SessionRuntime,\n): SerialSessionStatusType {\n return runtime.status;\n}\n\n/**\n * Map internal runtime to the public {@link SerialSessionState} payload.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/406 | Issue #406}\n */\nexport function runtimeToPublicState(runtime: SessionRuntime): SerialSessionState {\n switch (runtime.status) {\n case S.Idle:\n return { status: S.Idle };\n case S.Connecting:\n return { status: S.Connecting };\n case S.Connected:\n return { status: S.Connected, portInfo: runtime.port.getInfo() };\n case S.Disconnecting:\n return { status: S.Disconnecting };\n case S.Unsupported:\n return { status: S.Unsupported };\n case S.Error:\n return { status: S.Error, error: runtime.error };\n case S.Disposed:\n return { status: S.Disposed };\n default:\n return assertNeverRuntime(runtime);\n }\n}\n\n/** @internal */\nexport function isValidTransition(\n from: SerialSessionStatusType,\n to: SerialSessionStatusType,\n): boolean {\n if (from === to) {\n return false;\n }\n return (ALLOWED_TRANSITIONS[from] as readonly SerialSessionStatusType[]).includes(\n to,\n );\n}\n\n/** @internal */\nexport function createIdleRuntime(): IdleRuntime {\n return { status: S.Idle };\n}\n\n/** @internal */\nexport function createConnectingRuntime(cancel: () => void): ConnectingRuntime {\n return { status: S.Connecting, cancel };\n}\n\n/** @internal */\nexport function createConnectedRuntime(\n port: SerialPort,\n pump: ReadPump,\n): ConnectedRuntime {\n return { status: S.Connected, port, pump };\n}\n\n/** @internal */\nexport function createDisconnectingRuntime(\n port: SerialPort | null,\n): DisconnectingRuntime {\n return { status: S.Disconnecting, port };\n}\n\n/** @internal */\nexport function createErrorRuntime(error: SerialError): ErrorRuntime {\n return { status: S.Error, error };\n}\n\n/** @internal */\nexport function createUnsupportedRuntime(): UnsupportedRuntime {\n return { status: S.Unsupported };\n}\n\n/** @internal */\nexport function createDisposedRuntime(): DisposedRuntime {\n return { status: S.Disposed };\n}\n\n/** @internal */\nexport function createInitialRuntime(supported: boolean): SessionRuntime {\n return supported ? createIdleRuntime() : createUnsupportedRuntime();\n}\n\n/**\n * Extract the active port from runtime when one is held.\n *\n * @internal\n */\nexport function getRuntimePort(runtime: SessionRuntime): SerialPort | null {\n switch (runtime.status) {\n case S.Connected:\n return runtime.port;\n case S.Disconnecting:\n return runtime.port;\n default:\n return null;\n }\n}\n\n/**\n * Extract the active pump from runtime when one is held.\n *\n * @internal\n */\nexport function getRuntimePump(runtime: SessionRuntime): ReadPump | null {\n if (runtime.status === S.Connected) {\n return runtime.pump;\n }\n return null;\n}\n\n/**\n * Controller that owns the {@link SessionRuntime} discriminated union and\n * exposes the public `state$` stream derived from the runtime.\n *\n * @internal\n */\nexport interface SessionRuntimeController {\n get runtime(): SessionRuntime;\n get status(): SerialSessionStatusType;\n get state$(): Observable<SerialSessionState>;\n transition(next: SessionRuntime): boolean;\n complete(): void;\n}\n\n/**\n * Create a controller for the internal session runtime.\n *\n * @internal\n */\nexport function createSessionRuntimeController(\n initial: SessionRuntime,\n): SessionRuntimeController {\n let runtime = initial;\n const subject = new BehaviorSubject<SerialSessionState>(\n runtimeToPublicState(initial),\n );\n\n const transition = (next: SessionRuntime): boolean => {\n const from = runtimeToSessionStatus(runtime);\n const to = runtimeToSessionStatus(next);\n\n if (from === to) {\n return false;\n }\n\n if (!isValidTransition(from, to)) {\n if (typeof console !== 'undefined' && console.warn) {\n console.warn(\n `[web-serial-rxjs] Ignoring invalid SerialSession transition ${from} -> ${to}`,\n );\n }\n return false;\n }\n\n // Project before mutating: `runtimeToPublicState` reads `port.getInfo()`,\n // so a throwing projection must not leave the runtime ahead of `state$`.\n const publicState = runtimeToPublicState(next);\n runtime = next;\n subject.next(publicState);\n return true;\n };\n\n return {\n get runtime() {\n return runtime;\n },\n get status() {\n return runtimeToSessionStatus(runtime);\n },\n get state$() {\n return subject.asObservable();\n },\n transition,\n complete() {\n subject.complete();\n },\n };\n}\n\n/**\n * Exhaustiveness helper for switch statements over {@link SessionRuntime}.\n *\n * @internal\n */\nexport function assertNeverRuntime(value: never): never {\n return assertNever(value);\n}\n", "import type { SerialError } from '../errors/serial-error';\n\n/**\n * Canonical status literals for a {@link SerialSession} lifecycle.\n *\n * Use these constants when comparing {@link SerialSessionState.status}\n * so call sites avoid string typos and get IDE completion.\n *\n * Lifecycle transitions:\n *\n * ```\n * idle -> connecting -> connected -> disconnecting -> idle\n * \\-> error\n * (any) -> unsupported (when Web Serial API is unavailable)\n * (any) -> error (when an unrecoverable failure occurs)\n * (any) -> disposed (when dispose$ completes)\n * ```\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/406 | Issue #406}\n */\nexport const SerialSessionStatus = {\n Idle: 'idle',\n Connecting: 'connecting',\n Connected: 'connected',\n Disconnecting: 'disconnecting',\n Unsupported: 'unsupported',\n Error: 'error',\n Disposed: 'disposed',\n} as const;\n\n/**\n * String union of allowed {@link SerialSessionStatus} runtime values\n * (same set as the values on the {@link SerialSessionStatus} object).\n */\nexport type SerialSessionStatus =\n (typeof SerialSessionStatus)[keyof typeof SerialSessionStatus];\n\n/** @see {@link SerialSessionState} */\nexport interface IdleSessionState {\n readonly status: typeof SerialSessionStatus.Idle;\n}\n\n/** @see {@link SerialSessionState} */\nexport interface ConnectingSessionState {\n readonly status: typeof SerialSessionStatus.Connecting;\n}\n\n/** Connected lifecycle variant with narrowed {@link SerialPortInfo} access via `portInfo`. */\nexport interface ConnectedSessionState {\n readonly status: typeof SerialSessionStatus.Connected;\n readonly portInfo: SerialPortInfo;\n}\n\n/** @see {@link SerialSessionState} */\nexport interface DisconnectingSessionState {\n readonly status: typeof SerialSessionStatus.Disconnecting;\n}\n\n/** @see {@link SerialSessionState} */\nexport interface UnsupportedSessionState {\n readonly status: typeof SerialSessionStatus.Unsupported;\n}\n\n/** @see {@link SerialSessionState} */\nexport interface ErrorSessionState {\n readonly status: typeof SerialSessionStatus.Error;\n readonly error: SerialError;\n}\n\n/** @see {@link SerialSessionState} */\nexport interface DisposedSessionState {\n readonly status: typeof SerialSessionStatus.Disposed;\n}\n\n/**\n * Discriminated union emitted by {@link SerialSession.state$} \u2014 the\n * canonical lifecycle source.\n *\n * Each variant carries the lifecycle `status` plus optional detail fields\n * (`portInfo` when connected, `error` when in a fatal error state).\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/406 | Issue #406}\n */\nexport type SerialSessionState =\n | IdleSessionState\n | ConnectingSessionState\n | ConnectedSessionState\n | DisconnectingSessionState\n | UnsupportedSessionState\n | ErrorSessionState\n | DisposedSessionState;\n", "import type { Subject } from 'rxjs';\nimport { SerialError } from '../../errors/serial-error';\nimport { SerialErrorCode } from '../../errors/serial-error-code';\nimport { resolveErrorSeverity } from './error-severity';\nimport {\n normalizeSerialError,\n type NormalizeSerialErrorOptions,\n} from '../normalize-serial-error';\nimport type { ReadPump } from '../read-pump';\nimport type { SendQueue } from '../send-queue';\nimport {\n createErrorRuntime,\n getRuntimePort,\n getRuntimePump,\n type SessionRuntimeController,\n} from '../session-runtime';\n\n/**\n * Dependencies for {@link createSessionErrorReporter}.\n *\n * @internal\n */\nexport interface SessionErrorReporterDeps {\n controller: SessionRuntimeController;\n errorsSubject: Subject<SerialError>;\n sendQueue: SendQueue;\n isDisposed: () => boolean;\n teardownPump: (pump: ReadPump | null) => Promise<void>;\n closePortSafely: (port: SerialPort | null) => Promise<void>;\n}\n\n/**\n * Centralised error reporting for {@link createSerialSession}.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/204 | Issue #204}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/401 | Issue #401}\n */\nexport function createSessionErrorReporter(deps: SessionErrorReporterDeps): {\n reportError: (\n error: unknown,\n options: NormalizeSerialErrorOptions,\n ) => SerialError;\n createDisposedError: () => SerialError;\n} {\n const {\n controller,\n errorsSubject,\n sendQueue,\n isDisposed,\n teardownPump,\n closePortSafely,\n } = deps;\n\n const createDisposedError = (): SerialError =>\n new SerialError(\n SerialErrorCode.SESSION_DISPOSED,\n 'SerialSession has been disposed',\n );\n\n /**\n * Single entry point for every error that should reach `errors$`.\n *\n * Responsibilities:\n *\n * 1. Normalise the input through {@link normalizeSerialError} so every\n * emission is a well-formed {@link SerialError}.\n * 2. Multiplex the normalised error on `errors$`.\n * 3. Resolve severity from the normalised {@link SerialError.code} via\n * {@link resolveErrorSeverity} (see `ERROR_SEVERITY` in\n * `error-severity.ts`).\n * 4. For fatal severities, drive `state$` to `'error'`, clear the send\n * queue so pending writes fail fast, and tear down the live pump +\n * port off the hot path.\n *\n * Returning the normalised error keeps call sites terse: they can hand\n * the result straight to `subscriber.error(...)` without re-normalising.\n */\n const reportError = (\n error: unknown,\n options: NormalizeSerialErrorOptions,\n ): SerialError => {\n const serialError = normalizeSerialError(error, options);\n if (isDisposed()) {\n return serialError;\n }\n errorsSubject.next(serialError);\n if (resolveErrorSeverity(serialError.code) === 'fatal') {\n const runtime = controller.runtime;\n const portToClose = getRuntimePort(runtime);\n const pump = getRuntimePump(runtime);\n controller.transition(createErrorRuntime(serialError));\n sendQueue.clear();\n void teardownPump(pump).then(() => closePortSafely(portToClose));\n }\n return serialError;\n };\n\n return { reportError, createDisposedError };\n}\n", "import { Observable, type Subject } from 'rxjs';\nimport { SerialError } from '../../errors/serial-error';\nimport { SerialErrorCode } from '../../errors/serial-error-code';\nimport { buildRequestOptions } from './build-request-options';\nimport { isWebSerialSupported } from './has-web-serial-support';\nimport type { ReceivePipeline } from './receive-pipeline';\nimport {\n normalizeSerialError,\n type NormalizeSerialErrorOptions,\n} from '../normalize-serial-error';\nimport { createReadPump, type ReadPump } from '../read-pump';\nimport type { SendQueue } from '../send-queue';\nimport type { ResolvedSerialSessionOptions } from '../serial-session-options';\nimport { SerialSessionStatus } from '../serial-session-state';\nimport {\n createConnectedRuntime,\n createConnectingRuntime,\n createDisconnectingRuntime,\n createDisposedRuntime,\n createIdleRuntime,\n getRuntimePort,\n getRuntimePump,\n type SessionRuntime,\n type SessionRuntimeController,\n} from '../session-runtime';\n\n/**\n * Dependencies for {@link createSessionLifecycle}.\n *\n * @internal\n */\nexport interface SessionLifecycleDeps {\n controller: SessionRuntimeController;\n resolvedOptions: ResolvedSerialSessionOptions;\n sendQueue: SendQueue;\n receivePipeline: ReceivePipeline;\n errorsSubject: Subject<SerialError>;\n isDisposed: () => boolean;\n teardownPump: (pump: ReadPump | null) => Promise<void>;\n closePortSafely: (port: SerialPort | null) => Promise<void>;\n reportError: (\n error: unknown,\n options: NormalizeSerialErrorOptions,\n ) => SerialError;\n createDisposedError: () => SerialError;\n}\n\n/**\n * Port and pump lifecycle operations for {@link createSerialSession}.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/401 | Issue #401}\n */\nexport interface SessionLifecycle {\n connect$: () => Observable<void>;\n disconnect$: () => Observable<void>;\n dispose$: () => Observable<void>;\n writeToPort: (payload: Uint8Array) => Promise<void>;\n}\n\n/**\n * @internal\n */\nexport function createSessionLifecycle(\n deps: SessionLifecycleDeps,\n): SessionLifecycle {\n const {\n controller,\n resolvedOptions,\n sendQueue,\n receivePipeline,\n errorsSubject,\n isDisposed,\n teardownPump,\n closePortSafely,\n reportError,\n createDisposedError,\n } = deps;\n\n const teardownFromSnapshot = async (\n snapshot: SessionRuntime,\n ): Promise<void> => {\n if (snapshot.status === SerialSessionStatus.Connecting) {\n snapshot.cancel();\n }\n\n sendQueue.clear();\n\n if (\n snapshot.status === SerialSessionStatus.Connected ||\n snapshot.status === SerialSessionStatus.Disconnecting ||\n snapshot.status === SerialSessionStatus.Error\n ) {\n const portToClose = getRuntimePort(snapshot);\n const pump = getRuntimePump(snapshot);\n await teardownPump(pump);\n await closePortSafely(portToClose);\n }\n\n receivePipeline.clearLineBuffer();\n };\n\n const completeSubjects = (): void => {\n controller.complete();\n errorsSubject.complete();\n receivePipeline.complete();\n };\n\n const dispose$ = (): Observable<void> =>\n new Observable<void>((subscriber) => {\n if (isDisposed()) {\n subscriber.next();\n subscriber.complete();\n return;\n }\n\n const snapshot = controller.runtime;\n controller.transition(createDisposedRuntime());\n\n const run = async (): Promise<void> => {\n try {\n await teardownFromSnapshot(snapshot);\n completeSubjects();\n subscriber.next();\n subscriber.complete();\n } catch (error) {\n const serialError = normalizeSerialError(error, {\n fallbackCode: SerialErrorCode.UNKNOWN,\n messagePrefix: 'Unexpected dispose failure',\n });\n subscriber.error(serialError);\n }\n };\n\n void run();\n });\n\n const writeToPort = async (payload: Uint8Array): Promise<void> => {\n const runtime = controller.runtime;\n if (\n runtime.status !== SerialSessionStatus.Connected ||\n !runtime.port.writable\n ) {\n throw new SerialError(\n SerialErrorCode.PORT_NOT_OPEN,\n 'Cannot send data while session is not connected',\n );\n }\n const writer = runtime.port.writable.getWriter();\n try {\n await writer.write(payload);\n } finally {\n try {\n writer.releaseLock();\n } catch {\n // releaseLock throws when the stream is already errored; the real\n // failure is surfaced through the write() rejection above so we\n // intentionally swallow this secondary error.\n }\n }\n };\n\n const connect$ = (): Observable<void> =>\n new Observable<void>((subscriber) => {\n if (isDisposed()) {\n subscriber.error(createDisposedError());\n return;\n }\n\n if (!isWebSerialSupported()) {\n const error = reportError(\n new SerialError(\n SerialErrorCode.BROWSER_NOT_SUPPORTED,\n 'Web Serial API is not supported in this environment',\n ),\n { fallbackCode: SerialErrorCode.BROWSER_NOT_SUPPORTED },\n );\n subscriber.error(error);\n return;\n }\n\n const current = controller.status;\n if (\n current !== SerialSessionStatus.Idle &&\n current !== SerialSessionStatus.Error\n ) {\n const error = reportError(\n new SerialError(\n SerialErrorCode.PORT_ALREADY_OPEN,\n `Cannot connect while session state is '${current}'`,\n ),\n { fallbackCode: SerialErrorCode.PORT_ALREADY_OPEN },\n );\n subscriber.error(error);\n return;\n }\n\n let cancelled = false;\n const cancelInFlightConnect = (): void => {\n cancelled = true;\n if (controller.status === SerialSessionStatus.Connecting) {\n controller.transition(createIdleRuntime());\n }\n };\n controller.transition(createConnectingRuntime(cancelInFlightConnect));\n\n const run = async (): Promise<void> => {\n let selectedPort: SerialPort | null = null;\n try {\n selectedPort = await navigator.serial.requestPort(\n buildRequestOptions(resolvedOptions),\n );\n await selectedPort.open({\n baudRate: resolvedOptions.baudRate,\n dataBits: resolvedOptions.dataBits,\n stopBits: resolvedOptions.stopBits,\n parity: resolvedOptions.parity,\n bufferSize: resolvedOptions.bufferSize,\n flowControl: resolvedOptions.flowControl,\n } satisfies SerialOptions);\n } catch (error) {\n if (selectedPort) {\n await closePortSafely(selectedPort);\n }\n const serialError = reportError(error, {\n fallbackCode: SerialErrorCode.PORT_OPEN_FAILED,\n messagePrefix: 'Failed to open port',\n });\n if (!cancelled) {\n subscriber.error(serialError);\n }\n return;\n }\n\n if (cancelled || isDisposed()) {\n await closePortSafely(selectedPort);\n return;\n }\n\n receivePipeline.clearLineBuffer();\n const pump = createReadPump(selectedPort, {\n onChunk: receivePipeline.handleChunk,\n onError: (pumpError) =>\n reportError(pumpError, {\n fallbackCode: SerialErrorCode.READ_FAILED,\n messagePrefix: 'Read pump failed',\n }),\n onDone: () => {\n if (controller.status !== SerialSessionStatus.Connected) {\n return;\n }\n reportError(\n new SerialError(\n SerialErrorCode.CONNECTION_LOST,\n 'Read pump ended unexpectedly: stream closed while connected',\n ),\n {\n fallbackCode: SerialErrorCode.CONNECTION_LOST,\n },\n );\n },\n });\n pump.start();\n sendQueue.clear();\n if (isDisposed()) {\n await teardownPump(pump);\n await closePortSafely(selectedPort);\n return;\n }\n controller.transition(createConnectedRuntime(selectedPort, pump));\n subscriber.next();\n subscriber.complete();\n };\n\n void run();\n\n return () => {\n cancelInFlightConnect();\n };\n });\n\n const disconnect$ = (): Observable<void> =>\n new Observable<void>((subscriber) => {\n if (isDisposed()) {\n subscriber.next();\n subscriber.complete();\n return;\n }\n\n const runtime = controller.runtime;\n\n if (\n runtime.status === SerialSessionStatus.Idle ||\n runtime.status === SerialSessionStatus.Unsupported ||\n runtime.status === SerialSessionStatus.Disconnecting\n ) {\n subscriber.next();\n subscriber.complete();\n return;\n }\n\n if (runtime.status === SerialSessionStatus.Connecting) {\n runtime.cancel();\n subscriber.next();\n subscriber.complete();\n return;\n }\n\n if (\n runtime.status !== SerialSessionStatus.Connected &&\n runtime.status !== SerialSessionStatus.Error\n ) {\n const error = reportError(\n new SerialError(\n SerialErrorCode.PORT_NOT_OPEN,\n `Cannot disconnect while session state is '${runtime.status}'`,\n ),\n { fallbackCode: SerialErrorCode.PORT_NOT_OPEN },\n );\n subscriber.error(error);\n return;\n }\n\n const portToClose = getRuntimePort(runtime);\n controller.transition(createDisconnectingRuntime(portToClose));\n sendQueue.clear();\n\n const run = async (): Promise<void> => {\n try {\n const pump = getRuntimePump(controller.runtime);\n await teardownPump(pump);\n if (portToClose) {\n try {\n await portToClose.close();\n } catch (error) {\n const serialError = reportError(error, {\n fallbackCode: SerialErrorCode.CONNECTION_LOST,\n messagePrefix: 'Failed to close port',\n });\n subscriber.error(serialError);\n return;\n }\n }\n if (!isDisposed()) {\n controller.transition(createIdleRuntime());\n }\n subscriber.next();\n subscriber.complete();\n } catch (error) {\n const serialError = reportError(error, {\n fallbackCode: SerialErrorCode.UNKNOWN,\n messagePrefix: 'Unexpected disconnect failure',\n });\n subscriber.error(serialError);\n }\n };\n\n void run();\n });\n\n return {\n connect$,\n disconnect$,\n dispose$,\n writeToPort,\n };\n}\n", "import type { SerialSessionOptions } from '../serial-session-options';\n\n/**\n * Build {@link SerialPortRequestOptions} from resolved {@link SerialSessionOptions}.\n *\n * Converts the `filters` field into the shape expected by\n * `navigator.serial.requestPort`. Returns `undefined` when no filters are\n * supplied so the browser shows all available ports.\n *\n * Filter validation is performed earlier by\n * {@link resolveSerialSessionOptions} at factory time.\n *\n * @param options - Resolved session options (filters already validated).\n * @returns The request options, or `undefined` when no filters are set.\n *\n * @internal\n */\nexport function buildRequestOptions(\n options?: SerialSessionOptions,\n): SerialPortRequestOptions | undefined {\n if (!options?.filters || options.filters.length === 0) {\n return undefined;\n }\n\n return {\n filters: options.filters,\n };\n}\n", "import { SerialError } from '../errors/serial-error';\nimport { SerialErrorCode } from '../errors/serial-error-code';\nimport { normalizeSerialError } from './normalize-serial-error';\n\n/**\n * Callback invoked for every decoded chunk the read pump produces.\n *\n * The text is decoded from the raw bytes using a shared `TextDecoder` with\n * `{ stream: true }`, so multi-byte characters that straddle chunk\n * boundaries are joined correctly before reaching the callback.\n *\n * @internal\n */\nexport type ReadPumpChunkHandler = (text: string) => void;\n\n/**\n * Callback invoked when the read pump cannot continue.\n *\n * The provided error is always a {@link SerialError}; raw platform errors\n * are normalized by the pump before they reach the caller so consumers only\n * ever observe the library's error type.\n *\n * @internal\n */\nexport type ReadPumpErrorHandler = (error: SerialError) => void;\n\n/**\n * Callback invoked when the read loop ends naturally (`reader.read()`\n * resolved with `done: true`) while the pump is still active.\n *\n * @internal\n */\nexport type ReadPumpDoneHandler = () => void;\n\n/**\n * Options accepted by {@link createReadPump}.\n *\n * @internal\n */\nexport interface ReadPumpOptions {\n onChunk: ReadPumpChunkHandler;\n onError: ReadPumpErrorHandler;\n onDone?: ReadPumpDoneHandler;\n}\n\n/**\n * Handle returned by {@link createReadPump}.\n *\n * @internal\n */\nexport interface ReadPump {\n /**\n * Start reading from the associated `SerialPort.readable` stream.\n *\n * Subsequent calls are ignored while the pump is already running.\n */\n start(): void;\n /**\n * Stop the read loop and release the underlying reader lock.\n *\n * Safe to call multiple times or before `start()`.\n */\n stop(): Promise<void>;\n /**\n * `true` while the internal loop is actively reading.\n */\n readonly isRunning: boolean;\n}\n\n/**\n * Create an internal read pump for a {@link SerialPort}.\n *\n * The pump is an implementation detail of the v2 `SerialSession` API: it\n * owns a single `TextDecoder` (with `stream: true`), drives a\n * `reader.read()` loop against `port.readable`, and forwards decoded text\n * to the provided sink. It is **not** subscription-lazy - the `SerialSession`\n * starts the pump as soon as `connect$` succeeds so that `receive$`\n * subscribers never miss data because of subscription timing.\n *\n * Errors are normalized into {@link SerialError} with\n * {@link SerialErrorCode.READ_FAILED} for read-side failures and\n * {@link SerialErrorCode.CONNECTION_LOST} for missing readable streams, so\n * the session can forward them to `errors$` without rewrapping.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/202 | Issue #202}\n */\nexport function createReadPump(\n port: SerialPort,\n { onChunk, onError, onDone }: ReadPumpOptions,\n): ReadPump {\n let reader: ReadableStreamDefaultReader<Uint8Array> | null = null;\n let running = false;\n let stopped = false;\n const decoder = new TextDecoder(undefined, { fatal: false });\n\n const releaseReader = (): void => {\n if (!reader) {\n return;\n }\n try {\n reader.releaseLock();\n } catch {\n // releaseLock may throw when the reader is already detached; ignore.\n }\n reader = null;\n };\n\n const pump = async (stream: ReadableStream<Uint8Array>): Promise<void> => {\n reader = stream.getReader();\n running = true;\n try {\n while (!stopped) {\n const { done, value } = await reader.read();\n if (done) {\n if (!stopped) {\n onDone?.();\n }\n break;\n }\n if (value && value.byteLength > 0) {\n const text = decoder.decode(value, { stream: true });\n if (text.length > 0) {\n onChunk(text);\n }\n }\n }\n if (!stopped) {\n const tail = decoder.decode();\n if (tail.length > 0) {\n onChunk(tail);\n }\n }\n } catch (error) {\n if (!stopped) {\n onError(\n normalizeSerialError(error, {\n fallbackCode: SerialErrorCode.READ_FAILED,\n messagePrefix: 'Read pump failed',\n }),\n );\n }\n } finally {\n running = false;\n releaseReader();\n }\n };\n\n return {\n start(): void {\n if (running || stopped) {\n return;\n }\n const stream = port.readable;\n if (!stream) {\n stopped = true;\n onError(\n new SerialError(\n SerialErrorCode.CONNECTION_LOST,\n 'Read pump failed: port.readable is not available',\n ),\n );\n return;\n }\n void pump(stream);\n },\n async stop(): Promise<void> {\n if (stopped) {\n return;\n }\n stopped = true;\n if (!reader) {\n return;\n }\n try {\n await reader.cancel();\n } catch {\n // Cancel can reject when the stream is already errored; ignore so\n // the caller's disconnect flow is not derailed by a read failure.\n } finally {\n releaseReader();\n }\n },\n get isRunning(): boolean {\n return running;\n },\n };\n}\n", "import { Observable, defer } from 'rxjs';\n\n/**\n * A single enqueued unit of work. Operations are awaited sequentially so\n * that `send$` guarantees call order even when the caller fires multiple\n * observables concurrently.\n *\n * @internal\n */\nexport type SendQueueOperation<T> = () => Promise<T>;\n\n/**\n * Internal helper returned by {@link createSendQueue}.\n *\n * The queue is intentionally not an RxJS operator because Issue #199 calls\n * out explicitly that `send$` must not be re-implemented with `mergeMap`\n * (or any other concurrency-altering operator). A chained `Promise<void>`\n * is the simplest way to guarantee strict FIFO ordering while still\n * surfacing per-operation completion back to the caller.\n *\n * @internal\n */\nexport interface SendQueue {\n /**\n * Schedule an operation to run after all previously enqueued operations\n * have settled (resolved or rejected). The returned Observable completes\n * with the operation's resolved value, or errors with its rejection.\n *\n * Subscribing is what actually schedules the work - the queue is driven\n * by `defer`, so late/never-subscribed Observables never enqueue.\n */\n enqueue<T>(operation: SendQueueOperation<T>): Observable<T>;\n /**\n * Reset the internal promise chain. Existing in-flight operations keep\n * running because we do not cancel native promises, but no newly\n * enqueued work will wait for them. Use this when the session is torn\n * down so a fresh `connect$` starts with a clean chain.\n */\n clear(): void;\n}\n\n/**\n * Create an internal send queue that serialises `send$` writes.\n *\n * Ordering model:\n *\n * - Each `enqueue` call appends its operation to a single `Promise<void>`\n * chain.\n * - A failure in one operation does not poison the chain - the next\n * operation still runs (the chain is advanced with a `.then(() => {},\n * () => {})` safety tail).\n * - Unsubscribing before the operation resolves suppresses `next` /\n * `complete` / `error` for that subscriber; the underlying promise is\n * still awaited so later operations continue to respect call order.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/203 | Issue #203}\n */\nexport function createSendQueue(): SendQueue {\n let chain: Promise<void> = Promise.resolve();\n\n return {\n enqueue<T>(operation: SendQueueOperation<T>): Observable<T> {\n return defer(\n () =>\n new Observable<T>((subscriber) => {\n let cancelled = false;\n\n const run = async (): Promise<void> => {\n try {\n const value = await operation();\n if (!cancelled) {\n subscriber.next(value);\n subscriber.complete();\n }\n } catch (error) {\n if (!cancelled) {\n subscriber.error(error);\n }\n }\n };\n\n const scheduled = chain.then(run, run);\n chain = scheduled.then(\n () => undefined,\n () => undefined,\n );\n\n return () => {\n cancelled = true;\n };\n }),\n );\n },\n clear(): void {\n chain = Promise.resolve();\n },\n };\n}\n", "import { SerialError } from '../../errors/serial-error';\nimport { SerialErrorCode } from '../../errors/serial-error-code';\n\n/**\n * Validate {@link SerialPortFilter} entries for session creation.\n *\n * @param filters - Optional filters from {@link SerialSessionOptions}.\n * @returns The same filters reference when valid, or `undefined` when omitted/empty.\n * @throws {@link SerialError} with {@link SerialErrorCode.INVALID_FILTER_OPTIONS}\n * when a filter is empty or contains out-of-range IDs.\n *\n * @internal\n */\nexport function validateSerialPortFilters(\n filters?: SerialPortFilter[],\n): SerialPortFilter[] | undefined {\n if (!filters || filters.length === 0) {\n return filters;\n }\n\n for (let filterIndex = 0; filterIndex < filters.length; filterIndex++) {\n const filter = filters[filterIndex];\n if (filter === undefined) {\n continue;\n }\n if (!filter.usbVendorId && !filter.usbProductId) {\n throw new SerialError(\n SerialErrorCode.INVALID_FILTER_OPTIONS,\n 'Filter must have at least usbVendorId or usbProductId',\n undefined,\n {\n field: 'filters',\n value: filter,\n constraint: 'at-least-one-usb-id',\n filterIndex,\n },\n );\n }\n\n if (filter.usbVendorId !== undefined) {\n if (\n !Number.isInteger(filter.usbVendorId) ||\n filter.usbVendorId < 0 ||\n filter.usbVendorId > 0xffff\n ) {\n throw new SerialError(\n SerialErrorCode.INVALID_FILTER_OPTIONS,\n `Invalid usbVendorId: ${filter.usbVendorId}. Must be an integer between 0 and 65535.`,\n undefined,\n {\n field: 'usbVendorId',\n value: filter.usbVendorId,\n constraint: 'usb-id-0-65535',\n filterIndex,\n },\n );\n }\n }\n\n if (filter.usbProductId !== undefined) {\n if (\n !Number.isInteger(filter.usbProductId) ||\n filter.usbProductId < 0 ||\n filter.usbProductId > 0xffff\n ) {\n throw new SerialError(\n SerialErrorCode.INVALID_FILTER_OPTIONS,\n `Invalid usbProductId: ${filter.usbProductId}. Must be an integer between 0 and 65535.`,\n undefined,\n {\n field: 'usbProductId',\n value: filter.usbProductId,\n constraint: 'usb-id-0-65535',\n filterIndex,\n },\n );\n }\n }\n }\n\n return filters;\n}\n", "import {\n brandBaudRate,\n brandSerialPortBufferSize,\n type BaudRate,\n type SerialPortBufferSize,\n} from '../internal/branded-numbers';\nimport type { SerialConnectionOptions } from '../types';\nimport {\n resolveTerminalBufferOptions,\n type ResolvedTerminalBufferOptions,\n type TerminalBufferOptions,\n} from '../terminal/create-terminal-buffer';\nimport { SerialError } from '../errors/serial-error';\nimport { SerialErrorCode } from '../errors/serial-error-code';\nimport {\n resolveLineBufferOptions,\n type LineBufferOptions,\n type ResolvedLineBufferOptions,\n} from './internal/line-buffer';\nimport { validateSerialPortFilters } from './internal/validate-serial-port-filters';\n\nexport type {\n ResolvedLineBufferOptions,\n ResolvedTerminalBufferOptions,\n};\n\n/**\n * Library-specific options for {@link createSerialSession} that are not passed\n * to W3C `port.open`.\n *\n * Responsibility split (Issue #488):\n *\n * - {@link SerialSessionFeatureOptions.filters} \u2014 port selection only\n * (`navigator.serial.requestPort`)\n * - {@link SerialSessionFeatureOptions.lineBuffer} \u2014 incomplete-line tail for\n * {@link SerialSession.lines$}\n * - {@link SerialSessionFeatureOptions.terminalBuffer} \u2014 display memory for\n * {@link SerialSession.terminalText$}\n *\n * Connection parameters (`baudRate`, `dataBits`, \u2026) live on\n * {@link SerialConnectionOptions} and are composed into\n * {@link SerialSessionOptions}. Minimal callers typically only set\n * `baudRate`; other fields keep safe defaults.\n *\n * @see {@link SerialConnectionOptions} for W3C connection parameters\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/441 | Issue #441}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/488 | Issue #488}\n */\nexport interface SerialSessionFeatureOptions {\n /**\n * Filters for port selection when requesting a port.\n *\n * When specified, the port selection dialog will only show devices\n * matching these filters. Each filter can specify `usbVendorId` and/or\n * `usbProductId`. Not passed to `port.open`.\n */\n filters?: SerialPortFilter[];\n\n /**\n * Limits for {@link SerialSession.terminalText$} display memory. Oldest\n * completed lines and leading characters are dropped when exceeded.\n * Character counts use UTF-16 string length (JavaScript `.length`).\n *\n * Pass `0` for `maxLines` or `maxChars` to disable that limit (unlimited).\n * Negative values, non-integers, `NaN`, and `Infinity` are rejected at\n * factory time.\n *\n * @default `{ maxLines: 10000, maxChars: 1048576, stripAnsi: true }`\n * (see {@link DEFAULT_SERIAL_SESSION_OPTIONS})\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/370 | Issue #370}\n */\n terminalBuffer?: TerminalBufferOptions;\n\n /**\n * Limits for the incomplete line tail held by {@link SerialSession.lines$}\n * framing. When exceeded, leading characters are discarded and a non-fatal\n * {@link SerialErrorCode.LINE_BUFFER_OVERFLOW} is emitted on\n * {@link SerialSession.errors$}. Character counts use UTF-16 string length.\n *\n * Pass `0` for `maxChars` to disable the limit (unlimited). Negative values,\n * non-integers, `NaN`, and `Infinity` are rejected at factory time.\n *\n * @default `{ maxChars: 1048576 }` (see {@link DEFAULT_SERIAL_SESSION_OPTIONS})\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/371 | Issue #371}\n */\n lineBuffer?: LineBufferOptions;\n}\n\n/**\n * Options for creating a {@link SerialSession} via {@link createSerialSession}.\n *\n * Composes {@link Partial}<{@link SerialConnectionOptions}> (W3C connection\n * parameters passed to `port.open`) with {@link SerialSessionFeatureOptions}\n * (library-specific session features). All fields are optional; omitted values\n * fall back to {@link DEFAULT_SERIAL_SESSION_OPTIONS}.\n *\n * Minimal usage typically only needs `baudRate`:\n *\n * @example\n * ```typescript\n * const session = createSerialSession({ baudRate: 115200 });\n * ```\n *\n * Full example:\n *\n * @example\n * ```typescript\n * const session = createSerialSession({\n * baudRate: 115200,\n * dataBits: 8,\n * stopBits: 1,\n * parity: 'none',\n * flowControl: 'none',\n * filters: [{ usbVendorId: 0x1234, usbProductId: 0x5678 }],\n * });\n * ```\n *\n * Boundary semantics for numeric limits:\n *\n * - `undefined` \u2014 apply the default from {@link DEFAULT_SERIAL_SESSION_OPTIONS}\n * - `baudRate` / `bufferSize` \u2014 must be safe integers `> 0` (rejected otherwise)\n * - `terminalBuffer` / `lineBuffer` limits \u2014 safe integers `>= 0`; `0` means unlimited\n *\n * @see {@link SerialConnectionOptions}\n * @see {@link SerialSessionFeatureOptions}\n * @see {@link SerialOptions}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/200 | Issue #200}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/402 | Issue #402}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/441 | Issue #441}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/488 | Issue #488}\n */\nexport interface SerialSessionOptions\n extends Partial<SerialConnectionOptions>, SerialSessionFeatureOptions {}\n\n/**\n * Fully resolved session options after merging {@link SerialSessionOptions}\n * with {@link DEFAULT_SERIAL_SESSION_OPTIONS}. All invariant fields are\n * required; `filters` remains optional.\n */\nexport type ResolvedSerialSessionOptions = Required<\n Omit<\n SerialSessionOptions,\n | 'filters'\n | 'terminalBuffer'\n | 'lineBuffer'\n | 'baudRate'\n | 'bufferSize'\n >\n> & {\n baudRate: BaudRate;\n bufferSize: SerialPortBufferSize;\n filters?: SerialPortFilter[];\n terminalBuffer: ResolvedTerminalBufferOptions;\n lineBuffer: ResolvedLineBufferOptions;\n};\n\n/**\n * Default values applied to omitted {@link SerialSessionOptions} fields.\n *\n * Nested buffer defaults are owned by {@link resolveTerminalBufferOptions} and\n * {@link resolveLineBufferOptions}; this object is the single session-level\n * snapshot used by {@link resolveSerialSessionOptions}.\n *\n * @internal\n */\nexport const DEFAULT_SERIAL_SESSION_OPTIONS = {\n baudRate: brandBaudRate(9600),\n dataBits: 8,\n stopBits: 1,\n parity: 'none',\n bufferSize: brandSerialPortBufferSize(255),\n flowControl: 'none',\n terminalBuffer: resolveTerminalBufferOptions(),\n lineBuffer: resolveLineBufferOptions(),\n} satisfies ResolvedSerialSessionOptions;\n\n/** Resolved W3C connection fields for {@link ResolvedSerialSessionOptions}. */\nexport type ResolvedSerialSessionConnectionOptions = Required<\n Omit<SerialConnectionOptions, 'baudRate' | 'bufferSize'>\n> & {\n baudRate: BaudRate;\n bufferSize: SerialPortBufferSize;\n};\n\n/**\n * Merge and validate W3C connection fields from {@link SerialSessionOptions}.\n *\n * @throws {@link SerialError} with {@link SerialErrorCode.INVALID_CONNECTION_OPTIONS}\n * when `baudRate` or `bufferSize` are out of range.\n */\nexport function resolveConnectionOptions(\n options?: Partial<SerialConnectionOptions>,\n): ResolvedSerialSessionConnectionOptions {\n const merged = {\n baudRate: DEFAULT_SERIAL_SESSION_OPTIONS.baudRate,\n dataBits: DEFAULT_SERIAL_SESSION_OPTIONS.dataBits,\n stopBits: DEFAULT_SERIAL_SESSION_OPTIONS.stopBits,\n parity: DEFAULT_SERIAL_SESSION_OPTIONS.parity,\n bufferSize: DEFAULT_SERIAL_SESSION_OPTIONS.bufferSize,\n flowControl: DEFAULT_SERIAL_SESSION_OPTIONS.flowControl,\n ...options,\n };\n\n const baudRate =\n merged.baudRate ?? DEFAULT_SERIAL_SESSION_OPTIONS.baudRate;\n const bufferSize =\n merged.bufferSize ?? DEFAULT_SERIAL_SESSION_OPTIONS.bufferSize;\n\n if (!Number.isSafeInteger(baudRate) || baudRate <= 0) {\n throw new SerialError(\n SerialErrorCode.INVALID_CONNECTION_OPTIONS,\n `Invalid baudRate: ${baudRate}. Must be a safe integer > 0.`,\n undefined,\n {\n field: 'baudRate',\n value: baudRate,\n constraint: 'positive-safe-integer',\n },\n );\n }\n\n if (!Number.isSafeInteger(bufferSize) || bufferSize <= 0) {\n throw new SerialError(\n SerialErrorCode.INVALID_CONNECTION_OPTIONS,\n `Invalid bufferSize: ${bufferSize}. Must be a safe integer > 0.`,\n undefined,\n {\n field: 'bufferSize',\n value: bufferSize,\n constraint: 'positive-safe-integer',\n },\n );\n }\n\n return {\n dataBits: merged.dataBits ?? DEFAULT_SERIAL_SESSION_OPTIONS.dataBits,\n stopBits: merged.stopBits ?? DEFAULT_SERIAL_SESSION_OPTIONS.stopBits,\n parity: merged.parity ?? DEFAULT_SERIAL_SESSION_OPTIONS.parity,\n flowControl:\n merged.flowControl ?? DEFAULT_SERIAL_SESSION_OPTIONS.flowControl,\n baudRate: brandBaudRate(baudRate),\n bufferSize: brandSerialPortBufferSize(bufferSize),\n };\n}\n\n/**\n * Merge and validate {@link SerialSessionOptions} into a fully resolved\n * options object for internal session use.\n *\n * Defaults and validation for nested buffers live next to their option types\n * ({@link resolveTerminalBufferOptions}, {@link resolveLineBufferOptions}).\n * This function is the single entry point that assembles connection + feature\n * options for a session.\n *\n * @throws {@link SerialError} when option values are out of range:\n * {@link SerialErrorCode.INVALID_CONNECTION_OPTIONS},\n * {@link SerialErrorCode.INVALID_FILTER_OPTIONS},\n * {@link SerialErrorCode.INVALID_TERMINAL_BUFFER_OPTIONS}, or\n * {@link SerialErrorCode.INVALID_LINE_BUFFER_OPTIONS}.\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/403 | Issue #403}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/488 | Issue #488}\n */\nexport function resolveSerialSessionOptions(\n options?: SerialSessionOptions,\n): ResolvedSerialSessionOptions {\n const connection = resolveConnectionOptions(options);\n const filters = validateSerialPortFilters(options?.filters);\n\n return {\n ...connection,\n ...(filters !== undefined ? { filters } : {}),\n terminalBuffer: resolveTerminalBufferOptions(options?.terminalBuffer),\n lineBuffer: resolveLineBufferOptions(options?.lineBuffer),\n };\n}\n", "import {\n type ConnectedSessionState,\n type SerialSessionState,\n SerialSessionStatus,\n} from './serial-session-state';\n\n/**\n * Type predicate for {@link ConnectedSessionState}.\n *\n * Use with RxJS `filter()` to preserve discriminated union narrowing in\n * pipelines so `portInfo` and other connected-only fields stay typed.\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/436 | Issue #436}\n */\nexport function isConnectedSessionState(\n state: SerialSessionState,\n): state is ConnectedSessionState {\n return state.status === SerialSessionStatus.Connected;\n}\n"],
4
+ "sourcesContent": ["/**\n * Exhaustiveness helper for internal switch statements.\n *\n * @deprecated Not part of the canonical public API. Define a local helper in\n * application code, or use `switch (state.status)` with\n * {@link SerialSessionStatus}. Still available in v4; will be removed from\n * public exports in a future major (v5+).\n * @internal\n */\nexport function assertNever(value: never): never {\n throw new Error(`Unexpected value: ${String(value)}`);\n}\n", "import { Observable, Subject } from 'rxjs';\nimport { SerialError } from '../errors/serial-error';\nimport { SerialErrorCode } from '../errors/serial-error-code';\nimport { createTerminalBuffer } from '../terminal/create-terminal-buffer';\nimport type { SerialPayload } from '../types';\nimport { isWebSerialSupported } from './internal/has-web-serial-support';\nimport { createPortTeardown } from './internal/port-teardown';\nimport { createReceivePipeline } from './internal/receive-pipeline';\nimport { createSessionErrorReporter } from './internal/session-error-reporter';\nimport { createSessionLifecycle } from './internal/session-lifecycle';\nimport { createSendQueue } from './send-queue';\nimport type { SerialSession } from './serial-session';\nimport {\n resolveSerialSessionOptions,\n type SerialSessionOptions,\n} from './serial-session-options';\nimport { SerialSessionStatus } from './serial-session-state';\nimport {\n createInitialRuntime,\n createSessionRuntimeController,\n} from './session-runtime';\n\n/**\n * Create a {@link SerialSession}.\n *\n * This release wires the internal read pump (#202) and the internal send\n * queue (#203) into the session so that `connect$`, `disconnect$`,\n * `receive$`, and `send$` all operate end-to-end. Error handling is\n * centralised through a single `reportError` helper (#204) so every\n * failure path normalises through {@link normalizeSerialError} and emits\n * on the one `errors$` channel.\n *\n * Key behaviors:\n *\n * - `state$` replays the current lifecycle state driven by the internal\n * {@link SessionRuntime} controller.\n * - `connect$()` opens a user-selected port, starts the internal read pump,\n * and transitions `idle -> connecting -> connected`.\n * - `disconnect$()` stops the read pump, closes the port, and transitions\n * `connected -> disconnecting -> idle`. When called during `connecting`,\n * it cancels the in-flight `connect$()` and returns to `idle`.\n * - `receive$` emits UTF-8 decoded text chunks pushed by the pump. It is\n * **not** subscription-lazy - the pump is started by `connect$` and\n * decoded text is multicast to all subscribers; late subscribers see only\n * new data.\n * - `lines$` emits the same decoded stream split into line-terminated\n * segments (`\\n`, `\\r\\n`); a trailing line without a terminator is\n * buffered. It is also not subscription-lazy relative to the pump.\n * - `send$` enqueues each payload on an internal FIFO queue so concurrent\n * subscribers are written to the port in call order. String payloads are\n * UTF-8 encoded through a shared `TextEncoder`.\n * - `errors$` multiplexes every {@link SerialError} produced by the\n * session. Connect / read / close failures are treated as fatal and\n * also drive `state$` to `'error'`; write failures are non-fatal and\n * do not mutate `state$` because a real connection loss will be\n * observed by the read pump on the next tick anyway.\n *\n * @param options - Session options. W3C connection fields (`baudRate`,\n * `dataBits`, \u2026) are passed to `port.open`. `filters` is forwarded to\n * `navigator.serial.requestPort`. `lineBuffer` / `terminalBuffer` configure\n * {@link SerialSession.lines$} and {@link SerialSession.terminalText$}.\n * Omitted fields use {@link resolveSerialSessionOptions} defaults; minimal\n * callers typically only set `baudRate`.\n * @returns A {@link SerialSession} instance.\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/202 | Issue #202}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/203 | Issue #203}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/204 | Issue #204}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/397 | Issue #397}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/399 | Issue #399}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/401 | Issue #401}\n */\nexport function createSerialSession(\n options?: SerialSessionOptions,\n): SerialSession {\n const resolvedOptions = resolveSerialSessionOptions(options);\n\n const controller = createSessionRuntimeController(\n createInitialRuntime(isWebSerialSupported()),\n );\n const errorsSubject = new Subject<SerialError>();\n const sendQueue = createSendQueue();\n const textEncoder = new TextEncoder();\n\n const isDisposed = (): boolean =>\n controller.status === SerialSessionStatus.Disposed;\n\n const receivePipeline = createReceivePipeline({ resolvedOptions });\n\n const { teardownPump, closePortSafely } = createPortTeardown({\n receivePipeline,\n });\n\n const { reportError, createDisposedError } = createSessionErrorReporter({\n controller,\n errorsSubject,\n sendQueue,\n isDisposed,\n teardownPump,\n closePortSafely,\n });\n\n // Route receive-side buffer overflows through the single reportError entry\n // point. The subscription completes when the pipeline completes on dispose.\n receivePipeline.bufferErrors$.subscribe((error) =>\n reportError(error, { fallbackCode: error.code }),\n );\n\n const lifecycle = createSessionLifecycle({\n controller,\n resolvedOptions,\n sendQueue,\n receivePipeline,\n errorsSubject,\n isDisposed,\n teardownPump,\n closePortSafely,\n reportError,\n createDisposedError,\n });\n\n const { receive$, lines$ } = receivePipeline;\n const errors$ = errorsSubject.asObservable();\n const terminalText$ = createTerminalBuffer(\n receive$,\n resolvedOptions.terminalBuffer,\n ).text$;\n\n return {\n connect$: lifecycle.connect$,\n disconnect$: lifecycle.disconnect$,\n dispose$: lifecycle.dispose$,\n send$(data: SerialPayload): Observable<void> {\n if (isDisposed()) {\n return new Observable<void>((subscriber) => {\n subscriber.error(createDisposedError());\n });\n }\n\n return sendQueue.enqueue(async () => {\n const payload =\n typeof data === 'string' ? textEncoder.encode(data) : data;\n try {\n await lifecycle.writeToPort(payload);\n } catch (error) {\n throw reportError(error, {\n fallbackCode: SerialErrorCode.WRITE_FAILED,\n messagePrefix: 'Failed to write data',\n });\n }\n });\n },\n state$: controller.state$,\n errors$,\n receive$,\n terminalText$,\n lines$,\n } satisfies SerialSession;\n}\n", "/**\n * Error codes for serial port operations.\n *\n * These codes identify specific error conditions that can occur when working with\n * serial ports. Each error code corresponds to a specific failure scenario, making\n * it easier to handle errors programmatically.\n *\n * @example\n * ```typescript\n * try {\n * await client.connect().toPromise();\n * } catch (error) {\n * if (error instanceof SerialError) {\n * switch (error.code) {\n * case SerialErrorCode.BROWSER_NOT_SUPPORTED:\n * console.error('Please use a supported desktop browser (Chrome, Edge, Opera, or Firefox 151+)');\n * break;\n * case SerialErrorCode.OPERATION_CANCELLED:\n * console.log('User cancelled port selection');\n * break;\n * // ... handle other error codes\n * }\n * }\n * }\n * ```\n */\nexport const SerialErrorCode = {\n /**\n * Browser does not support the Web Serial API.\n *\n * Emitted on `connect$` when `navigator.serial` is unavailable.\n *\n * **Suggested action**: Inform the user to use a supported browser.\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/438 | Issue #438}\n */\n BROWSER_NOT_SUPPORTED: 'BROWSER_NOT_SUPPORTED',\n\n /**\n * Serial port is not available.\n *\n * **Reserved \u2014 not emitted in v4.** The current implementation uses only\n * `navigator.serial.requestPort`; there is no `getPorts` API path. Still\n * present in v4 (deprecated); scheduled for removal in a future major (v5+).\n *\n * **Suggested action**: Handle port acquisition failures with\n * {@link SerialErrorCode.PORT_OPEN_FAILED} or\n * {@link SerialErrorCode.OPERATION_CANCELLED} instead.\n *\n * @deprecated Not emitted at runtime in v4. Will be removed in a future major (v5+).\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/438 | Issue #438}\n */\n PORT_NOT_AVAILABLE: 'PORT_NOT_AVAILABLE',\n\n /**\n * Failed to open the serial port.\n *\n * Emitted on `connect$` when `port.open()` rejects.\n *\n * **Suggested action**: Verify connection parameters and check hardware connections.\n */\n PORT_OPEN_FAILED: 'PORT_OPEN_FAILED',\n\n /**\n * Serial port is already open.\n *\n * Emitted on `connect$` when the session is not in `'idle'` or `'error'`\n * (non-fatal; session state is unchanged).\n *\n * **Suggested action**: Disconnect the current port before connecting a new one.\n */\n PORT_ALREADY_OPEN: 'PORT_ALREADY_OPEN',\n\n /**\n * Serial port is not open.\n *\n * Emitted when `send$` or `disconnect$` is called in an invalid session\n * state (for example before `connect$` completes).\n *\n * **Suggested action**: Call {@link SerialSession.connect$} before sending data.\n */\n PORT_NOT_OPEN: 'PORT_NOT_OPEN',\n\n /**\n * Failed to read from the serial port.\n *\n * This error occurs when reading data from the port fails, typically due to\n * connection loss, hardware issues, or stream errors.\n *\n * **Suggested action**: Check the connection and hardware, then retry the read operation.\n */\n READ_FAILED: 'READ_FAILED',\n\n /**\n * Failed to write to the serial port.\n *\n * This error occurs when writing data to the port fails, typically due to\n * connection loss, hardware issues, or stream errors.\n *\n * **Suggested action**: Check the connection and hardware, then retry the write operation.\n */\n WRITE_FAILED: 'WRITE_FAILED',\n\n /**\n * Serial port connection was lost.\n *\n * This error occurs when the connection to the serial port is unexpectedly\n * terminated, such as when the device is disconnected or the port is closed\n * by another process.\n *\n * **Suggested action**: Check the physical connection and reconnect if needed.\n */\n CONNECTION_LOST: 'CONNECTION_LOST',\n\n /**\n * Invalid filter options provided.\n *\n * This error occurs when port filter options are invalid, such as when\n * filter values are out of range or missing required fields.\n *\n * **Suggested action**: Verify filter options match the expected format and value ranges.\n */\n INVALID_FILTER_OPTIONS: 'INVALID_FILTER_OPTIONS',\n\n /**\n * Operation was cancelled by the user.\n *\n * This error occurs when the user cancels a port selection dialog or aborts\n * an operation before it completes.\n *\n * **Suggested action**: This is a normal condition - no action required, but you may want\n * to inform the user that the operation was cancelled.\n */\n OPERATION_CANCELLED: 'OPERATION_CANCELLED',\n\n /**\n * Operation timed out before completion.\n *\n * **Reserved \u2014 not emitted in v4.** No timeout / prompt detection /\n * transaction API exists yet. Still present in v4 (deprecated); scheduled for\n * removal in a future major (v5+) unless a future API adopts this code.\n *\n * @deprecated Not emitted at runtime in v4. Will be removed in a future major (v5+).\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/438 | Issue #438}\n */\n OPERATION_TIMEOUT: 'OPERATION_TIMEOUT',\n\n /**\n * Internal line buffer exceeded its configured size limit.\n *\n * This error occurs when the incomplete line tail held by the {@link SerialSession.lines$}\n * framing buffer grows beyond {@link SerialSessionOptions.lineBuffer} `maxChars`.\n * Leading characters are discarded to bound memory; the session remains connected.\n *\n * **Suggested action**: Increase `lineBuffer.maxChars`, handle framing on `receive$`,\n * or ensure the device sends line terminators.\n */\n LINE_BUFFER_OVERFLOW: 'LINE_BUFFER_OVERFLOW',\n\n /**\n * Invalid terminal buffer options provided.\n *\n * This error occurs when {@link SerialSessionOptions.terminalBuffer} values are\n * out of range, such as a negative or non-integer `maxLines` or `maxChars`.\n *\n * **Suggested action**: Verify `terminalBuffer` options match the documented\n * ranges and value types.\n */\n INVALID_TERMINAL_BUFFER_OPTIONS: 'INVALID_TERMINAL_BUFFER_OPTIONS',\n\n /**\n * Invalid line buffer options provided.\n *\n * This error occurs when {@link SerialSessionOptions.lineBuffer} values are\n * out of range, such as a negative or non-integer `maxChars`.\n *\n * **Suggested action**: Verify `lineBuffer` options match the documented\n * ranges and value types.\n */\n INVALID_LINE_BUFFER_OPTIONS: 'INVALID_LINE_BUFFER_OPTIONS',\n\n /**\n * Invalid connection options provided.\n *\n * This error occurs when connection fields such as `baudRate` are out of\n * range at session creation time.\n *\n * **Suggested action**: Verify connection options match the documented\n * ranges and value types.\n */\n INVALID_CONNECTION_OPTIONS: 'INVALID_CONNECTION_OPTIONS',\n\n /**\n * Session has been disposed and can no longer be used.\n *\n * This error occurs when calling {@link SerialSession.connect$} or\n * {@link SerialSession.send$} after {@link SerialSession.dispose$} has\n * completed. Create a new session instead of reusing a disposed instance.\n *\n * **Suggested action**: Call {@link SerialSession.dispose$} only when\n * permanently tearing down a session, then create a new one if needed.\n */\n SESSION_DISPOSED: 'SESSION_DISPOSED',\n\n /**\n * Unknown error occurred.\n *\n * Emitted as a fallback when dispose or disconnect encounters an error that\n * cannot be classified more specifically. The underlying failure is on\n * {@link SerialError.context | context.cause}.\n *\n * **Suggested action**: Check the error message and `context.cause` for more details.\n */\n UNKNOWN: 'UNKNOWN',\n} as const;\n\n/**\n * String union of allowed {@link SerialErrorCode} runtime values\n * (same set as the values on the {@link SerialErrorCode} object).\n */\nexport type SerialErrorCode =\n (typeof SerialErrorCode)[keyof typeof SerialErrorCode];\n", "import { type Observable, map, scan, shareReplay } from 'rxjs';\nimport { SerialError } from '../errors/serial-error';\nimport { SerialErrorCode } from '../errors/serial-error-code';\nimport {\n brandMaxChars,\n brandMaxLines,\n type MaxChars,\n type MaxLines,\n} from '../internal/branded-numbers';\nimport { createNewlineTokenizer } from '../internal/newline-tokenizer';\nimport { createAnsiStripper } from '../internal/strip-ansi-sequences';\n\n/** @internal Folded state between {@link createTerminalBuffer} emissions. */\nexport interface TerminalBufferState {\n completed: string;\n currentLine: string;\n}\n\n/**\n * Applies one raw decoder chunk to terminal display state.\n * Handles `\\r\\n` and lone `\\n` as line endings, and lone `\\r` as\n * carriage return (clear current line for redraw). When\n * {@link TerminalBufferOptions.stripAnsi} is enabled (default), ANSI escape\n * sequences are removed before line folding.\n *\n * @internal Exported for unit tests.\n */\nexport function applyTerminalChunk(\n state: TerminalBufferState,\n chunk: string,\n): TerminalBufferState {\n const tokenizer = createNewlineTokenizer('terminal');\n tokenizer.restorePending(state.currentLine);\n const events = tokenizer.feed(chunk);\n\n let { completed } = state;\n for (const event of events) {\n if (event.type === 'line') {\n completed += event.content + '\\n';\n }\n }\n\n return {\n completed,\n currentLine: tokenizer.getPendingText(),\n };\n}\n\n/** @internal */\nexport function terminalDisplayText(state: TerminalBufferState): string {\n return state.completed + state.currentLine;\n}\n\n/** Resolved limits for {@link trimTerminalState}. `0` means unlimited. */\nexport interface TerminalBufferLimits {\n maxLines: MaxLines;\n maxChars: MaxChars;\n}\n\n/** @internal Count newline-terminated rows in `completed`. */\nexport function countCompletedLines(completed: string): number {\n if (completed.length === 0) {\n return 0;\n }\n let count = 0;\n for (let i = 0; i < completed.length; i++) {\n if (completed.charAt(i) === '\\n') {\n count++;\n }\n }\n return count;\n}\n\n/**\n * Drops oldest completed lines when `maxLines` is exceeded.\n *\n * @internal Exported for unit tests.\n */\nexport function trimCompletedByMaxLines(\n completed: string,\n maxLines: number,\n): string {\n if (maxLines <= 0) {\n return completed;\n }\n\n let trimmed = completed;\n while (countCompletedLines(trimmed) > maxLines) {\n const firstNewline = trimmed.indexOf('\\n');\n if (firstNewline < 0) {\n break;\n }\n trimmed = trimmed.slice(firstNewline + 1);\n }\n return trimmed;\n}\n\n/**\n * Trims {@link TerminalBufferState} to respect memory limits. Oldest\n * `completed` content is removed first; `currentLine` is trimmed only when\n * the display text still exceeds `maxChars` after `completed` is empty.\n *\n * @internal Exported for unit tests.\n */\nexport function trimTerminalState(\n state: TerminalBufferState,\n limits: TerminalBufferLimits,\n): TerminalBufferState {\n let { completed, currentLine } = state;\n\n if (limits.maxLines > 0) {\n completed = trimCompletedByMaxLines(completed, limits.maxLines);\n }\n\n if (limits.maxChars > 0) {\n let total = completed.length + currentLine.length;\n while (total > limits.maxChars) {\n const excess = total - limits.maxChars;\n if (completed.length >= excess) {\n completed = completed.slice(excess);\n break;\n }\n const removeFromCurrent = excess - completed.length;\n completed = '';\n currentLine = currentLine.slice(removeFromCurrent);\n total = completed.length + currentLine.length;\n }\n }\n\n return { completed, currentLine };\n}\n\nexport interface TerminalBuffer {\n /**\n * Cumulative text suitable for terminal-style display: completed lines plus\n * the current line, with `\\r` redraws collapsed per Issue #275.\n */\n readonly text$: Observable<string>;\n}\n\n/**\n * Options for {@link createTerminalBuffer} memory limits.\n *\n * Character counts use UTF-16 string length (JavaScript `.length`).\n * Pass `0` for `maxLines` or `maxChars` to disable that limit (unlimited).\n */\nexport interface TerminalBufferOptions {\n /**\n * Maximum number of completed lines to retain in the display buffer.\n * `0` means unlimited. Must be a safe integer `>= 0` when validated.\n *\n * @default 10000\n */\n maxLines?: number;\n\n /**\n * Maximum total characters in the cumulative display text\n * (`completed` + `currentLine`). Oldest content is dropped first.\n * `0` means unlimited. Must be a safe integer `>= 0` when validated.\n *\n * @default 1048576\n */\n maxChars?: number;\n\n /**\n * When `true`, strips ANSI escape sequences from incoming chunks before\n * folding carriage-return redraws. Use `false` to preserve raw escape\n * codes in {@link TerminalBuffer.text$}.\n *\n * @default true\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/428 | Issue #428}\n */\n stripAnsi?: boolean;\n}\n\n/** Default limits applied when options are omitted. */\nexport const DEFAULT_TERMINAL_BUFFER_OPTIONS: Required<TerminalBufferOptions> =\n {\n maxLines: 10_000,\n maxChars: 1_048_576,\n stripAnsi: true,\n };\n\n/** Resolved terminal buffer options with validated branded numeric fields. */\nexport type ResolvedTerminalBufferOptions = {\n maxLines: MaxLines;\n maxChars: MaxChars;\n stripAnsi: boolean;\n};\n\n/**\n * Merge and validate {@link TerminalBufferOptions}.\n *\n * This is the single normalization path for terminal buffer defaults and\n * boundary checks (session factory and standalone {@link createTerminalBuffer}).\n *\n * @throws {@link SerialError} with {@link SerialErrorCode.INVALID_TERMINAL_BUFFER_OPTIONS}\n * when `maxLines` or `maxChars` are out of range.\n */\nexport function resolveTerminalBufferOptions(\n options?: TerminalBufferOptions,\n): ResolvedTerminalBufferOptions {\n const merged: Required<TerminalBufferOptions> = {\n ...DEFAULT_TERMINAL_BUFFER_OPTIONS,\n ...options,\n };\n\n const { maxLines, maxChars } = merged;\n\n if (!Number.isSafeInteger(maxLines) || maxLines < 0) {\n throw new SerialError(\n SerialErrorCode.INVALID_TERMINAL_BUFFER_OPTIONS,\n `Invalid terminalBuffer.maxLines: ${maxLines}. Must be a safe integer >= 0.`,\n undefined,\n {\n field: 'terminalBuffer.maxLines',\n value: maxLines,\n constraint: 'non-negative-safe-integer',\n },\n );\n }\n\n if (!Number.isSafeInteger(maxChars) || maxChars < 0) {\n throw new SerialError(\n SerialErrorCode.INVALID_TERMINAL_BUFFER_OPTIONS,\n `Invalid terminalBuffer.maxChars: ${maxChars}. Must be a safe integer >= 0.`,\n undefined,\n {\n field: 'terminalBuffer.maxChars',\n value: maxChars,\n constraint: 'non-negative-safe-integer',\n },\n );\n }\n\n return {\n maxLines: brandMaxLines(maxLines),\n maxChars: brandMaxChars(maxChars),\n stripAnsi: merged.stripAnsi,\n };\n}\n\nconst initialTerminalState: TerminalBufferState = {\n completed: '',\n currentLine: '',\n};\n\n/**\n * Builds a terminal-oriented text stream from {@link SerialSession.receive$} (or any\n * `Observable<string>` of decoded chunks). Uses internal buffering so callers need not\n * implement carriage-return collapse themselves.\n *\n * By default, retains at most {@link DEFAULT_TERMINAL_BUFFER_OPTIONS.maxLines}\n * completed lines and {@link DEFAULT_TERMINAL_BUFFER_OPTIONS.maxChars} characters\n * so long-running sessions do not grow memory without bound. Pass `0` for either\n * limit to disable that constraint.\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/370 | Issue #370}\n */\nexport function createTerminalBuffer(\n receive$: Observable<string>,\n options?: TerminalBufferOptions,\n): TerminalBuffer {\n const resolved = resolveTerminalBufferOptions(options);\n const limits: TerminalBufferLimits = {\n maxLines: resolved.maxLines,\n maxChars: resolved.maxChars,\n };\n const ansiStripper = resolved.stripAnsi ? createAnsiStripper() : null;\n\n const text$ = receive$.pipe(\n scan((state, chunk: string) => {\n const normalized =\n ansiStripper !== null ? ansiStripper.feed(chunk) : chunk;\n const next = applyTerminalChunk(state, normalized);\n return trimTerminalState(next, limits);\n }, initialTerminalState),\n map(terminalDisplayText),\n shareReplay({ bufferSize: 1, refCount: true }),\n );\n\n return { text$ };\n}\n", "import { SerialErrorCode } from './serial-error-code';\n\n/**\n * Context payload for errors that wrap an underlying failure.\n */\nexport type SerialErrorCauseContext = {\n readonly cause: unknown;\n};\n\n/**\n * Machine-readable constraint identifiers for factory-time validation errors.\n */\nexport type ValidationErrorConstraint =\n | 'at-least-one-usb-id'\n | 'usb-id-0-65535'\n | 'positive-safe-integer'\n | 'non-negative-safe-integer';\n\n/**\n * Structured metadata for `INVALID_*` validation errors thrown at session\n * factory time. {@link SerialError.message} remains human-readable.\n */\nexport type ValidationErrorContext = {\n readonly field: string;\n readonly value: unknown;\n readonly constraint: ValidationErrorConstraint;\n readonly filterIndex?: number;\n};\n\n/**\n * Maps each {@link SerialErrorCode} to its structured context shape.\n *\n * Codes mapped to `undefined` have no machine-readable metadata beyond\n * {@link SerialError.message}. Overflow and validation codes expose structured\n * metadata so callers do not need to parse error messages.\n */\nexport interface SerialErrorContextMap {\n [SerialErrorCode.BROWSER_NOT_SUPPORTED]: undefined;\n [SerialErrorCode.PORT_NOT_AVAILABLE]: SerialErrorCauseContext;\n [SerialErrorCode.PORT_OPEN_FAILED]: SerialErrorCauseContext;\n [SerialErrorCode.PORT_ALREADY_OPEN]: undefined;\n [SerialErrorCode.PORT_NOT_OPEN]: undefined;\n [SerialErrorCode.READ_FAILED]: SerialErrorCauseContext;\n [SerialErrorCode.WRITE_FAILED]: SerialErrorCauseContext;\n [SerialErrorCode.CONNECTION_LOST]: SerialErrorCauseContext;\n [SerialErrorCode.INVALID_FILTER_OPTIONS]: ValidationErrorContext;\n [SerialErrorCode.OPERATION_CANCELLED]: SerialErrorCauseContext;\n [SerialErrorCode.OPERATION_TIMEOUT]: undefined;\n [SerialErrorCode.LINE_BUFFER_OVERFLOW]: {\n readonly maxChars: number;\n };\n [SerialErrorCode.INVALID_TERMINAL_BUFFER_OPTIONS]: ValidationErrorContext;\n [SerialErrorCode.INVALID_LINE_BUFFER_OPTIONS]: ValidationErrorContext;\n [SerialErrorCode.INVALID_CONNECTION_OPTIONS]: ValidationErrorContext;\n [SerialErrorCode.SESSION_DISPOSED]: undefined;\n [SerialErrorCode.UNKNOWN]: SerialErrorCauseContext;\n}\n\nconst CAUSE_CONTEXT_CODES = new Set<SerialErrorCode>([\n SerialErrorCode.PORT_NOT_AVAILABLE,\n SerialErrorCode.PORT_OPEN_FAILED,\n SerialErrorCode.READ_FAILED,\n SerialErrorCode.WRITE_FAILED,\n SerialErrorCode.CONNECTION_LOST,\n SerialErrorCode.OPERATION_CANCELLED,\n SerialErrorCode.UNKNOWN,\n]);\n\n/**\n * @internal\n */\nexport function isCauseContextCode(\n code: SerialErrorCode,\n): code is keyof {\n [K in keyof SerialErrorContextMap as SerialErrorContextMap[K] extends SerialErrorCauseContext\n ? K\n : never]: SerialErrorContextMap[K];\n} {\n return CAUSE_CONTEXT_CODES.has(code);\n}\n", "import { SerialErrorCode } from './serial-error-code';\nimport {\n isCauseContextCode,\n type SerialErrorCauseContext,\n type SerialErrorContextMap,\n type ValidationErrorConstraint,\n type ValidationErrorContext,\n} from './serial-error-context';\n\n// Re-export SerialErrorCode for convenience\nexport { SerialErrorCode };\nexport type {\n SerialErrorCauseContext,\n SerialErrorContextMap,\n ValidationErrorConstraint,\n ValidationErrorContext,\n};\n\ninterface V8ErrorCaptureStackTrace {\n captureStackTrace?(\n targetObject: object,\n constructorOpt?: abstract new (...args: never[]) => unknown,\n ): void;\n}\n\nconst ErrorWithCaptureStackTrace = Error as typeof Error &\n V8ErrorCaptureStackTrace;\n\n/**\n * Custom error class for serial port operations.\n *\n * This error class extends the standard Error class and includes additional information\n * about the type of error that occurred. It provides an error code for programmatic\n * error handling and may include the original error that caused the failure.\n *\n * @example\n * ```typescript\n * session.errors$.subscribe((error) => {\n * if (error.is(SerialErrorCode.READ_FAILED)) {\n * console.error(`Error code: ${error.code}`);\n * console.error(`Message: ${error.message}`);\n * console.error(`Cause:`, error.context.cause);\n * }\n *\n * if (error.is(SerialErrorCode.LINE_BUFFER_OVERFLOW)) {\n * console.error(`maxChars: ${error.context.maxChars}`);\n * }\n *\n * if (error.is(SerialErrorCode.BROWSER_NOT_SUPPORTED)) {\n * // Handle browser not supported\n * }\n * });\n * ```\n */\nexport class SerialError<\n TCode extends SerialErrorCode = SerialErrorCode,\n> extends Error {\n /**\n * The error code identifying the type of error that occurred.\n *\n * Use this code to programmatically handle specific error conditions.\n *\n * @see {@link SerialErrorCode} for all available error codes\n */\n public readonly code: TCode;\n\n /**\n * Structured metadata associated with {@link code}.\n *\n * When {@link is} returns `true`, TypeScript narrows this property to the\n * context shape defined in {@link SerialErrorContextMap} for that code.\n */\n public readonly context: SerialErrorContextMap[TCode];\n\n /**\n * The original error that caused this SerialError, if available.\n *\n * This property contains the underlying error (e.g., DOMException, TypeError)\n * that was wrapped in this SerialError. It may be undefined if no original error exists.\n *\n * @deprecated Prefer {@link context} for cause-bearing codes. Still present in\n * v4 for backward compatibility and kept in sync when a cause is provided.\n * Removal is deferred to a future major (v5+).\n */\n public readonly originalError?: Error;\n\n /**\n * Creates a new SerialError instance.\n *\n * @param code - The error code identifying the type of error\n * @param message - A human-readable error message\n * @param originalError - The original error that caused this SerialError, if any.\n * @deprecated Prefer passing `{ cause }` via {@link context}. Still accepted\n * in v4 for backward compatibility; when omitted, cause-bearing codes derive\n * `{ cause }` from this argument. Removal is deferred to a future major (v5+).\n * @param context - Structured metadata for the error code. For cause-bearing\n * codes, pass `{ cause }` here. When omitted, cause-bearing codes derive\n * `{ cause }` from the legacy {@link originalError} argument.\n */\n constructor(\n code: TCode,\n message: string,\n originalError?: Error,\n context?: SerialErrorContextMap[TCode],\n ) {\n super(message);\n this.name = 'SerialError';\n this.code = code;\n\n if (context !== undefined) {\n this.context = context;\n } else if (\n originalError !== undefined &&\n isCauseContextCode(code)\n ) {\n this.context = { cause: originalError } as SerialErrorContextMap[TCode];\n } else {\n this.context = undefined as SerialErrorContextMap[TCode];\n }\n\n if (originalError !== undefined) {\n this.originalError = originalError;\n } else if (isCauseContextCode(code) && this.context !== undefined) {\n const causeContext = this.context as SerialErrorCauseContext;\n if (causeContext.cause instanceof Error) {\n this.originalError = causeContext.cause;\n }\n }\n\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n if (ErrorWithCaptureStackTrace.captureStackTrace) {\n ErrorWithCaptureStackTrace.captureStackTrace(this, SerialError);\n }\n }\n\n /**\n * Check if the error matches a specific error code.\n *\n * This is a convenience method for checking the error code without directly\n * comparing the code property. When this method returns `true`, TypeScript\n * narrows `this.code` and `this.context` to the shapes defined for the\n * provided `code` argument.\n *\n * @param code - The error code to check against\n * @returns Type predicate: `true` if this error's code matches the provided\n * code (and `this.code` / `this.context` are narrowed), `false` otherwise\n *\n * @example\n * ```typescript\n * if (error.is(SerialErrorCode.LINE_BUFFER_OVERFLOW)) {\n * // error.code and error.context.maxChars are narrowed\n * }\n * ```\n */\n public is<C extends SerialErrorCode>(\n code: C,\n ): this is SerialError<C> {\n return (this.code as SerialErrorCode) === code;\n }\n}\n", "declare const baudRateBrand: unique symbol;\n/** Validated serial port baud rate (safe integer > 0). */\nexport type BaudRate = number & { readonly [baudRateBrand]: true };\n\ndeclare const serialPortBufferSizeBrand: unique symbol;\n/** Validated W3C {@link SerialOptions} `bufferSize` (safe integer > 0). */\nexport type SerialPortBufferSize = number & {\n readonly [serialPortBufferSizeBrand]: true;\n};\n\ndeclare const maxCharsBrand: unique symbol;\n/** Validated character limit for buffers (`0` means unlimited). */\nexport type MaxChars = number & { readonly [maxCharsBrand]: true };\n\ndeclare const maxLinesBrand: unique symbol;\n/** Validated line count limit for terminal display (`0` means unlimited). */\nexport type MaxLines = number & { readonly [maxLinesBrand]: true };\n\n/** @internal Brand a validated baud rate. */\nexport function brandBaudRate(value: number): BaudRate {\n return value as BaudRate;\n}\n\n/** @internal Brand a validated serial port buffer size. */\nexport function brandSerialPortBufferSize(value: number): SerialPortBufferSize {\n return value as SerialPortBufferSize;\n}\n\n/** @internal Brand a validated max character limit. */\nexport function brandMaxChars(value: number): MaxChars {\n return value as MaxChars;\n}\n\n/** @internal Brand a validated max line limit. */\nexport function brandMaxLines(value: number): MaxLines {\n return value as MaxLines;\n}\n", "/**\n * Shared CR/LF/CRLF scanner for {@link createLineBuffer} and\n * {@link applyTerminalChunk}. Mode controls intentional semantic differences\n * between line-delimited output and terminal display redraw.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/376 | Issue #376}\n */\nexport type NewlineTokenizerMode = 'line' | 'terminal';\n\nexport type NewlineEvent =\n | { type: 'line'; content: string }\n | { type: 'carriage-return' };\n\nexport interface NewlineTokenizer {\n feed(chunk: string): NewlineEvent[];\n clear(): void;\n /** Replaces the incomplete tail (used when folding external state in). */\n restorePending(text: string): void;\n /** Incomplete tail retained across feeds (line mode may end with `\\r`). */\n getPendingText(): string;\n /**\n * Discards leading characters when pending text exceeds `maxChars`.\n * @returns `true` when trimming occurred.\n */\n trimPending(maxChars: number): boolean;\n}\n\n/**\n * Creates a streaming newline tokenizer.\n *\n * - **line**: interior lone `\\r` completes a line (#237); trailing `\\r` is\n * deferred until the next chunk disambiguates `\\r` vs `\\r\\n`.\n * - **terminal**: lone `\\r` clears the current line (#275); trailing `\\r`\n * applies immediately.\n */\nexport function createNewlineTokenizer(\n mode: NewlineTokenizerMode,\n): NewlineTokenizer {\n let pending = '';\n let deferredTrailingCr = false;\n\n const clear = (): void => {\n pending = '';\n deferredTrailingCr = false;\n };\n\n const restorePending = (text: string): void => {\n pending = text;\n deferredTrailingCr = false;\n };\n\n const feed = (chunk: string): NewlineEvent[] => {\n const events: NewlineEvent[] = [];\n let i = 0;\n\n if (mode === 'line' && deferredTrailingCr) {\n deferredTrailingCr = false;\n if (chunk.length > 0 && chunk.charAt(0) === '\\n') {\n events.push({ type: 'line', content: pending });\n pending = '';\n i = 1;\n } else {\n events.push({ type: 'line', content: pending });\n pending = '';\n }\n }\n\n const len = chunk.length;\n for (; i < len; i++) {\n const c = chunk.charAt(i);\n if (c === '\\r') {\n const next = i + 1 < len ? chunk.charAt(i + 1) : '';\n if (next === '\\n') {\n events.push({ type: 'line', content: pending });\n pending = '';\n i++;\n continue;\n }\n\n if (mode === 'line') {\n if (next !== '') {\n events.push({ type: 'line', content: pending });\n pending = '';\n continue;\n }\n deferredTrailingCr = true;\n break;\n }\n\n events.push({ type: 'carriage-return' });\n pending = '';\n continue;\n }\n\n if (c === '\\n') {\n events.push({ type: 'line', content: pending });\n pending = '';\n continue;\n }\n\n pending += c;\n }\n\n return events;\n };\n\n const trimPending = (maxChars: number): boolean => {\n if (maxChars <= 0) {\n return false;\n }\n\n const text = deferredTrailingCr ? `${pending}\\r` : pending;\n if (text.length <= maxChars) {\n return false;\n }\n\n const trimmed = text.slice(text.length - maxChars);\n if (trimmed.endsWith('\\r')) {\n pending = trimmed.slice(0, -1);\n deferredTrailingCr = true;\n } else {\n pending = trimmed;\n deferredTrailingCr = false;\n }\n return true;\n };\n\n return {\n feed,\n clear,\n restorePending,\n getPendingText: () => (deferredTrailingCr ? `${pending}\\r` : pending),\n trimPending,\n };\n}\n", "const ESC = '\\u001b';\n\n/** CSI final byte range per ECMA-48. */\nconst CSI_FINAL_BYTE = /[@-~]/;\n\nconst BEL = '\\u0007';\nconst OSC_STRING_TERMINATOR = '\\u001b\\\\';\n\nfunction findOscTerminator(\n oscBody: string,\n): { index: number; length: number } | null {\n let earliest: { index: number; length: number } | null = null;\n\n const belIndex = oscBody.indexOf(BEL);\n if (belIndex >= 0) {\n earliest = { index: belIndex, length: 1 };\n }\n\n const stIndex = oscBody.indexOf(OSC_STRING_TERMINATOR);\n if (stIndex >= 0) {\n const candidate = { index: stIndex, length: OSC_STRING_TERMINATOR.length };\n if (!earliest || stIndex < earliest.index) {\n earliest = candidate;\n }\n }\n\n return earliest;\n}\n\n/**\n * Stateful ANSI escape stripper for streaming text chunks.\n *\n * Buffers incomplete sequences that span Web Serial read boundaries so\n * partial ESC bytes are not emitted as visible text.\n *\n * @internal\n */\nexport interface AnsiStripper {\n feed(chunk: string): string;\n flush(): string;\n}\n\n/**\n * Strips ANSI escape sequences from a complete string in one pass.\n *\n * @internal\n */\nexport function stripAnsiSequences(text: string): string {\n const stripper = createAnsiStripper();\n return stripper.feed(text) + stripper.flush();\n}\n\n/**\n * Creates a streaming ANSI escape stripper.\n *\n * @internal\n */\nexport function createAnsiStripper(): AnsiStripper {\n let pending = '';\n\n const feed = (chunk: string): string => {\n const input = pending + chunk;\n pending = '';\n let output = '';\n let i = 0;\n\n while (i < input.length) {\n const escIndex = input.indexOf(ESC, i);\n if (escIndex < 0) {\n output += input.slice(i);\n break;\n }\n\n output += input.slice(i, escIndex);\n const next = escIndex + 1;\n if (next >= input.length) {\n pending = ESC;\n break;\n }\n\n const code = input.charCodeAt(next);\n\n // CSI: ESC [\n if (code === 0x5b) {\n const csiStart = next + 1;\n let j = csiStart;\n while (j < input.length && !CSI_FINAL_BYTE.test(input.charAt(j))) {\n j++;\n }\n if (j < input.length) {\n i = j + 1;\n continue;\n }\n pending = input.slice(escIndex);\n break;\n }\n\n // OSC: ESC ]\n if (code === 0x5d) {\n const oscBody = input.slice(next);\n const terminator = findOscTerminator(oscBody);\n if (terminator) {\n i = escIndex + 1 + terminator.index + terminator.length;\n continue;\n }\n pending = input.slice(escIndex);\n break;\n }\n\n // Two-character ESC sequences (e.g. ESC M, ESC 7, ESC 8)\n if (next < input.length) {\n i = next + 1;\n continue;\n }\n\n pending = input.slice(escIndex);\n break;\n }\n\n return output;\n };\n\n const flush = (): string => {\n if (pending.length === 0) {\n return '';\n }\n const remainder = pending;\n pending = '';\n return remainder;\n };\n\n return { feed, flush };\n}\n", "/**\n * Synchronous feature detection for the Web Serial API.\n *\n * Prefer this helper **before** creating a session. After a session exists,\n * drive unsupported UI from `state$` with `SerialSessionStatus.Unsupported`.\n * This helper is SSR-safe: it returns `false` when `navigator` is not available.\n *\n * @returns `true` when `navigator.serial` is available.\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/490 | Issue #490}\n */\nexport function isWebSerialSupported(): boolean {\n return (\n typeof navigator !== 'undefined' &&\n 'serial' in navigator &&\n navigator.serial !== undefined &&\n navigator.serial !== null\n );\n}\n", "import type { ReadPump } from '../read-pump';\nimport type { ReceivePipeline } from './receive-pipeline';\n\n/**\n * Dependencies for {@link createPortTeardown}.\n *\n * @internal\n */\nexport interface PortTeardownDeps {\n receivePipeline: ReceivePipeline;\n}\n\n/**\n * Port and pump teardown primitives for {@link createSerialSession}.\n *\n * These are extracted from the session lifecycle so that both the lifecycle\n * and the error reporter can depend on them without forming a construction\n * cycle: the error reporter needs `teardownPump` / `closePortSafely` for fatal\n * cleanup, while the lifecycle needs the error reporter for `reportError`.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/476 | Issue #476}\n */\nexport interface PortTeardown {\n teardownPump: (pump: ReadPump | null) => Promise<void>;\n closePortSafely: (port: SerialPort | null) => Promise<void>;\n}\n\n/**\n * @internal\n */\nexport function createPortTeardown(deps: PortTeardownDeps): PortTeardown {\n const { receivePipeline } = deps;\n\n const teardownPump = async (pump: ReadPump | null): Promise<void> => {\n receivePipeline.clearLineBuffer();\n if (pump) {\n await pump.stop();\n }\n };\n\n const closePortSafely = async (port: SerialPort | null): Promise<void> => {\n if (!port) {\n return;\n }\n try {\n await port.close();\n } catch {\n // The read pump may already have errored the stream, which makes\n // close() reject. We ignore it here because disconnect$ has a\n // dedicated error path for close failures initiated by the user.\n }\n };\n\n return { teardownPump, closePortSafely };\n}\n", "import { type Observable, Subject } from 'rxjs';\nimport { SerialError } from '../../errors/serial-error';\nimport { SerialErrorCode } from '../../errors/serial-error-code';\nimport { createLineBuffer, type LineBuffer } from './line-buffer';\nimport type { ResolvedSerialSessionOptions } from '../serial-session-options';\n\n/**\n * Dependencies for {@link createReceivePipeline}.\n *\n * @internal\n */\nexport interface ReceivePipelineDeps {\n resolvedOptions: ResolvedSerialSessionOptions;\n}\n\n/**\n * Receive-side stream wiring for {@link createSerialSession}.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/401 | Issue #401}\n */\nexport interface ReceivePipeline {\n receive$: Observable<string>;\n lines$: Observable<string>;\n /**\n * Buffer-overflow errors produced while decoding chunks. The factory pipes\n * these into the session's single error-reporting entry point so the receive\n * pipeline stays decoupled from error reporting and initialization order.\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/476 | Issue #476}\n */\n bufferErrors$: Observable<SerialError>;\n clearLineBuffer: () => void;\n handleChunk: (text: string) => void;\n complete: () => void;\n}\n\n/**\n * @internal\n */\nexport function createReceivePipeline(\n deps: ReceivePipelineDeps,\n): ReceivePipeline {\n const { resolvedOptions } = deps;\n\n const receiveSubject = new Subject<string>();\n const linesSubject = new Subject<string>();\n const bufferErrorsSubject = new Subject<SerialError>();\n const lineBuffer: LineBuffer = createLineBuffer(resolvedOptions.lineBuffer);\n\n const receive$ = receiveSubject.asObservable();\n const lines$ = linesSubject.asObservable();\n const bufferErrors$ = bufferErrorsSubject.asObservable();\n\n const clearLineBuffer = (): void => {\n lineBuffer.clear();\n };\n\n const handleChunk = (text: string): void => {\n receiveSubject.next(text);\n const { lines, overflowed } = lineBuffer.feed(text);\n if (overflowed) {\n bufferErrorsSubject.next(\n new SerialError(\n SerialErrorCode.LINE_BUFFER_OVERFLOW,\n 'Line buffer exceeded maxChars; leading data was discarded',\n undefined,\n { maxChars: resolvedOptions.lineBuffer.maxChars },\n ),\n );\n }\n for (const line of lines) {\n linesSubject.next(line);\n }\n };\n\n const complete = (): void => {\n receiveSubject.complete();\n linesSubject.complete();\n bufferErrorsSubject.complete();\n };\n\n return {\n receive$,\n lines$,\n bufferErrors$,\n clearLineBuffer,\n handleChunk,\n complete,\n };\n}\n", "import { createNewlineTokenizer } from '../../internal/newline-tokenizer';\nimport type { MaxChars } from '../../internal/branded-numbers';\nimport { brandMaxChars } from '../../internal/branded-numbers';\nimport { SerialError } from '../../errors/serial-error';\nimport { SerialErrorCode } from '../../errors/serial-error-code';\n\n/**\n * Options for {@link createLineBuffer}.\n *\n * Character counts use UTF-16 string length (JavaScript `.length`).\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/371 | Issue #371}\n */\nexport interface LineBufferOptions {\n /**\n * Maximum characters retained in the incomplete line tail (no line terminator yet).\n * When exceeded, leading characters are discarded. `0` means unlimited.\n * Must be a safe integer `>= 0` when validated.\n *\n * @default 1048576\n */\n maxChars?: number;\n}\n\n/** Default limits applied when {@link LineBufferOptions} fields are omitted. */\nexport const DEFAULT_LINE_BUFFER_OPTIONS: Required<LineBufferOptions> = {\n maxChars: 1_048_576,\n};\n\n/** Resolved line buffer options with validated branded numeric fields. */\nexport type ResolvedLineBufferOptions = {\n maxChars: MaxChars;\n};\n\n/**\n * Merge and validate {@link LineBufferOptions}.\n *\n * This is the single normalization path for line buffer defaults and boundary\n * checks used by {@link resolveSerialSessionOptions}.\n *\n * @throws {@link SerialError} with {@link SerialErrorCode.INVALID_LINE_BUFFER_OPTIONS}\n * when `maxChars` is out of range.\n */\nexport function resolveLineBufferOptions(\n options?: LineBufferOptions,\n): ResolvedLineBufferOptions {\n const merged: Required<LineBufferOptions> = {\n ...DEFAULT_LINE_BUFFER_OPTIONS,\n ...options,\n };\n\n const { maxChars } = merged;\n\n if (!Number.isSafeInteger(maxChars) || maxChars < 0) {\n throw new SerialError(\n SerialErrorCode.INVALID_LINE_BUFFER_OPTIONS,\n `Invalid lineBuffer.maxChars: ${maxChars}. Must be a safe integer >= 0.`,\n undefined,\n {\n field: 'lineBuffer.maxChars',\n value: maxChars,\n constraint: 'non-negative-safe-integer',\n },\n );\n }\n\n return {\n maxChars: brandMaxChars(maxChars),\n };\n}\n\n/** Result of {@link createLineBuffer.feed}. */\nexport interface LineBufferFeedResult {\n lines: string[];\n /** `true` when leading characters were discarded due to `maxChars`. */\n overflowed: boolean;\n}\n\n/**\n * Handle returned by {@link createLineBuffer}.\n *\n * @internal\n */\nexport interface LineBuffer {\n feed(chunk: string): LineBufferFeedResult;\n clear(): void;\n}\n\n/** @internal Resolved limits for {@link createLineBuffer}. */\nexport interface LineBufferLimits {\n maxChars: MaxChars;\n}\n\n/**\n * Streaming UTF-16 text to newline-delimited lines for {@link createSerialSession}.\n * Supports `\\r\\n` and `\\n` per #237; a lone `\\r` that is not the last character\n * in the buffer is treated as a line end (compatibility with some devices). A\n * trailing `\\r` is retained until a following chunk disambiguates `\\r` vs\n * `\\r\\n`.\n *\n * @internal\n */\nexport function createLineBuffer(\n options?: LineBufferOptions | LineBufferLimits,\n): LineBuffer {\n const limits: LineBufferLimits = resolveLineBufferOptions(options);\n\n const tokenizer = createNewlineTokenizer('line');\n\n const clear = (): void => {\n tokenizer.clear();\n };\n\n const feed = (chunk: string): LineBufferFeedResult => {\n const events = tokenizer.feed(chunk);\n const out: string[] = [];\n\n for (const event of events) {\n if (event.type === 'line') {\n out.push(event.content);\n }\n }\n\n const overflowed = tokenizer.trimPending(limits.maxChars);\n\n return { lines: out, overflowed };\n };\n\n return { feed, clear };\n}\n", "import { SerialErrorCode } from '../../errors/serial-error-code';\n\n/**\n * Internal error classification used by the single `reportError` entry\n * point. `'fatal'` errors drive `state$` into `'error'` and tear down\n * the live session (pump stop + port close); `'non-fatal'` errors are\n * only multiplexed on `errors$` without mutating session state.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/399 | Issue #399}\n */\nexport type ReportErrorSeverity = 'fatal' | 'non-fatal';\n\n/**\n * Maps each {@link SerialErrorCode} that can surface through session\n * `reportError` to its severity. Unmapped codes fall back to `'fatal'`\n * in {@link resolveErrorSeverity}.\n *\n * @internal\n */\nexport const ERROR_SEVERITY = {\n [SerialErrorCode.READ_FAILED]: 'fatal',\n [SerialErrorCode.CONNECTION_LOST]: 'fatal',\n [SerialErrorCode.PORT_OPEN_FAILED]: 'fatal',\n [SerialErrorCode.OPERATION_CANCELLED]: 'fatal',\n [SerialErrorCode.UNKNOWN]: 'fatal',\n [SerialErrorCode.LINE_BUFFER_OVERFLOW]: 'non-fatal',\n [SerialErrorCode.BROWSER_NOT_SUPPORTED]: 'non-fatal',\n [SerialErrorCode.PORT_ALREADY_OPEN]: 'non-fatal',\n [SerialErrorCode.PORT_NOT_OPEN]: 'non-fatal',\n [SerialErrorCode.WRITE_FAILED]: 'non-fatal',\n} as const satisfies Partial<Record<SerialErrorCode, ReportErrorSeverity>>;\n\n/**\n * Resolve the reporting severity for a normalised {@link SerialErrorCode}.\n *\n * @internal\n */\nexport function resolveErrorSeverity(\n code: SerialErrorCode,\n): ReportErrorSeverity {\n if (code in ERROR_SEVERITY) {\n return ERROR_SEVERITY[code as keyof typeof ERROR_SEVERITY];\n }\n return 'fatal';\n}\n", "import { SerialError } from '../errors/serial-error';\nimport { SerialErrorCode } from '../errors/serial-error-code';\n\n/**\n * Default human-readable prefix attached to normalized errors when the\n * caller does not supply one. Kept short so downstream messages remain\n * readable (e.g. `\"Serial operation failed: <cause>\"`).\n *\n * @internal\n */\nconst DEFAULT_MESSAGE_PREFIX = 'Serial operation failed';\n\n/**\n * Options accepted by {@link normalizeSerialError}.\n *\n * @internal\n */\nexport interface NormalizeSerialErrorOptions {\n /**\n * Error code assigned when the input cannot be classified more\n * specifically (for example a generic `Error` thrown from `port.open`).\n *\n * Most call sites in the session pipeline know which lifecycle phase\n * they are in (connect / read / write / close) and therefore pick a\n * phase-specific fallback such as {@link SerialErrorCode.PORT_OPEN_FAILED}\n * or {@link SerialErrorCode.WRITE_FAILED}.\n */\n fallbackCode: SerialErrorCode;\n /**\n * Prefix used when the input has to be wrapped. Ignored when the input\n * is already a {@link SerialError} so we never rewrite a well-formed\n * message the caller has already chosen.\n */\n messagePrefix?: string;\n}\n\nconst isDomExceptionWithName = (\n error: unknown,\n name: string,\n): error is DOMException =>\n typeof DOMException !== 'undefined' &&\n error instanceof DOMException &&\n error.name === name;\n\n/**\n * Normalize an arbitrary thrown value into a {@link SerialError}.\n *\n * This helper is the single entry point used by every v2 session\n * component (session factory, read pump, send queue) to coerce raw\n * platform errors into the library's error type. Centralising the\n * mapping here satisfies Issue #204's completion criterion that\n * \"error handling lives in one place\" and removes the duplicated\n * `toError` / `normalizeError` helpers previously scattered across\n * session modules.\n *\n * Mapping rules applied, in order:\n *\n * 1. `SerialError` instances pass through unchanged so a caller that has\n * already classified the failure (e.g. `PORT_NOT_OPEN` on a pre-open\n * `send$`) is not rewrapped.\n * 2. `DOMException('NotFoundError')` is mapped to\n * {@link SerialErrorCode.OPERATION_CANCELLED}. Chromium raises this\n * when the user dismisses the `requestPort` dialog; it is a normal\n * control-flow event, not a hard failure.\n * 3. Any other value is wrapped as a {@link SerialError} with\n * {@link NormalizeSerialErrorOptions.fallbackCode}, preserving the\n * underlying failure on {@link SerialError.context | context.cause}.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/204 | Issue #204}\n */\nexport function normalizeSerialError(\n error: unknown,\n options: NormalizeSerialErrorOptions,\n): SerialError {\n if (error instanceof SerialError) {\n return error;\n }\n\n const prefix = options.messagePrefix ?? DEFAULT_MESSAGE_PREFIX;\n\n if (isDomExceptionWithName(error, 'NotFoundError')) {\n return new SerialError(\n SerialErrorCode.OPERATION_CANCELLED,\n 'Port selection was cancelled by the user',\n undefined,\n { cause: error },\n );\n }\n\n const cause = error instanceof Error ? error : new Error(String(error));\n return new SerialError(\n options.fallbackCode,\n `${prefix}: ${cause.message}`,\n undefined,\n { cause },\n );\n}\n", "import { BehaviorSubject, Observable } from 'rxjs';\nimport { assertNever } from '../internal/assert-never';\nimport type { SerialError } from '../errors/serial-error';\nimport type { ReadPump } from './read-pump';\nimport {\n SerialSessionStatus,\n type SerialSessionState,\n type SerialSessionStatus as SerialSessionStatusType,\n} from './serial-session-state';\n\n/**\n * Discriminated union representing the full internal runtime state of a\n * {@link SerialSession}, including lifecycle status and any resources that\n * are valid for that status.\n *\n * This is the single source of truth for session lifecycle + resources.\n * Public consumers observe the mapped {@link SerialSessionState} via\n * `state$`; this union is internal only.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/397 | Issue #397}\n */\nexport type SessionRuntime =\n | IdleRuntime\n | ConnectingRuntime\n | ConnectedRuntime\n | DisconnectingRuntime\n | ErrorRuntime\n | UnsupportedRuntime\n | DisposedRuntime;\n\n/** @internal */\nexport interface IdleRuntime {\n readonly status: typeof SerialSessionStatus.Idle;\n}\n\n/** @internal */\nexport interface ConnectingRuntime {\n readonly status: typeof SerialSessionStatus.Connecting;\n readonly cancel: () => void;\n}\n\n/** @internal */\nexport interface ConnectedRuntime {\n readonly status: typeof SerialSessionStatus.Connected;\n readonly port: SerialPort;\n readonly pump: ReadPump;\n}\n\n/** @internal */\nexport interface DisconnectingRuntime {\n readonly status: typeof SerialSessionStatus.Disconnecting;\n readonly port: SerialPort | null;\n}\n\n/** @internal */\nexport interface ErrorRuntime {\n readonly status: typeof SerialSessionStatus.Error;\n readonly error: SerialError;\n}\n\n/** @internal */\nexport interface UnsupportedRuntime {\n readonly status: typeof SerialSessionStatus.Unsupported;\n}\n\n/** @internal */\nexport interface DisposedRuntime {\n readonly status: typeof SerialSessionStatus.Disposed;\n}\n\nconst S = SerialSessionStatus;\n\n/**\n * Allowed transitions for the internal SerialSession state machine.\n *\n * `as const satisfies` keeps literal transition targets while ensuring every\n * {@link SerialSessionStatus} key is present. Drift from the status union\n * becomes a compile error.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/398 | Issue #398}\n */\nexport const ALLOWED_TRANSITIONS = {\n [S.Idle]: [S.Connecting, S.Error, S.Disposed],\n [S.Connecting]: [S.Connected, S.Error, S.Idle, S.Disposed],\n [S.Connected]: [S.Disconnecting, S.Error, S.Disposed],\n [S.Disconnecting]: [S.Idle, S.Error, S.Disposed],\n [S.Error]: [S.Idle, S.Connecting, S.Disposed],\n [S.Unsupported]: [S.Disposed],\n [S.Disposed]: [],\n} as const satisfies Readonly<\n Record<SerialSessionStatusType, readonly SerialSessionStatusType[]>\n>;\n\n/**\n * Valid target states when transitioning from {@link T}.\n *\n * @internal\n */\nexport type AllowedTransition<T extends SerialSessionStatusType> =\n (typeof ALLOWED_TRANSITIONS)[T][number];\n\n/** @internal */\nexport function runtimeToSessionStatus(\n runtime: SessionRuntime,\n): SerialSessionStatusType {\n return runtime.status;\n}\n\n/**\n * Map internal runtime to the public {@link SerialSessionState} payload.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/406 | Issue #406}\n */\nexport function runtimeToPublicState(runtime: SessionRuntime): SerialSessionState {\n switch (runtime.status) {\n case S.Idle:\n return { status: S.Idle };\n case S.Connecting:\n return { status: S.Connecting };\n case S.Connected:\n return { status: S.Connected, portInfo: runtime.port.getInfo() };\n case S.Disconnecting:\n return { status: S.Disconnecting };\n case S.Unsupported:\n return { status: S.Unsupported };\n case S.Error:\n return { status: S.Error, error: runtime.error };\n case S.Disposed:\n return { status: S.Disposed };\n default:\n return assertNeverRuntime(runtime);\n }\n}\n\n/** @internal */\nexport function isValidTransition(\n from: SerialSessionStatusType,\n to: SerialSessionStatusType,\n): boolean {\n if (from === to) {\n return false;\n }\n return (ALLOWED_TRANSITIONS[from] as readonly SerialSessionStatusType[]).includes(\n to,\n );\n}\n\n/** @internal */\nexport function createIdleRuntime(): IdleRuntime {\n return { status: S.Idle };\n}\n\n/** @internal */\nexport function createConnectingRuntime(cancel: () => void): ConnectingRuntime {\n return { status: S.Connecting, cancel };\n}\n\n/** @internal */\nexport function createConnectedRuntime(\n port: SerialPort,\n pump: ReadPump,\n): ConnectedRuntime {\n return { status: S.Connected, port, pump };\n}\n\n/** @internal */\nexport function createDisconnectingRuntime(\n port: SerialPort | null,\n): DisconnectingRuntime {\n return { status: S.Disconnecting, port };\n}\n\n/** @internal */\nexport function createErrorRuntime(error: SerialError): ErrorRuntime {\n return { status: S.Error, error };\n}\n\n/** @internal */\nexport function createUnsupportedRuntime(): UnsupportedRuntime {\n return { status: S.Unsupported };\n}\n\n/** @internal */\nexport function createDisposedRuntime(): DisposedRuntime {\n return { status: S.Disposed };\n}\n\n/** @internal */\nexport function createInitialRuntime(supported: boolean): SessionRuntime {\n return supported ? createIdleRuntime() : createUnsupportedRuntime();\n}\n\n/**\n * Extract the active port from runtime when one is held.\n *\n * @internal\n */\nexport function getRuntimePort(runtime: SessionRuntime): SerialPort | null {\n switch (runtime.status) {\n case S.Connected:\n return runtime.port;\n case S.Disconnecting:\n return runtime.port;\n default:\n return null;\n }\n}\n\n/**\n * Extract the active pump from runtime when one is held.\n *\n * @internal\n */\nexport function getRuntimePump(runtime: SessionRuntime): ReadPump | null {\n if (runtime.status === S.Connected) {\n return runtime.pump;\n }\n return null;\n}\n\n/**\n * Controller that owns the {@link SessionRuntime} discriminated union and\n * exposes the public `state$` stream derived from the runtime.\n *\n * @internal\n */\nexport interface SessionRuntimeController {\n get runtime(): SessionRuntime;\n get status(): SerialSessionStatusType;\n get state$(): Observable<SerialSessionState>;\n transition(next: SessionRuntime): boolean;\n complete(): void;\n}\n\n/**\n * Create a controller for the internal session runtime.\n *\n * @internal\n */\nexport function createSessionRuntimeController(\n initial: SessionRuntime,\n): SessionRuntimeController {\n let runtime = initial;\n const subject = new BehaviorSubject<SerialSessionState>(\n runtimeToPublicState(initial),\n );\n\n const transition = (next: SessionRuntime): boolean => {\n const from = runtimeToSessionStatus(runtime);\n const to = runtimeToSessionStatus(next);\n\n if (from === to) {\n return false;\n }\n\n if (!isValidTransition(from, to)) {\n if (typeof console !== 'undefined' && console.warn) {\n console.warn(\n `[web-serial-rxjs] Ignoring invalid SerialSession transition ${from} -> ${to}`,\n );\n }\n return false;\n }\n\n // Project before mutating: `runtimeToPublicState` reads `port.getInfo()`,\n // so a throwing projection must not leave the runtime ahead of `state$`.\n const publicState = runtimeToPublicState(next);\n runtime = next;\n subject.next(publicState);\n return true;\n };\n\n return {\n get runtime() {\n return runtime;\n },\n get status() {\n return runtimeToSessionStatus(runtime);\n },\n get state$() {\n return subject.asObservable();\n },\n transition,\n complete() {\n subject.complete();\n },\n };\n}\n\n/**\n * Exhaustiveness helper for switch statements over {@link SessionRuntime}.\n *\n * @internal\n */\nexport function assertNeverRuntime(value: never): never {\n return assertNever(value);\n}\n", "import type { SerialError } from '../errors/serial-error';\n\n/**\n * Canonical status literals for a {@link SerialSession} lifecycle.\n *\n * Use these constants when comparing {@link SerialSessionState.status}\n * so call sites avoid string typos and get IDE completion.\n *\n * Lifecycle transitions:\n *\n * ```\n * idle -> connecting -> connected -> disconnecting -> idle\n * \\-> error\n * (any) -> unsupported (when Web Serial API is unavailable)\n * (any) -> error (when an unrecoverable failure occurs)\n * (any) -> disposed (when dispose$ completes)\n * ```\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/406 | Issue #406}\n */\nexport const SerialSessionStatus = {\n Idle: 'idle',\n Connecting: 'connecting',\n Connected: 'connected',\n Disconnecting: 'disconnecting',\n Unsupported: 'unsupported',\n Error: 'error',\n Disposed: 'disposed',\n} as const;\n\n/**\n * String union of allowed {@link SerialSessionStatus} runtime values\n * (same set as the values on the {@link SerialSessionStatus} object).\n */\nexport type SerialSessionStatus =\n (typeof SerialSessionStatus)[keyof typeof SerialSessionStatus];\n\n/** @see {@link SerialSessionState} */\nexport interface IdleSessionState {\n readonly status: typeof SerialSessionStatus.Idle;\n}\n\n/** @see {@link SerialSessionState} */\nexport interface ConnectingSessionState {\n readonly status: typeof SerialSessionStatus.Connecting;\n}\n\n/** Connected lifecycle variant with narrowed {@link SerialPortInfo} access via `portInfo`. */\nexport interface ConnectedSessionState {\n readonly status: typeof SerialSessionStatus.Connected;\n readonly portInfo: SerialPortInfo;\n}\n\n/** @see {@link SerialSessionState} */\nexport interface DisconnectingSessionState {\n readonly status: typeof SerialSessionStatus.Disconnecting;\n}\n\n/** @see {@link SerialSessionState} */\nexport interface UnsupportedSessionState {\n readonly status: typeof SerialSessionStatus.Unsupported;\n}\n\n/** @see {@link SerialSessionState} */\nexport interface ErrorSessionState {\n readonly status: typeof SerialSessionStatus.Error;\n readonly error: SerialError;\n}\n\n/** @see {@link SerialSessionState} */\nexport interface DisposedSessionState {\n readonly status: typeof SerialSessionStatus.Disposed;\n}\n\n/**\n * Discriminated union emitted by {@link SerialSession.state$} \u2014 the\n * canonical lifecycle source.\n *\n * Each variant carries the lifecycle `status` plus optional detail fields\n * (`portInfo` when connected, `error` when in a fatal error state).\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/406 | Issue #406}\n */\nexport type SerialSessionState =\n | IdleSessionState\n | ConnectingSessionState\n | ConnectedSessionState\n | DisconnectingSessionState\n | UnsupportedSessionState\n | ErrorSessionState\n | DisposedSessionState;\n", "import type { Subject } from 'rxjs';\nimport { SerialError } from '../../errors/serial-error';\nimport { SerialErrorCode } from '../../errors/serial-error-code';\nimport { resolveErrorSeverity } from './error-severity';\nimport {\n normalizeSerialError,\n type NormalizeSerialErrorOptions,\n} from '../normalize-serial-error';\nimport type { ReadPump } from '../read-pump';\nimport type { SendQueue } from '../send-queue';\nimport {\n createErrorRuntime,\n getRuntimePort,\n getRuntimePump,\n type SessionRuntimeController,\n} from '../session-runtime';\n\n/**\n * Dependencies for {@link createSessionErrorReporter}.\n *\n * @internal\n */\nexport interface SessionErrorReporterDeps {\n controller: SessionRuntimeController;\n errorsSubject: Subject<SerialError>;\n sendQueue: SendQueue;\n isDisposed: () => boolean;\n teardownPump: (pump: ReadPump | null) => Promise<void>;\n closePortSafely: (port: SerialPort | null) => Promise<void>;\n}\n\n/**\n * Centralised error reporting for {@link createSerialSession}.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/204 | Issue #204}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/401 | Issue #401}\n */\nexport function createSessionErrorReporter(deps: SessionErrorReporterDeps): {\n reportError: (\n error: unknown,\n options: NormalizeSerialErrorOptions,\n ) => SerialError;\n createDisposedError: () => SerialError;\n} {\n const {\n controller,\n errorsSubject,\n sendQueue,\n isDisposed,\n teardownPump,\n closePortSafely,\n } = deps;\n\n const createDisposedError = (): SerialError =>\n new SerialError(\n SerialErrorCode.SESSION_DISPOSED,\n 'SerialSession has been disposed',\n );\n\n /**\n * Single entry point for every error that should reach `errors$`.\n *\n * Responsibilities:\n *\n * 1. Normalise the input through {@link normalizeSerialError} so every\n * emission is a well-formed {@link SerialError}.\n * 2. Multiplex the normalised error on `errors$`.\n * 3. Resolve severity from the normalised {@link SerialError.code} via\n * {@link resolveErrorSeverity} (see `ERROR_SEVERITY` in\n * `error-severity.ts`).\n * 4. For fatal severities, drive `state$` to `'error'`, clear the send\n * queue so pending writes fail fast, and tear down the live pump +\n * port off the hot path.\n *\n * Returning the normalised error keeps call sites terse: they can hand\n * the result straight to `subscriber.error(...)` without re-normalising.\n */\n const reportError = (\n error: unknown,\n options: NormalizeSerialErrorOptions,\n ): SerialError => {\n const serialError = normalizeSerialError(error, options);\n if (isDisposed()) {\n return serialError;\n }\n errorsSubject.next(serialError);\n if (resolveErrorSeverity(serialError.code) === 'fatal') {\n const runtime = controller.runtime;\n const portToClose = getRuntimePort(runtime);\n const pump = getRuntimePump(runtime);\n controller.transition(createErrorRuntime(serialError));\n sendQueue.clear();\n void teardownPump(pump).then(() => closePortSafely(portToClose));\n }\n return serialError;\n };\n\n return { reportError, createDisposedError };\n}\n", "import { Observable, type Subject } from 'rxjs';\nimport { SerialError } from '../../errors/serial-error';\nimport { SerialErrorCode } from '../../errors/serial-error-code';\nimport { buildRequestOptions } from './build-request-options';\nimport { isWebSerialSupported } from './has-web-serial-support';\nimport type { ReceivePipeline } from './receive-pipeline';\nimport {\n normalizeSerialError,\n type NormalizeSerialErrorOptions,\n} from '../normalize-serial-error';\nimport { createReadPump, type ReadPump } from '../read-pump';\nimport type { SendQueue } from '../send-queue';\nimport type { ResolvedSerialSessionOptions } from '../serial-session-options';\nimport { SerialSessionStatus } from '../serial-session-state';\nimport {\n createConnectedRuntime,\n createConnectingRuntime,\n createDisconnectingRuntime,\n createDisposedRuntime,\n createIdleRuntime,\n getRuntimePort,\n getRuntimePump,\n type SessionRuntime,\n type SessionRuntimeController,\n} from '../session-runtime';\n\n/**\n * Dependencies for {@link createSessionLifecycle}.\n *\n * @internal\n */\nexport interface SessionLifecycleDeps {\n controller: SessionRuntimeController;\n resolvedOptions: ResolvedSerialSessionOptions;\n sendQueue: SendQueue;\n receivePipeline: ReceivePipeline;\n errorsSubject: Subject<SerialError>;\n isDisposed: () => boolean;\n teardownPump: (pump: ReadPump | null) => Promise<void>;\n closePortSafely: (port: SerialPort | null) => Promise<void>;\n reportError: (\n error: unknown,\n options: NormalizeSerialErrorOptions,\n ) => SerialError;\n createDisposedError: () => SerialError;\n}\n\n/**\n * Port and pump lifecycle operations for {@link createSerialSession}.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/401 | Issue #401}\n */\nexport interface SessionLifecycle {\n connect$: () => Observable<void>;\n disconnect$: () => Observable<void>;\n dispose$: () => Observable<void>;\n writeToPort: (payload: Uint8Array) => Promise<void>;\n}\n\n/**\n * @internal\n */\nexport function createSessionLifecycle(\n deps: SessionLifecycleDeps,\n): SessionLifecycle {\n const {\n controller,\n resolvedOptions,\n sendQueue,\n receivePipeline,\n errorsSubject,\n isDisposed,\n teardownPump,\n closePortSafely,\n reportError,\n createDisposedError,\n } = deps;\n\n const teardownFromSnapshot = async (\n snapshot: SessionRuntime,\n ): Promise<void> => {\n if (snapshot.status === SerialSessionStatus.Connecting) {\n snapshot.cancel();\n }\n\n sendQueue.clear();\n\n if (\n snapshot.status === SerialSessionStatus.Connected ||\n snapshot.status === SerialSessionStatus.Disconnecting ||\n snapshot.status === SerialSessionStatus.Error\n ) {\n const portToClose = getRuntimePort(snapshot);\n const pump = getRuntimePump(snapshot);\n await teardownPump(pump);\n await closePortSafely(portToClose);\n }\n\n receivePipeline.clearLineBuffer();\n };\n\n const completeSubjects = (): void => {\n controller.complete();\n errorsSubject.complete();\n receivePipeline.complete();\n };\n\n const dispose$ = (): Observable<void> =>\n new Observable<void>((subscriber) => {\n if (isDisposed()) {\n subscriber.next();\n subscriber.complete();\n return;\n }\n\n const snapshot = controller.runtime;\n controller.transition(createDisposedRuntime());\n\n const run = async (): Promise<void> => {\n try {\n await teardownFromSnapshot(snapshot);\n completeSubjects();\n subscriber.next();\n subscriber.complete();\n } catch (error) {\n const serialError = normalizeSerialError(error, {\n fallbackCode: SerialErrorCode.UNKNOWN,\n messagePrefix: 'Unexpected dispose failure',\n });\n subscriber.error(serialError);\n }\n };\n\n void run();\n });\n\n const writeToPort = async (payload: Uint8Array): Promise<void> => {\n const runtime = controller.runtime;\n if (\n runtime.status !== SerialSessionStatus.Connected ||\n !runtime.port.writable\n ) {\n throw new SerialError(\n SerialErrorCode.PORT_NOT_OPEN,\n 'Cannot send data while session is not connected',\n );\n }\n const writer = runtime.port.writable.getWriter();\n try {\n await writer.write(payload);\n } finally {\n try {\n writer.releaseLock();\n } catch {\n // releaseLock throws when the stream is already errored; the real\n // failure is surfaced through the write() rejection above so we\n // intentionally swallow this secondary error.\n }\n }\n };\n\n const connect$ = (): Observable<void> =>\n new Observable<void>((subscriber) => {\n if (isDisposed()) {\n subscriber.error(createDisposedError());\n return;\n }\n\n if (!isWebSerialSupported()) {\n const error = reportError(\n new SerialError(\n SerialErrorCode.BROWSER_NOT_SUPPORTED,\n 'Web Serial API is not supported in this environment',\n ),\n { fallbackCode: SerialErrorCode.BROWSER_NOT_SUPPORTED },\n );\n subscriber.error(error);\n return;\n }\n\n const current = controller.status;\n if (\n current !== SerialSessionStatus.Idle &&\n current !== SerialSessionStatus.Error\n ) {\n const error = reportError(\n new SerialError(\n SerialErrorCode.PORT_ALREADY_OPEN,\n `Cannot connect while session state is '${current}'`,\n ),\n { fallbackCode: SerialErrorCode.PORT_ALREADY_OPEN },\n );\n subscriber.error(error);\n return;\n }\n\n let cancelled = false;\n const cancelInFlightConnect = (): void => {\n cancelled = true;\n if (controller.status === SerialSessionStatus.Connecting) {\n controller.transition(createIdleRuntime());\n }\n };\n controller.transition(createConnectingRuntime(cancelInFlightConnect));\n\n const run = async (): Promise<void> => {\n let selectedPort: SerialPort | null = null;\n try {\n selectedPort = await navigator.serial.requestPort(\n buildRequestOptions(resolvedOptions),\n );\n await selectedPort.open({\n baudRate: resolvedOptions.baudRate,\n dataBits: resolvedOptions.dataBits,\n stopBits: resolvedOptions.stopBits,\n parity: resolvedOptions.parity,\n bufferSize: resolvedOptions.bufferSize,\n flowControl: resolvedOptions.flowControl,\n } satisfies SerialOptions);\n } catch (error) {\n if (selectedPort) {\n await closePortSafely(selectedPort);\n }\n const serialError = reportError(error, {\n fallbackCode: SerialErrorCode.PORT_OPEN_FAILED,\n messagePrefix: 'Failed to open port',\n });\n if (!cancelled) {\n subscriber.error(serialError);\n }\n return;\n }\n\n if (cancelled || isDisposed()) {\n await closePortSafely(selectedPort);\n return;\n }\n\n receivePipeline.clearLineBuffer();\n const pump = createReadPump(selectedPort, {\n onChunk: receivePipeline.handleChunk,\n onError: (pumpError) =>\n reportError(pumpError, {\n fallbackCode: SerialErrorCode.READ_FAILED,\n messagePrefix: 'Read pump failed',\n }),\n onDone: () => {\n if (controller.status !== SerialSessionStatus.Connected) {\n return;\n }\n reportError(\n new SerialError(\n SerialErrorCode.CONNECTION_LOST,\n 'Read pump ended unexpectedly: stream closed while connected',\n ),\n {\n fallbackCode: SerialErrorCode.CONNECTION_LOST,\n },\n );\n },\n });\n pump.start();\n sendQueue.clear();\n if (isDisposed()) {\n await teardownPump(pump);\n await closePortSafely(selectedPort);\n return;\n }\n controller.transition(createConnectedRuntime(selectedPort, pump));\n subscriber.next();\n subscriber.complete();\n };\n\n void run();\n\n return () => {\n cancelInFlightConnect();\n };\n });\n\n const disconnect$ = (): Observable<void> =>\n new Observable<void>((subscriber) => {\n if (isDisposed()) {\n subscriber.next();\n subscriber.complete();\n return;\n }\n\n const runtime = controller.runtime;\n\n if (\n runtime.status === SerialSessionStatus.Idle ||\n runtime.status === SerialSessionStatus.Unsupported ||\n runtime.status === SerialSessionStatus.Disconnecting\n ) {\n subscriber.next();\n subscriber.complete();\n return;\n }\n\n if (runtime.status === SerialSessionStatus.Connecting) {\n runtime.cancel();\n subscriber.next();\n subscriber.complete();\n return;\n }\n\n if (\n runtime.status !== SerialSessionStatus.Connected &&\n runtime.status !== SerialSessionStatus.Error\n ) {\n const error = reportError(\n new SerialError(\n SerialErrorCode.PORT_NOT_OPEN,\n `Cannot disconnect while session state is '${runtime.status}'`,\n ),\n { fallbackCode: SerialErrorCode.PORT_NOT_OPEN },\n );\n subscriber.error(error);\n return;\n }\n\n const portToClose = getRuntimePort(runtime);\n controller.transition(createDisconnectingRuntime(portToClose));\n sendQueue.clear();\n\n const run = async (): Promise<void> => {\n try {\n const pump = getRuntimePump(controller.runtime);\n await teardownPump(pump);\n if (portToClose) {\n try {\n await portToClose.close();\n } catch (error) {\n const serialError = reportError(error, {\n fallbackCode: SerialErrorCode.CONNECTION_LOST,\n messagePrefix: 'Failed to close port',\n });\n subscriber.error(serialError);\n return;\n }\n }\n if (!isDisposed()) {\n controller.transition(createIdleRuntime());\n }\n subscriber.next();\n subscriber.complete();\n } catch (error) {\n const serialError = reportError(error, {\n fallbackCode: SerialErrorCode.UNKNOWN,\n messagePrefix: 'Unexpected disconnect failure',\n });\n subscriber.error(serialError);\n }\n };\n\n void run();\n });\n\n return {\n connect$,\n disconnect$,\n dispose$,\n writeToPort,\n };\n}\n", "import type { SerialSessionOptions } from '../serial-session-options';\n\n/**\n * Build {@link SerialPortRequestOptions} from resolved {@link SerialSessionOptions}.\n *\n * Converts the `filters` field into the shape expected by\n * `navigator.serial.requestPort`. Returns `undefined` when no filters are\n * supplied so the browser shows all available ports.\n *\n * Filter validation is performed earlier by\n * {@link resolveSerialSessionOptions} at factory time.\n *\n * @param options - Resolved session options (filters already validated).\n * @returns The request options, or `undefined` when no filters are set.\n *\n * @internal\n */\nexport function buildRequestOptions(\n options?: SerialSessionOptions,\n): SerialPortRequestOptions | undefined {\n if (!options?.filters || options.filters.length === 0) {\n return undefined;\n }\n\n return {\n filters: options.filters,\n };\n}\n", "import { SerialError } from '../errors/serial-error';\nimport { SerialErrorCode } from '../errors/serial-error-code';\nimport { normalizeSerialError } from './normalize-serial-error';\n\n/**\n * Callback invoked for every decoded chunk the read pump produces.\n *\n * The text is decoded from the raw bytes using a shared `TextDecoder` with\n * `{ stream: true }`, so multi-byte characters that straddle chunk\n * boundaries are joined correctly before reaching the callback.\n *\n * @internal\n */\nexport type ReadPumpChunkHandler = (text: string) => void;\n\n/**\n * Callback invoked when the read pump cannot continue.\n *\n * The provided error is always a {@link SerialError}; raw platform errors\n * are normalized by the pump before they reach the caller so consumers only\n * ever observe the library's error type.\n *\n * @internal\n */\nexport type ReadPumpErrorHandler = (error: SerialError) => void;\n\n/**\n * Callback invoked when the read loop ends naturally (`reader.read()`\n * resolved with `done: true`) while the pump is still active.\n *\n * @internal\n */\nexport type ReadPumpDoneHandler = () => void;\n\n/**\n * Options accepted by {@link createReadPump}.\n *\n * @internal\n */\nexport interface ReadPumpOptions {\n onChunk: ReadPumpChunkHandler;\n onError: ReadPumpErrorHandler;\n onDone?: ReadPumpDoneHandler;\n}\n\n/**\n * Handle returned by {@link createReadPump}.\n *\n * @internal\n */\nexport interface ReadPump {\n /**\n * Start reading from the associated `SerialPort.readable` stream.\n *\n * Subsequent calls are ignored while the pump is already running.\n */\n start(): void;\n /**\n * Stop the read loop and release the underlying reader lock.\n *\n * Safe to call multiple times or before `start()`.\n */\n stop(): Promise<void>;\n /**\n * `true` while the internal loop is actively reading.\n */\n readonly isRunning: boolean;\n}\n\n/**\n * Create an internal read pump for a {@link SerialPort}.\n *\n * The pump is an implementation detail of the v2 `SerialSession` API: it\n * owns a single `TextDecoder` (with `stream: true`), drives a\n * `reader.read()` loop against `port.readable`, and forwards decoded text\n * to the provided sink. It is **not** subscription-lazy - the `SerialSession`\n * starts the pump as soon as `connect$` succeeds so that `receive$`\n * subscribers never miss data because of subscription timing.\n *\n * Errors are normalized into {@link SerialError} with\n * {@link SerialErrorCode.READ_FAILED} for read-side failures and\n * {@link SerialErrorCode.CONNECTION_LOST} for missing readable streams, so\n * the session can forward them to `errors$` without rewrapping.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/202 | Issue #202}\n */\nexport function createReadPump(\n port: SerialPort,\n { onChunk, onError, onDone }: ReadPumpOptions,\n): ReadPump {\n let reader: ReadableStreamDefaultReader<Uint8Array> | null = null;\n let running = false;\n let stopped = false;\n const decoder = new TextDecoder(undefined, { fatal: false });\n\n const releaseReader = (): void => {\n if (!reader) {\n return;\n }\n try {\n reader.releaseLock();\n } catch {\n // releaseLock may throw when the reader is already detached; ignore.\n }\n reader = null;\n };\n\n const pump = async (stream: ReadableStream<Uint8Array>): Promise<void> => {\n reader = stream.getReader();\n running = true;\n try {\n while (!stopped) {\n const { done, value } = await reader.read();\n if (done) {\n if (!stopped) {\n onDone?.();\n }\n break;\n }\n if (value && value.byteLength > 0) {\n const text = decoder.decode(value, { stream: true });\n if (text.length > 0) {\n onChunk(text);\n }\n }\n }\n if (!stopped) {\n const tail = decoder.decode();\n if (tail.length > 0) {\n onChunk(tail);\n }\n }\n } catch (error) {\n if (!stopped) {\n onError(\n normalizeSerialError(error, {\n fallbackCode: SerialErrorCode.READ_FAILED,\n messagePrefix: 'Read pump failed',\n }),\n );\n }\n } finally {\n running = false;\n releaseReader();\n }\n };\n\n return {\n start(): void {\n if (running || stopped) {\n return;\n }\n const stream = port.readable;\n if (!stream) {\n stopped = true;\n onError(\n new SerialError(\n SerialErrorCode.CONNECTION_LOST,\n 'Read pump failed: port.readable is not available',\n ),\n );\n return;\n }\n void pump(stream);\n },\n async stop(): Promise<void> {\n if (stopped) {\n return;\n }\n stopped = true;\n if (!reader) {\n return;\n }\n try {\n await reader.cancel();\n } catch {\n // Cancel can reject when the stream is already errored; ignore so\n // the caller's disconnect flow is not derailed by a read failure.\n } finally {\n releaseReader();\n }\n },\n get isRunning(): boolean {\n return running;\n },\n };\n}\n", "import { Observable, defer } from 'rxjs';\n\n/**\n * A single enqueued unit of work. Operations are awaited sequentially so\n * that `send$` guarantees call order even when the caller fires multiple\n * observables concurrently.\n *\n * @internal\n */\nexport type SendQueueOperation<T> = () => Promise<T>;\n\n/**\n * Internal helper returned by {@link createSendQueue}.\n *\n * The queue is intentionally not an RxJS operator because Issue #199 calls\n * out explicitly that `send$` must not be re-implemented with `mergeMap`\n * (or any other concurrency-altering operator). A chained `Promise<void>`\n * is the simplest way to guarantee strict FIFO ordering while still\n * surfacing per-operation completion back to the caller.\n *\n * @internal\n */\nexport interface SendQueue {\n /**\n * Schedule an operation to run after all previously enqueued operations\n * have settled (resolved or rejected). The returned Observable completes\n * with the operation's resolved value, or errors with its rejection.\n *\n * Subscribing is what actually schedules the work - the queue is driven\n * by `defer`, so late/never-subscribed Observables never enqueue.\n */\n enqueue<T>(operation: SendQueueOperation<T>): Observable<T>;\n /**\n * Reset the internal promise chain. Existing in-flight operations keep\n * running because we do not cancel native promises, but no newly\n * enqueued work will wait for them. Use this when the session is torn\n * down so a fresh `connect$` starts with a clean chain.\n */\n clear(): void;\n}\n\n/**\n * Create an internal send queue that serialises `send$` writes.\n *\n * Ordering model:\n *\n * - Each `enqueue` call appends its operation to a single `Promise<void>`\n * chain.\n * - A failure in one operation does not poison the chain - the next\n * operation still runs (the chain is advanced with a `.then(() => {},\n * () => {})` safety tail).\n * - Unsubscribing before the operation resolves suppresses `next` /\n * `complete` / `error` for that subscriber; the underlying promise is\n * still awaited so later operations continue to respect call order.\n *\n * @internal\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/203 | Issue #203}\n */\nexport function createSendQueue(): SendQueue {\n let chain: Promise<void> = Promise.resolve();\n\n return {\n enqueue<T>(operation: SendQueueOperation<T>): Observable<T> {\n return defer(\n () =>\n new Observable<T>((subscriber) => {\n let cancelled = false;\n\n const run = async (): Promise<void> => {\n try {\n const value = await operation();\n if (!cancelled) {\n subscriber.next(value);\n subscriber.complete();\n }\n } catch (error) {\n if (!cancelled) {\n subscriber.error(error);\n }\n }\n };\n\n const scheduled = chain.then(run, run);\n chain = scheduled.then(\n () => undefined,\n () => undefined,\n );\n\n return () => {\n cancelled = true;\n };\n }),\n );\n },\n clear(): void {\n chain = Promise.resolve();\n },\n };\n}\n", "import { SerialError } from '../../errors/serial-error';\nimport { SerialErrorCode } from '../../errors/serial-error-code';\n\n/**\n * Validate {@link SerialPortFilter} entries for session creation.\n *\n * @param filters - Optional filters from {@link SerialSessionOptions}.\n * @returns The same filters reference when valid, or `undefined` when omitted/empty.\n * @throws {@link SerialError} with {@link SerialErrorCode.INVALID_FILTER_OPTIONS}\n * when a filter is empty or contains out-of-range IDs.\n *\n * @internal\n */\nexport function validateSerialPortFilters(\n filters?: SerialPortFilter[],\n): SerialPortFilter[] | undefined {\n if (!filters || filters.length === 0) {\n return filters;\n }\n\n for (let filterIndex = 0; filterIndex < filters.length; filterIndex++) {\n const filter = filters[filterIndex];\n if (filter === undefined) {\n continue;\n }\n if (!filter.usbVendorId && !filter.usbProductId) {\n throw new SerialError(\n SerialErrorCode.INVALID_FILTER_OPTIONS,\n 'Filter must have at least usbVendorId or usbProductId',\n undefined,\n {\n field: 'filters',\n value: filter,\n constraint: 'at-least-one-usb-id',\n filterIndex,\n },\n );\n }\n\n if (filter.usbVendorId !== undefined) {\n if (\n !Number.isInteger(filter.usbVendorId) ||\n filter.usbVendorId < 0 ||\n filter.usbVendorId > 0xffff\n ) {\n throw new SerialError(\n SerialErrorCode.INVALID_FILTER_OPTIONS,\n `Invalid usbVendorId: ${filter.usbVendorId}. Must be an integer between 0 and 65535.`,\n undefined,\n {\n field: 'usbVendorId',\n value: filter.usbVendorId,\n constraint: 'usb-id-0-65535',\n filterIndex,\n },\n );\n }\n }\n\n if (filter.usbProductId !== undefined) {\n if (\n !Number.isInteger(filter.usbProductId) ||\n filter.usbProductId < 0 ||\n filter.usbProductId > 0xffff\n ) {\n throw new SerialError(\n SerialErrorCode.INVALID_FILTER_OPTIONS,\n `Invalid usbProductId: ${filter.usbProductId}. Must be an integer between 0 and 65535.`,\n undefined,\n {\n field: 'usbProductId',\n value: filter.usbProductId,\n constraint: 'usb-id-0-65535',\n filterIndex,\n },\n );\n }\n }\n }\n\n return filters;\n}\n", "import {\n brandBaudRate,\n brandSerialPortBufferSize,\n type BaudRate,\n type SerialPortBufferSize,\n} from '../internal/branded-numbers';\nimport type { SerialConnectionOptions } from '../types';\nimport {\n resolveTerminalBufferOptions,\n type ResolvedTerminalBufferOptions,\n type TerminalBufferOptions,\n} from '../terminal/create-terminal-buffer';\nimport { SerialError } from '../errors/serial-error';\nimport { SerialErrorCode } from '../errors/serial-error-code';\nimport {\n resolveLineBufferOptions,\n type LineBufferOptions,\n type ResolvedLineBufferOptions,\n} from './internal/line-buffer';\nimport { validateSerialPortFilters } from './internal/validate-serial-port-filters';\n\nexport type {\n ResolvedLineBufferOptions,\n ResolvedTerminalBufferOptions,\n};\n\n/**\n * Library-specific options for {@link createSerialSession} that are not passed\n * to W3C `port.open`.\n *\n * Responsibility split (Issue #488):\n *\n * - {@link SerialSessionFeatureOptions.filters} \u2014 port selection only\n * (`navigator.serial.requestPort`)\n * - {@link SerialSessionFeatureOptions.lineBuffer} \u2014 incomplete-line tail for\n * {@link SerialSession.lines$}\n * - {@link SerialSessionFeatureOptions.terminalBuffer} \u2014 display memory for\n * {@link SerialSession.terminalText$}\n *\n * Connection parameters (`baudRate`, `dataBits`, \u2026) live on\n * {@link SerialConnectionOptions} and are composed into\n * {@link SerialSessionOptions}. Minimal callers typically only set\n * `baudRate`; other fields keep safe defaults.\n *\n * @see {@link SerialConnectionOptions} for W3C connection parameters\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/441 | Issue #441}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/488 | Issue #488}\n */\nexport interface SerialSessionFeatureOptions {\n /**\n * Filters for port selection when requesting a port.\n *\n * When specified, the port selection dialog will only show devices\n * matching these filters. Each filter can specify `usbVendorId` and/or\n * `usbProductId`. Not passed to `port.open`.\n */\n filters?: SerialPortFilter[];\n\n /**\n * Limits for {@link SerialSession.terminalText$} display memory. Oldest\n * completed lines and leading characters are dropped when exceeded.\n * Character counts use UTF-16 string length (JavaScript `.length`).\n *\n * Pass `0` for `maxLines` or `maxChars` to disable that limit (unlimited).\n * Negative values, non-integers, `NaN`, and `Infinity` are rejected at\n * factory time.\n *\n * @default `{ maxLines: 10000, maxChars: 1048576, stripAnsi: true }`\n * (see {@link DEFAULT_SERIAL_SESSION_OPTIONS})\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/370 | Issue #370}\n */\n terminalBuffer?: TerminalBufferOptions;\n\n /**\n * Limits for the incomplete line tail held by {@link SerialSession.lines$}\n * framing. When exceeded, leading characters are discarded and a non-fatal\n * {@link SerialErrorCode.LINE_BUFFER_OVERFLOW} is emitted on\n * {@link SerialSession.errors$}. Character counts use UTF-16 string length.\n *\n * Pass `0` for `maxChars` to disable the limit (unlimited). Negative values,\n * non-integers, `NaN`, and `Infinity` are rejected at factory time.\n *\n * @default `{ maxChars: 1048576 }` (see {@link DEFAULT_SERIAL_SESSION_OPTIONS})\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/371 | Issue #371}\n */\n lineBuffer?: LineBufferOptions;\n}\n\n/**\n * Options for creating a {@link SerialSession} via {@link createSerialSession}.\n *\n * Composes {@link Partial}<{@link SerialConnectionOptions}> (W3C connection\n * parameters passed to `port.open`) with {@link SerialSessionFeatureOptions}\n * (library-specific session features). All fields are optional; omitted values\n * fall back to {@link DEFAULT_SERIAL_SESSION_OPTIONS}.\n *\n * Minimal usage typically only needs `baudRate`:\n *\n * @example\n * ```typescript\n * const session = createSerialSession({ baudRate: 115200 });\n * ```\n *\n * Full example:\n *\n * @example\n * ```typescript\n * const session = createSerialSession({\n * baudRate: 115200,\n * dataBits: 8,\n * stopBits: 1,\n * parity: 'none',\n * flowControl: 'none',\n * filters: [{ usbVendorId: 0x1234, usbProductId: 0x5678 }],\n * });\n * ```\n *\n * Boundary semantics for numeric limits:\n *\n * - `undefined` \u2014 apply the default from {@link DEFAULT_SERIAL_SESSION_OPTIONS}\n * - `baudRate` / `bufferSize` \u2014 must be safe integers `> 0` (rejected otherwise)\n * - `terminalBuffer` / `lineBuffer` limits \u2014 safe integers `>= 0`; `0` means unlimited\n *\n * @see {@link SerialConnectionOptions}\n * @see {@link SerialSessionFeatureOptions}\n * @see {@link SerialOptions}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/199 | Issue #199}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/200 | Issue #200}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/402 | Issue #402}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/441 | Issue #441}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/488 | Issue #488}\n */\nexport interface SerialSessionOptions\n extends Partial<SerialConnectionOptions>, SerialSessionFeatureOptions {}\n\n/**\n * Fully resolved session options after merging {@link SerialSessionOptions}\n * with {@link DEFAULT_SERIAL_SESSION_OPTIONS}. All invariant fields are\n * required; `filters` remains optional.\n */\nexport type ResolvedSerialSessionOptions = Required<\n Omit<\n SerialSessionOptions,\n | 'filters'\n | 'terminalBuffer'\n | 'lineBuffer'\n | 'baudRate'\n | 'bufferSize'\n >\n> & {\n baudRate: BaudRate;\n bufferSize: SerialPortBufferSize;\n filters?: SerialPortFilter[];\n terminalBuffer: ResolvedTerminalBufferOptions;\n lineBuffer: ResolvedLineBufferOptions;\n};\n\n/**\n * Default values applied to omitted {@link SerialSessionOptions} fields.\n *\n * Nested buffer defaults are owned by {@link resolveTerminalBufferOptions} and\n * {@link resolveLineBufferOptions}; this object is the single session-level\n * snapshot used by {@link resolveSerialSessionOptions}.\n *\n * @internal\n */\nexport const DEFAULT_SERIAL_SESSION_OPTIONS = {\n baudRate: brandBaudRate(9600),\n dataBits: 8,\n stopBits: 1,\n parity: 'none',\n bufferSize: brandSerialPortBufferSize(255),\n flowControl: 'none',\n terminalBuffer: resolveTerminalBufferOptions(),\n lineBuffer: resolveLineBufferOptions(),\n} satisfies ResolvedSerialSessionOptions;\n\n/** Resolved W3C connection fields for {@link ResolvedSerialSessionOptions}. */\nexport type ResolvedSerialSessionConnectionOptions = Required<\n Omit<SerialConnectionOptions, 'baudRate' | 'bufferSize'>\n> & {\n baudRate: BaudRate;\n bufferSize: SerialPortBufferSize;\n};\n\n/**\n * Merge and validate W3C connection fields from {@link SerialSessionOptions}.\n *\n * @throws {@link SerialError} with {@link SerialErrorCode.INVALID_CONNECTION_OPTIONS}\n * when `baudRate` or `bufferSize` are out of range.\n */\nexport function resolveConnectionOptions(\n options?: Partial<SerialConnectionOptions>,\n): ResolvedSerialSessionConnectionOptions {\n const merged = {\n baudRate: DEFAULT_SERIAL_SESSION_OPTIONS.baudRate,\n dataBits: DEFAULT_SERIAL_SESSION_OPTIONS.dataBits,\n stopBits: DEFAULT_SERIAL_SESSION_OPTIONS.stopBits,\n parity: DEFAULT_SERIAL_SESSION_OPTIONS.parity,\n bufferSize: DEFAULT_SERIAL_SESSION_OPTIONS.bufferSize,\n flowControl: DEFAULT_SERIAL_SESSION_OPTIONS.flowControl,\n ...options,\n };\n\n const baudRate =\n merged.baudRate ?? DEFAULT_SERIAL_SESSION_OPTIONS.baudRate;\n const bufferSize =\n merged.bufferSize ?? DEFAULT_SERIAL_SESSION_OPTIONS.bufferSize;\n\n if (!Number.isSafeInteger(baudRate) || baudRate <= 0) {\n throw new SerialError(\n SerialErrorCode.INVALID_CONNECTION_OPTIONS,\n `Invalid baudRate: ${baudRate}. Must be a safe integer > 0.`,\n undefined,\n {\n field: 'baudRate',\n value: baudRate,\n constraint: 'positive-safe-integer',\n },\n );\n }\n\n if (!Number.isSafeInteger(bufferSize) || bufferSize <= 0) {\n throw new SerialError(\n SerialErrorCode.INVALID_CONNECTION_OPTIONS,\n `Invalid bufferSize: ${bufferSize}. Must be a safe integer > 0.`,\n undefined,\n {\n field: 'bufferSize',\n value: bufferSize,\n constraint: 'positive-safe-integer',\n },\n );\n }\n\n return {\n dataBits: merged.dataBits ?? DEFAULT_SERIAL_SESSION_OPTIONS.dataBits,\n stopBits: merged.stopBits ?? DEFAULT_SERIAL_SESSION_OPTIONS.stopBits,\n parity: merged.parity ?? DEFAULT_SERIAL_SESSION_OPTIONS.parity,\n flowControl:\n merged.flowControl ?? DEFAULT_SERIAL_SESSION_OPTIONS.flowControl,\n baudRate: brandBaudRate(baudRate),\n bufferSize: brandSerialPortBufferSize(bufferSize),\n };\n}\n\n/**\n * Merge and validate {@link SerialSessionOptions} into a fully resolved\n * options object for internal session use.\n *\n * Defaults and validation for nested buffers live next to their option types\n * ({@link resolveTerminalBufferOptions}, {@link resolveLineBufferOptions}).\n * This function is the single entry point that assembles connection + feature\n * options for a session.\n *\n * @throws {@link SerialError} when option values are out of range:\n * {@link SerialErrorCode.INVALID_CONNECTION_OPTIONS},\n * {@link SerialErrorCode.INVALID_FILTER_OPTIONS},\n * {@link SerialErrorCode.INVALID_TERMINAL_BUFFER_OPTIONS}, or\n * {@link SerialErrorCode.INVALID_LINE_BUFFER_OPTIONS}.\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/403 | Issue #403}\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/488 | Issue #488}\n */\nexport function resolveSerialSessionOptions(\n options?: SerialSessionOptions,\n): ResolvedSerialSessionOptions {\n const connection = resolveConnectionOptions(options);\n const filters = validateSerialPortFilters(options?.filters);\n\n return {\n ...connection,\n ...(filters !== undefined ? { filters } : {}),\n terminalBuffer: resolveTerminalBufferOptions(options?.terminalBuffer),\n lineBuffer: resolveLineBufferOptions(options?.lineBuffer),\n };\n}\n", "import {\n type ConnectedSessionState,\n type SerialSessionState,\n SerialSessionStatus,\n} from './serial-session-state';\n\n/**\n * Type predicate for {@link ConnectedSessionState}.\n *\n * Use with RxJS `filter()` to preserve discriminated union narrowing in\n * pipelines so `portInfo` and other connected-only fields stay typed.\n *\n * @see {@link https://github.com/gurezo/web-serial-rxjs/issues/436 | Issue #436}\n */\nexport function isConnectedSessionState(\n state: SerialSessionState,\n): state is ConnectedSessionState {\n return state.status === SerialSessionStatus.Connected;\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": ["Observable", "Subject", "Observable", "Observable", "Observable", "Subject", "Observable"]
7
7
  }
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * @deprecated Not part of the canonical public API. Define a local helper in
5
5
  * application code, or use `switch (state.status)` with
6
- * {@link SerialSessionStatus}. Will be removed from public exports in the
7
- * next major version.
6
+ * {@link SerialSessionStatus}. Still available in v4; will be removed from
7
+ * public exports in a future major (v5+).
8
8
  * @internal
9
9
  */
10
10
  export declare function assertNever(value: never): never;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gurezo/web-serial-rxjs",
3
- "version": "4.0.3",
3
+ "version": "4.0.4",
4
4
  "description": "RxJS-based utilities for the Web Serial API, usable from Angular, React, Svelte, and Vanilla JavaScript/TypeScript.",
5
5
  "author": "Akihiko Kigure <akihiko.kigure@gmail.com>",
6
6
  "license": "MIT",
@@ -31,6 +31,7 @@
31
31
  "clean": "rm -rf dist",
32
32
  "prepublishOnly": "pnpm run build"
33
33
  },
34
+ "sideEffects": false,
34
35
  "dependencies": {},
35
36
  "peerDependencies": {
36
37
  "rxjs": "^7.8.0"