@fishka/seqio 0.3.0 → 0.5.0

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.
@@ -1,3 +1,5 @@
1
+ declare function tagNameFromInt32(n: number): string;
2
+
1
3
  /**
2
4
  * Low-level ABIF file model: a flat list of directory entries with raw
3
5
  * payloads. Lossless round-trip is preserved: every entry (including unknown
@@ -7,10 +9,35 @@
7
9
  interface AbifFile {
8
10
  /** ABIF version, e.g. 101 for v1.01. */
9
11
  version: number;
12
+ /** The root directory (tdir) header, exactly as read — including any directory padding it declares. */
13
+ tdir: AbifDirectory;
10
14
  /** Directory entries in the original on-disk order. */
11
15
  entries: AbifEntry[];
12
16
  /** MacBinary preamble offset (128 if present, 0 otherwise). Preserved for diagnostics. */
13
17
  macBinaryOffset: number;
18
+ /**
19
+ * The 128-byte MacBinary preamble, verbatim — present only when {@link macBinaryOffset} is 128.
20
+ * Exposed so a raw reader can reproduce the wrapper, not just note that it existed.
21
+ */
22
+ macBinaryHeader?: Uint8Array;
23
+ /**
24
+ * Reserved header bytes [34..127] (94 bytes), verbatim. Usually zeros; exposed so a raw reader
25
+ * keeps the entire 128-byte header, not just magic/version/tdir.
26
+ */
27
+ headerReserved: Uint8Array;
28
+ /**
29
+ * Physical byte ranges not covered by the header, directory, or any entry payload — orphaned
30
+ * blocks left by editing tools, trailing padding, etc. Empty for a tightly-packed file. Exposed
31
+ * so a raw reader accounts for every byte; the chromatogram never depends on these.
32
+ */
33
+ unreferencedRanges: AbifByteRange[];
34
+ }
35
+ /** A contiguous run of file bytes at an absolute offset — used for {@link AbifFile.unreferencedRanges}. */
36
+ interface AbifByteRange {
37
+ /** Absolute offset from the start of the file (includes any MacBinary preamble). */
38
+ offset: number;
39
+ /** The bytes in this range, verbatim. `bytes.length` is the range length. */
40
+ bytes: Uint8Array;
14
41
  }
15
42
  interface AbifEntry {
16
43
  /** 4-character tag name, e.g. "DATA", "PBAS". */
@@ -23,12 +50,75 @@ interface AbifEntry {
23
50
  elementType: number;
24
51
  /** Bytes per element. */
25
52
  elementSize: number;
26
- /** Number of elements. */
53
+ /** Number of elements (reconciled: clamped to what the payload holds). */
27
54
  elementCount: number;
28
- /** Raw payload bytes (length === elementCount * elementSize). */
55
+ /**
56
+ * Raw payload bytes. Length === the on-disk `dataSize` (the authoritative field), which for
57
+ * user/opaque types may differ from `elementCount * elementSize`.
58
+ */
29
59
  payload: Uint8Array;
30
60
  /** Opaque dataHandle field, usually 0. Preserved for round-trip. */
31
61
  dataHandle: number;
62
+ /**
63
+ * The directory fields exactly as they were on disk — present only when this entry was read from a
64
+ * file (absent for entries synthesized by setters/writeAbif). Lets consumers inspect the real record
65
+ * without our reconciliation, and lets writeAbif() reuse this entry's original directory slot/offset
66
+ * verbatim on an unmodified round-trip. Mutate entries only through upsertEntry() — it clears this
67
+ * field, since a hand-edited entry (payload/elementType/elementSize/elementCount set directly) can no
68
+ * longer be reproduced from its stale on-disk shape.
69
+ */
70
+ raw?: AbifEntryRaw;
71
+ }
72
+ /** A directory entry's on-disk fields, verbatim, before any reconciliation applied by {@link readAbif}. */
73
+ interface AbifEntryRaw {
74
+ /** `elementCount` (numElements) as written on disk. */
75
+ elementCount: number;
76
+ /** `dataSize` field as written on disk, in bytes. */
77
+ dataSize: number;
78
+ /** External payload offset relative to the ABIF start, or -1 when the payload is inline. */
79
+ dataOffset: number;
80
+ /** Whether the payload was stored inline (declared dataSize ≤ 4). */
81
+ inline: boolean;
82
+ /**
83
+ * The 4 raw bytes of the dataOffset/data slot, verbatim. For an external entry these are the
84
+ * big-endian offset; for an inline entry they are the value bytes plus any padding/stale bytes
85
+ * beyond `dataSize` — exposed so a raw reader loses no structure.
86
+ */
87
+ dataOffsetBytes: Uint8Array;
88
+ }
89
+ /**
90
+ * The root directory (`tdir`) header — the header's own directory entry, describing the directory
91
+ * block. Its fields are exposed like any other raw entry so a raw reader never loses them, even when
92
+ * `rawEntryCount` desyncs from `dataSize`.
93
+ */
94
+ interface AbifDirectory {
95
+ /** Effective number of entries actually read — equals `entries.length`. */
96
+ entryCount: number;
97
+ /** tdir `numElements` verbatim, before reconciliation; the authoritative on-disk entry count. */
98
+ rawEntryCount: number;
99
+ /** tdir `elementType` (1023). */
100
+ elementType: number;
101
+ /** tdir `tagNumber` (usually 1). */
102
+ tagNumber: number;
103
+ /** Bytes per directory entry (always 28). */
104
+ entrySize: number;
105
+ /**
106
+ * Directory block size in bytes, from the tdir `dataSize` field. May exceed `entryCount * 28`
107
+ * when the file carries directory padding / extra bytes.
108
+ */
109
+ dataSize: number;
110
+ /** File offset (relative to the ABIF start) where the directory block begins. */
111
+ dataOffset: number;
112
+ /** The 4 raw bytes of the tdir's dataOffset field, verbatim. */
113
+ dataOffsetBytes: Uint8Array;
114
+ /** tdir `dataHandle` field, usually 0. */
115
+ dataHandle: number;
116
+ /**
117
+ * The raw directory-padding bytes: everything from the end of the last entry to the end of the
118
+ * directory block (`dataSize - entryCount*28`). Usually zeros; exposed so a raw reader keeps the
119
+ * whole directory structure, not just the entries.
120
+ */
121
+ paddingBytes: Uint8Array;
32
122
  }
33
123
  interface ChannelSignals {
34
124
  A: number[];
@@ -36,6 +126,8 @@ interface ChannelSignals {
36
126
  G: number[];
37
127
  T: number[];
38
128
  }
129
+ /** Spec-defined role of a DATA<n> tag — see {@link dataChannelRole}. */
130
+ type AbifDataChannelRole = 'trace' | 'telemetry' | 'other';
39
131
  /** Per-base chromatogram view. */
40
132
  interface Chromatogram {
41
133
  /** Per-base peak position in sample points (from PLOC). */
@@ -47,7 +139,39 @@ interface Chromatogram {
47
139
  /** Average peak spacing (samples per base), from SPAC. */
48
140
  samplingRate?: number;
49
141
  }
50
- /** Basecalls extracted from PBAS/PCON/PLOC under the chosen version. */
142
+ /**
143
+ * One basecall version exactly as the file stores it. ABIF numbers the
144
+ * PBAS/PCON/PLOC tags by version, and the spec fixes what each number means:
145
+ *
146
+ * - version 2 (`role: 'called'`) — the sequence as produced by the basecaller.
147
+ * - version 1 (`role: 'edited'`) — the sequence after user hand-editing.
148
+ * - any other version (`role: 'unknown'`) — the spec only defines 1 and 2, so
149
+ * a vendor/future PBAS3+ is surfaced without a claimed role.
150
+ *
151
+ * A file may carry either or both, and the two can differ in content and even in
152
+ * length (edits insert/delete bases). {@link parseAbif} exposes every version it
153
+ * finds via {@link ParsedAbif.baseCallVariants} — picking which one to show or
154
+ * export is the consumer's call, not the parser's.
155
+ */
156
+ type AbifBaseCallRole = 'called' | 'edited' | 'unknown';
157
+ interface AbifBaseCallVariant {
158
+ /** Tag number of the PBAS/PCON/PLOC this variant came from (1, 2, or a vendor number). */
159
+ version: number;
160
+ /** Spec-defined role by tag number: 2 = basecaller-called, 1 = user-edited, else unknown. */
161
+ role: AbifBaseCallRole;
162
+ /** Bases from PBAS<version> as stored — case preserved, trailing NULs stripped, not normalized. */
163
+ sequence: string;
164
+ /** Per-base Q-scores from PCON<version>; `[]` when that version has no PCON. */
165
+ confidences: number[];
166
+ /** Per-base peak positions (sample indices) from PLOC<version>; `[]` when absent. */
167
+ positions: number[];
168
+ }
169
+ /**
170
+ * Convenience pointer to the preferred basecall version — the called one (PBAS2)
171
+ * when present, else whatever single version the file has. This is a spec-role
172
+ * choice (called over edited), not a quality judgement; see
173
+ * {@link ParsedAbif.baseCallVariants} for every version the file actually carries.
174
+ */
51
175
  interface AbifBaseCalls {
52
176
  /** Called bases (uppercase). */
53
177
  sequence: string;
@@ -68,34 +192,37 @@ interface AbifMetadata {
68
192
  runDate?: string;
69
193
  runTime?: string;
70
194
  samplingRate?: number;
195
+ /**
196
+ * Value of the RevC1 flag: whether the file declares its sequence already
197
+ * reverse-complemented. undefined when the tag is absent. Reported as-is — the
198
+ * consumer decides what to do with it.
199
+ */
200
+ reverseComplemented?: boolean;
71
201
  comments: string[];
72
202
  }
73
203
  /**
74
- * Full chromatogram bundle: all DATA channels, plus FWO_-aware A/C/G/T views.
75
- *
76
- * The two block views are named after their on-disk tag number ranges. Their
77
- * semantic content varies by instrument:
78
- *
79
- * - DATA1..4 — on modern KB-basecaller-aware instruments (3130, 3500,
80
- * 3730) these are POST-PROCESSED traces (mobility-corrected,
81
- * baseline-subtracted, color-separated). Basecallers operate
82
- * on these. On older simpler instruments DATA1..4 IS the raw
83
- * fluorescence.
84
- * - DATA9..12 RAW fluorescence on instruments that produce both blocks.
85
- * Absent on instruments that only write DATA1..8.
86
- *
87
- * Use {@link hasProcessedTraces} (data9To12 present) to detect whether
88
- * DATA1..4 has already been processed and bypass your own baseline/color
89
- * steps accordingly.
204
+ * All DATA channels plus FWO_-aware A/C/G/T views of the two dye-trace blocks.
205
+ *
206
+ * The block views are named after their on-disk tag ranges — nothing more. Which
207
+ * block is "raw" and which is "analyzed / processed" is an instrument-and-tool
208
+ * convention that the ABIF file does NOT state, so this parser refuses to label
209
+ * it. The consumer that needs to tell them apart has the primitives to decide:
210
+ * the per-block sample counts (channel lengths) and the basecall peak positions
211
+ * ({@link AbifBaseCalls.positions}); e.g. positions that overflow one block's
212
+ * length can only belong to the other.
213
+ *
214
+ * Per the ABIF spec, DATA5..8 are instrument telemetry (voltage / current / power
215
+ * / temperature), NOT dye traces — see {@link dataChannelRole}. They live in
216
+ * {@link dataChannels} but are excluded from the A/C/G/T views.
90
217
  */
91
218
  interface AbifChromatogramBundle {
92
219
  /** FWO_ value (e.g. "GATC"). */
93
220
  baseOrder: string;
94
- /** All DATA tags by tagNumber → trace. */
221
+ /** All DATA tags by tagNumber → trace (dye traces AND telemetry). */
95
222
  dataChannels: Record<number, number[]>;
96
- /** DATA1..4 mapped to A/C/G/T by FWO_. Post-processed on newer instruments, raw on older. */
223
+ /** DATA1..4 mapped to A/C/G/T by FWO_. */
97
224
  data1To4: ChannelSignals;
98
- /** DATA9..12 mapped to A/C/G/T by FWO_. Raw fluorescence; absent on older instruments. */
225
+ /** DATA9..12 mapped to A/C/G/T by FWO_; empty when the file has no 9..12 block. */
99
226
  data9To12: ChannelSignals;
100
227
  }
101
228
  /** Rich result from {@link parseAbif} — everything the typical viewer needs. */
@@ -107,7 +234,10 @@ interface ParsedAbif {
107
234
  dirEntryCount: number;
108
235
  metadata: AbifMetadata;
109
236
  chromatogram: AbifChromatogramBundle;
237
+ /** Preferred basecall version (called over edited); convenience over {@link baseCallVariants}. */
110
238
  baseCalls?: AbifBaseCalls;
239
+ /** Every basecall version the file carries (called and/or edited), in version order. */
240
+ baseCallVariants: AbifBaseCallVariant[];
111
241
  /** All directory entries with their decoded payloads (best-effort by type). */
112
242
  entries: AbifDirEntry[];
113
243
  }
@@ -119,9 +249,15 @@ interface AbifDirEntry {
119
249
  elementType: number;
120
250
  elementTypeName: string;
121
251
  elementSize: number;
252
+ /** Reconciled element count (clamped to the declared dataSize when it was smaller). */
122
253
  elementCount: number;
254
+ /** `numElements` exactly as written on disk, before reconciliation. */
255
+ rawElementCount: number;
256
+ /** `dataSize` as written on disk, in bytes (not recomputed). */
123
257
  dataSize: number;
258
+ /** External payload offset relative to the ABIF start, or -1 when the payload is inline. */
124
259
  dataOffset: number;
260
+ /** Whether the payload was stored inline (declared dataSize ≤ 4). */
125
261
  inline: boolean;
126
262
  decoded: AbifDecodedValue;
127
263
  preview: string;
@@ -161,14 +297,13 @@ type AbifDecodedValue = {
161
297
  value: Uint8Array;
162
298
  };
163
299
 
164
- declare function tagNameFromInt32(n: number): string;
165
-
166
300
  /**
167
- * Low-level ABIF reader/writer.
168
- *
169
- * Round-trip without loss: every directory entry (including unknown vendor
170
- * tags) is preserved as a raw payload (Uint8Array). The high-level layer
171
- * (view.ts, parser.ts) provides typed access on top of this.
301
+ * Raw layer: the ABIF byte<->struct codec. Every directory entry (including unknown
302
+ * vendor tags) is preserved as a raw payload (Uint8Array), and writeAbif() reproduces
303
+ * the original bytes exactly for an unmodified round-trip (see its own doc comment).
304
+ * The domain layer (view.ts, setters.ts, parser.ts, abif-op-*.ts) provides typed,
305
+ * format-aware access on top of this — this file knows nothing about what any tag
306
+ * means.
172
307
  *
173
308
  * ABIF file layout:
174
309
  * [0..3] "ABIF" magic
@@ -184,12 +319,14 @@ declare function tagNameFromInt32(n: number): string;
184
319
  * 8 2 elementType (int16)
185
320
  * 10 2 elementSize (int16)
186
321
  * 12 4 elementCount (int32)
187
- * 16 4 dataSize (int32; total payload bytes; spec says count*size)
322
+ * 16 4 dataSize (int32; total payload bytes authoritative; for
323
+ * well-formed entries equals count*size, but user/
324
+ * opaque types may differ, so we read by dataSize)
188
325
  * 20 4 dataOffset (int32) OR inline data if dataSize <= 4
189
326
  * 24 4 dataHandle (int32; usually 0)
190
327
  *
191
- * Inline rule (per ABIF spec): when count*size <= 4 the payload bytes are
192
- * stored directly in the dataOffset field (left-aligned, padded to 4 bytes).
328
+ * Inline rule (per ABIF spec): when the declared dataSize <= 4 the payload bytes
329
+ * are stored directly in the dataOffset field (left-aligned, padded to 4 bytes).
193
330
  *
194
331
  * MacBinary preamble: some ABIF files (older Mac-origin) start with a 128-byte
195
332
  * MacBinary header before the actual ABIF magic. We detect and skip it.
@@ -198,7 +335,13 @@ declare function tagNameFromInt32(n: number): string;
198
335
  declare const HEADER_SIZE = 128;
199
336
  declare const ENTRY_SIZE = 28;
200
337
  /**
201
- * Parse an ABIF file from raw bytes.
338
+ * Parse an ABIF file from raw bytes into its verbatim directory structure.
339
+ *
340
+ * This is the RAW source of truth: it reads the tdir header and every entry as
341
+ * stored — real dataSize, dataOffset, numElements (see `entry.raw`), the 4 inline
342
+ * bytes, and the tdir/directory metadata — and interprets nothing. For a
343
+ * high-level, opinionated view (typed channels, basecalls, metadata) layer
344
+ * {@link parseAbif} on top.
202
345
  *
203
346
  * Accepts Uint8Array (works in Node and the browser). Node's Buffer extends
204
347
  * Uint8Array, so `readAbif(buffer)` and `readAbif(new Uint8Array(arrayBuffer))`
@@ -211,15 +354,35 @@ declare function readAbif(bytes: Uint8Array): AbifFile;
211
354
  /**
212
355
  * Serialize an AbifFile back to a Uint8Array.
213
356
  *
214
- * Layout produced: header (128 B) + directory (N*28 B) + payload block.
215
- * External payloads are packed tightly in entry order. Inline payloads
216
- * (count*size <= 4) live entirely inside their directory entry.
357
+ * Byte-exact on an unmodified round-trip for well-formed records and the tolerated
358
+ * count/dataSize desyncs readAbif() accepts: readAbif() followed right back by
359
+ * writeAbif(), with nothing touched in between, reproduces the original bytes
360
+ * exactly (a MacBinary-wrapped input round-trips its ABIF payload byte-exact, but
361
+ * the preamble itself is never re-added — see the AGENTS.md note on why). The one
362
+ * exception is a genuinely invalid negative `dataSize`/`numElements` field (readAbif
363
+ * tolerates these defensively, falling back to a computed size, but never treats them
364
+ * as verbatim-eligible on write — see the byteLength check below) — such an entry
365
+ * round-trips meaning-lossless (correct payload/content) but its declared dataSize
366
+ * field gets normalized to the real payload length rather than reproducing the
367
+ * original garbage value.
217
368
  *
218
- * The output is not bit-identical to the input if the original had a different
219
- * physical layout (e.g. payloads before directory), but the *meaning*
220
- * round-trips: readAbif(writeAbif(f)) reproduces the same entries structurally.
369
+ * This works by keeping every entry whose payload size hasn't changed since it was
370
+ * read (`entry.raw` present upsertEntry() clears it on any mutation — and
371
+ * `payload.byteLength === raw.dataSize`) at its exact original directory slot and
372
+ * file offset, writing the *original* on-disk numElements/dataSize/offset-or-inline-
373
+ * padding instead of the reconciled/current ones. A new or resized entry is appended
374
+ * past the highest byte offset still known to hold preserved content (every verbatim
375
+ * entry's span, the directory, and any unreferenced range); only its own directory
376
+ * record changes, so every other entry's bytes stay untouched. A resized/removed
377
+ * entry's *old* span is not itself reserved — nothing points at it anymore, so it is
378
+ * free to be reused by whatever gets appended next; the output is not padded out to
379
+ * the original file's true end.
221
380
  *
222
- * MacBinary preamble is not preserved by writeAbif.
381
+ * The directory is kept at its original file offset as long as the entry count
382
+ * didn't grow (removing entries just shortens it in place; the original directory
383
+ * padding is only reused when the count is exactly unchanged, since a shorter
384
+ * directory can't reuse padding sized for more entries). Only a growing entry count
385
+ * relocates the whole directory to freshly appended space.
223
386
  */
224
387
  declare function writeAbif(file: AbifFile): Uint8Array;
225
388
  /** Find an entry by name+number, or undefined. */
@@ -229,6 +392,11 @@ declare function findEntries(file: AbifFile, name: string): AbifEntry[];
229
392
  /**
230
393
  * Replace the payload of an existing entry, or append a new one.
231
394
  * elementType/elementSize/elementCount must be supplied for new entries.
395
+ *
396
+ * This is the required way to mutate an entry read from a file — it clears `.raw`
397
+ * on replacement so writeAbif() never reuses a now-stale on-disk shape. Setting
398
+ * `entry.payload`/`elementType`/`elementSize`/`elementCount` directly instead
399
+ * leaves `.raw` behind and can corrupt the written directory record.
232
400
  */
233
401
  declare function upsertEntry(file: AbifFile, name: string, number: number, payload: Uint8Array, defaults: {
234
402
  elementType: number;
@@ -237,78 +405,36 @@ declare function upsertEntry(file: AbifFile, name: string, number: number, paylo
237
405
  }): void;
238
406
 
239
407
  /**
240
- * High-level typed view over an AbifFile.
241
- *
242
- * Reading helpers that interpret the raw payloads of well-known tags. For
243
- * mutation helpers (setSequence, setConfidences, ...) see ./setters.
244
- */
245
-
246
- /** Get DATA<n> as a signed-int16 array, or undefined. */
247
- declare function getDataChannel(file: AbifFile, n: number): Int16Array | undefined;
248
- /**
249
- * Dye order, 4 ASCII chars (e.g. "GATC"). Defaults to "GATC" if FWO_ absent.
250
- *
251
- * GATC is the dye order produced by modern ABI 3730/3500 instruments and the
252
- * dominant default in the wild. Older "ACGT" defaults caused channel
253
- * mis-mapping on files where FWO_ is missing or malformed.
254
- */
255
- declare function getFwo(file: AbifFile): string;
256
- /**
257
- * True when the file carries BOTH DATA1..4 and DATA9..12. Newer ABI-style
258
- * instruments produce both: DATA1..4 is post-processed (mobility-corrected,
259
- * baseline-subtracted, color-separated) and DATA9..12 is raw fluorescence.
260
- * Older instruments produce only DATA1..4 (which IS the raw signal).
261
- *
262
- * Callers that re-process traces (basecallers) should detect this and bypass
263
- * their own baseline-subtraction / color-matrix steps when DATA1..4 is
264
- * already cleaned.
265
- */
266
- declare function hasProcessedTraces(file: AbifFile): boolean;
267
- /**
268
- * Map from base letter ("A"|"C"|"G"|"T") to the DATA tag number that holds
269
- * its RAW fluorescence channel.
270
- *
271
- * ABIF stores up to 12 DATA tags. KB-basecaller-aware instruments (3130, 3500,
272
- * ...) use DATA1..4 for processed traces and DATA9..12 for raw. Older / simpler
273
- * instruments produce only DATA1..8 where DATA1..4 ARE the raw traces.
274
- *
275
- * This helper always returns the DATA1..4 mapping (the convention used by
276
- * basecallers operating on raw signal). Use {@link hasProcessedTraces} to
277
- * detect whether DATA1..4 has been pre-processed and bypass baseline/color
278
- * steps accordingly.
408
+ * High-level "parse everything" wrapper around the raw + view layers.
279
409
  *
280
- * The dye-to-channel order within each block is given by FWO_. If
281
- * FWO_="GATC" then G→1, A→2, T→3, C→4.
282
- */
283
- declare function getRawChannelMap(file: AbifFile): Record<'A' | 'C' | 'G' | 'T', number>;
284
- /** Get PBAS sequence (prefer PBAS2 over PBAS1). undefined if neither present. */
285
- declare function getSequence(file: AbifFile): string | undefined;
286
- /**
287
- * Get per-base Phred-like quality scores (0..255 byte values, typically 0..60
288
- * for Sanger). Prefers PCON2 over PCON1.
410
+ * Returns a single ParsedAbif object with metadata, the FWO_-aware
411
+ * DATA1..4 / DATA9..12 channel split, basecalls, and decoded directory entries
412
+ * for diagnostics. Mirrors what typical viewers (chromatogram UIs, sample
413
+ * inspectors) need from a single file.
289
414
  *
290
- * PCON is declared in the ABIF spec as elementType=2 (char) but the byte
291
- * values ARE the Q-scores we read raw bytes regardless of declared type.
415
+ * For round-trip authoring (basecallers), prefer {@link readAbif} +
416
+ * setters from ./setters which operate directly on the raw AbifFile.
292
417
  */
293
- declare function getConfidences(file: AbifFile): number[] | undefined;
418
+
294
419
  /**
295
- * Get peak scan positions (one per base). Prefers PLOC2 over PLOC1.
420
+ * Parse an ABIF file into a high-level view: metadata, channels, basecalls,
421
+ * and decoded directory entries.
296
422
  *
297
- * Read as UNSIGNED int16: PLOC indexes into the sample trace and is always
298
- * non-negative, but values > 32767 (long traces) would wrap to negative
299
- * if interpreted as signed.
300
- */
301
- declare function getPositions(file: AbifFile): number[] | undefined;
302
- /**
303
- * Get average peak spacing (samples per base), from SPAC/1.
423
+ * This is the INTERPRETING layer it makes convenience choices (FWO_ "GATC"
424
+ * fallback, derived samplingRate, preferred/upper-cased PBAS2 baseCalls). It is
425
+ * not the raw structural truth: for that use {@link readAbif}, which reads the
426
+ * directory verbatim (raw dataSize/offset/counts, tdir, inline bytes) and makes
427
+ * no interpretation. `parseAbif` builds on top of it.
304
428
  *
305
- * ABIF spec defines SPAC as float32 (elementType=7); some legacy files
306
- * mislabel it as long (type=5). Both are 4 bytes and on disk the payload
307
- * is always a 32-bit float, so we read as float regardless of declared type.
308
- *
309
- * Returns undefined if SPAC is absent or non-positive.
429
+ * Accepts ArrayBuffer or Uint8Array (Buffer in Node works too — it extends
430
+ * Uint8Array). The `fileName` argument is informational only; it's preserved
431
+ * in the result.
310
432
  */
311
- declare function getSamplingRate(file: AbifFile): number | undefined;
433
+ declare function parseAbif(input: ArrayBuffer | Uint8Array, fileName?: string): ParsedAbif;
434
+ /** True when any channel has at least one signal value. */
435
+ declare function hasSignals(s: ChannelSignals): boolean;
436
+ /** Length of the longest channel in a ChannelSignals bundle. */
437
+ declare function channelMaxLength(s: ChannelSignals): number;
312
438
 
313
439
  /**
314
440
  * Mutation helpers for AbifFile: write PBAS/PCON/PLOC/SPAC and ensure DATA9..12
@@ -359,29 +485,90 @@ declare function averagePeakSpacing(positions: ArrayLike<number>): number;
359
485
  declare function ensureRawDataChannels(file: AbifFile): void;
360
486
 
361
487
  /**
362
- * High-level "parse everything" wrapper around the raw + view layers.
363
- *
364
- * Returns a single ParsedAbif object with metadata, the FWO_-aware
365
- * DATA1..4 / DATA9..12 channel split, basecalls, and decoded directory entries
366
- * for diagnostics. Mirrors what typical viewers (chromatogram UIs, sample
367
- * inspectors) need from a single file.
488
+ * High-level typed view over an AbifFile.
368
489
  *
369
- * For round-trip authoring (basecallers), prefer {@link readAbif} +
370
- * setters from ./setters which operate directly on the raw AbifFile.
490
+ * Reading helpers that interpret the raw payloads of well-known tags. For
491
+ * mutation helpers (setSequence, setConfidences, ...) see ./setters.
371
492
  */
372
493
 
494
+ /** Get DATA<n> as a signed-int16 array, or undefined. */
495
+ declare function getDataChannel(file: AbifFile, n: number): Int16Array | undefined;
373
496
  /**
374
- * Parse an ABIF file into a high-level view: metadata, channels, basecalls,
375
- * and decoded directory entries.
497
+ * Dye order, 4 ASCII chars (e.g. "GATC"). Defaults to "GATC" if FWO_ absent.
376
498
  *
377
- * Accepts ArrayBuffer or Uint8Array (Buffer in Node works too it extends
378
- * Uint8Array). The `fileName` argument is informational only; it's preserved
379
- * in the result.
499
+ * GATC is the dye order produced by modern ABI 3730/3500 instruments and the
500
+ * dominant default in the wild. Older "ACGT" defaults caused channel
501
+ * mis-mapping on files where FWO_ is missing or malformed.
380
502
  */
381
- declare function parseAbif(input: ArrayBuffer | Uint8Array, fileName?: string): ParsedAbif;
382
- /** True when any channel has at least one signal value. */
383
- declare function hasSignals(s: ChannelSignals): boolean;
384
- /** Length of the longest channel in a ChannelSignals bundle. */
385
- declare function channelMaxLength(s: ChannelSignals): number;
503
+ declare function getFwo(file: AbifFile): string;
504
+ /**
505
+ * Whether an FWO_ string is a genuine dye order: a permutation of A/C/G/T with all
506
+ * four bases distinct. `"GATC"` passes; `"AAAA"` (regex-valid but degenerate) does
507
+ * not mapping four channels through it would collapse them onto one base.
508
+ */
509
+ declare function isFwoPermutation(fwo: string): boolean;
510
+ /**
511
+ * Fact: the file carries a second dye-trace block, DATA9..12 (all four tags), in
512
+ * addition to DATA1..4. Reports presence only — it does NOT say which block is
513
+ * raw and which is analyzed/processed; that convention isn't stored in the file.
514
+ */
515
+ declare function hasData9To12Block(file: AbifFile): boolean;
516
+ /**
517
+ * Spec-defined role of a DATA<n> tag, limited to the numbers the ABIF spec names:
518
+ *
519
+ * - `'trace'` — dye-signal channels: DATA1..4 (raw dyes 1-4), DATA9..12
520
+ * (analyzed dyes 1-4), and the two optional 5th-dye blocks
521
+ * DATA105 (raw dye 5) and DATA205 (analyzed dye 5).
522
+ * - `'telemetry'` — DATA5..8: instrument run telemetry (voltage, current,
523
+ * power, temperature), one value per scan — NOT dye signal.
524
+ * - `'other'` — any other DATA number. The spec does not enumerate higher
525
+ * extra-dye tags (106/206/…), so we don't claim a role for them.
526
+ *
527
+ * A fact from the ABIF specification, not an inference about content.
528
+ */
529
+ declare function dataChannelRole(n: number): AbifDataChannelRole;
530
+ /**
531
+ * Whether the file declares its sequence already reverse-complemented, from the
532
+ * RevC1 flag (int16, non-zero = true). undefined when RevC1 is absent. Reported
533
+ * as-is; the consumer decides how to act on it.
534
+ */
535
+ declare function getReverseComplemented(file: AbifFile): boolean | undefined;
536
+ /**
537
+ * Map from base letter ("A"|"C"|"G"|"T") to the DATA1..4 tag number that holds
538
+ * its channel, using the dye order declared by FWO_. If FWO_="GATC" then
539
+ * G→1, A→2, T→3, C→4.
540
+ *
541
+ * This follows the file's own FWO_ declaration for the DATA1..4 block; it makes
542
+ * no claim about whether that block is raw or processed.
543
+ */
544
+ declare function getChannelMap(file: AbifFile): Record<'A' | 'C' | 'G' | 'T', number>;
545
+ /** Get PBAS sequence (prefer PBAS2 over PBAS1). undefined if neither present. */
546
+ declare function getSequence(file: AbifFile): string | undefined;
547
+ /**
548
+ * Get per-base Phred-like quality scores (0..255 byte values, typically 0..60
549
+ * for Sanger). Prefers PCON2 over PCON1.
550
+ *
551
+ * PCON is declared in the ABIF spec as elementType=2 (char) but the byte
552
+ * values ARE the Q-scores — we read raw bytes regardless of declared type.
553
+ */
554
+ declare function getConfidences(file: AbifFile): number[] | undefined;
555
+ /**
556
+ * Get peak scan positions (one per base). Prefers PLOC2 over PLOC1.
557
+ *
558
+ * Read as UNSIGNED int16: PLOC indexes into the sample trace and is always
559
+ * non-negative, but values > 32767 (long traces) would wrap to negative
560
+ * if interpreted as signed.
561
+ */
562
+ declare function getPositions(file: AbifFile): number[] | undefined;
563
+ /**
564
+ * Get average peak spacing (samples per base), from SPAC/1.
565
+ *
566
+ * ABIF spec defines SPAC as float32 (elementType=7); some legacy files
567
+ * mislabel it as long (type=5). Both are 4 bytes and on disk the payload
568
+ * is always a 32-bit float, so we read as float regardless of declared type.
569
+ *
570
+ * Returns undefined if SPAC is absent or non-positive.
571
+ */
572
+ declare function getSamplingRate(file: AbifFile): number | undefined;
386
573
 
387
- export { type AbifBaseCalls, type AbifChromatogramBundle, type AbifDecodedValue, type AbifDirEntry, type AbifEntry, type AbifFile, type AbifMetadata, type ChannelSignals, type Chromatogram, ENTRY_SIZE, HEADER_SIZE, type ParsedAbif, averagePeakSpacing, channelMaxLength, ensureRawDataChannels, findEntries, findEntry, getConfidences, getDataChannel, getFwo, getPositions, getRawChannelMap, getSamplingRate, getSequence, hasProcessedTraces, hasSignals, parseAbif, readAbif, setAveragePeakSpacing, setConfidences, setPositions, setSequence, tagNameFromInt32, upsertEntry, writeAbif };
574
+ export { type AbifBaseCallRole, type AbifBaseCallVariant, type AbifBaseCalls, type AbifByteRange, type AbifChromatogramBundle, type AbifDataChannelRole, type AbifDecodedValue, type AbifDirEntry, type AbifDirectory, type AbifEntry, type AbifEntryRaw, type AbifFile, type AbifMetadata, type ChannelSignals, type Chromatogram, ENTRY_SIZE, HEADER_SIZE, type ParsedAbif, averagePeakSpacing, channelMaxLength, dataChannelRole, ensureRawDataChannels, findEntries, findEntry, getChannelMap, getConfidences, getDataChannel, getFwo, getPositions, getReverseComplemented, getSamplingRate, getSequence, hasData9To12Block, hasSignals, isFwoPermutation, parseAbif, readAbif, setAveragePeakSpacing, setConfidences, setPositions, setSequence, tagNameFromInt32, upsertEntry, writeAbif };