# Literal row comparison v1

Save `row-compare.mjs` beside a file named `row-compare-example.mjs` containing
the example below. Run `node row-compare-example.mjs` in Node 18+; the module also
works in modern JavaScript. No runtime dependencies.

```js
import assert from 'node:assert/strict';
import { compareRows } from './row-compare.mjs';
const result = compareRows(
  'id\tfruit\n001\tpear\n001\tpear\n002\tplum',
  'id\tfruit\n001\tpear\n003\tpeach',
);
assert.equal(result.leftOnlyTsv, 'id\tfruit\n002\tplum');
assert.equal(result.sharedTsv, 'id\tfruit\n001\tpear');
assert.equal(result.rightOnlyTsv, 'id\tfruit\n003\tpeach');
assert.deepEqual(result.shared[0], {
  cells: ['001', 'pear'], leftRows: [2, 3], rightRows: [2], leftCount: 2, rightCount: 1,
});
assert.equal(result.stats.leftDuplicateExtraOccurrences, 1);
assert.equal(result.stats.membershipEqual, false);
const unequalCounts = compareRows('value\nx\nx', 'value\nx');
assert.equal(unequalCounts.stats.membershipEqual, true);
assert.equal(unequalCounts.stats.multiplicityEqual, false);
assert.equal(compareRows('value\n\n', 'value\n\n').sharedTsv, 'value\n\n');
console.log('Row comparison example passed.');
```

Arguments: two literal TSV strings, each with a first-row header. Both are
required. Headers must match exactly, field by field and in the same order;
capitalization and surrounding spaces count. Blank and duplicate header names
are allowed. Every data row must have the same number of fields as its header.
Header-only tables are allowed.

Comparison uses the entire literal row. `leftOnly`, `shared` and `rightOnly`
contain each distinct row once, with its `cells`, original `leftRows` and
`rightRows` positions and `leftCount` and `rightCount`. Positions start at 2 for
the first data row because the header is row 1. Left-only and shared groups
follow first appearance in the left input; right-only groups follow first
appearance in the right input. A shared row remains shared when its occurrence
counts differ. `stats.membershipEqual` compares distinct-row membership;
`stats.multiplicityEqual` also compares occurrence counts. Duplicate extra
occurrences count all repeats beyond each distinct row's first occurrence.

`leftOnlyTsv`, `sharedTsv` and `rightOnlyTsv` each contain the header and one row
per group. They do not expand duplicate occurrences. `header` contains the
shared header cells. `summary` and `lines` form a human-readable report using
JSON cell arrays; `checked` counts all input data rows and `issues` counts
left-only groups, right-only groups and shared groups with unequal counts.

Leading zeros, spaces, quotes, formulas and blank cells remain literal. Quotes
do not escape tabs or newlines. No key-column join, changed-cell matching,
trimming, case folding, numeric coercion, formula evaluation or spreadsheet
writes. CRLF and bare CR become LF. Exactly one final newline is ignored in
each input; other blank rows are data. An empty final one-column row in a TSV
output gets an additional newline terminator so re-reading preserves it.
`value\n\n` therefore has one empty data row, while `value\n` is header-only.
A blank one-column header without data is serialized as `\n`.

Limits: 100,000 UTF-16 code units per input, 500 rows per input including its
header, and 50 columns. The three TSV outputs together are limited to 300,000
characters; the human-readable report has a separate 300,000-character limit.
Empty/ragged inputs, unequal headers or exceeded limits throw without partial
output. No network or telemetry. This does not certify spreadsheet import
safety, time savings or customer use.

Generate with `node scripts/build-row-compare.mjs`; verify browser-core bytes
with `node scripts/build-row-compare.mjs --check`.

## License

MIT License

Copyright (c) 2026 QuasiLife

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
