Skip to content

Comments

Add snippet method/SDK tests, Perl RecsSDK, and install-manpages command#96

Merged
benbernard merged 4 commits intomasterfrom
feature/snippet-method-and-sdk-tests
Feb 25, 2026
Merged

Add snippet method/SDK tests, Perl RecsSDK, and install-manpages command#96
benbernard merged 4 commits intomasterfrom
feature/snippet-method-and-sdk-tests

Conversation

@benbernard
Copy link
Owner

@benbernard benbernard commented Feb 24, 2026

Summary

  • Add tests for method calls on {{}} expanded values (.toFixed(), .upper(), sprintf(), etc.) across JS, Python, and Perl
  • Add tests for record SDK methods (r.get(), r.set(), r.has(), r.keys(), etc.) in all three languages
  • Make Perl $r a blessed RecsSDK object (data hash blessed directly) so it supports both $r->{field} and $r->get("a/b")
  • Add has() and TO_JSON() methods to RecsSDK, use Scalar::Util::reftype for blessed hashref handling
  • Add missing core record methods to Python and Perl SDKs: remove(), rename(), prune_to() (+ keys() for Perl)
  • Remove 46 checked-in man pages from man/man1/; add man/ to .gitignore
  • Extract generateManPages(outDir) from scripts/generate-manpages.ts for reuse
  • Add recs install-manpages subcommand that generates and installs man pages to ~/.local/share/man/man1/
  • Update manpages.test.ts to use a temp directory instead of writing into the repo

Test plan

  • All 1665 tests pass, 0 fail
  • TypeScript type check passes
  • git status stays clean after test runs (no more man page date noise)
  • bun bin/recs.ts install-manpages installs man pages to ~/.local/share/man/man1/

🤖 Generated with Claude Code

Add comprehensive tests for method calls on {{}} expanded values and
record SDK methods across all three languages (JS, Python, Perl).

Change Perl RecsSDK to bless the data hash directly (like the original
App::RecordStream::Record) so $r is now a RecsSDK object with both
direct hash access ($r->{field}) and SDK methods ($r->get("a/b"),
$r->set(), $r->has()).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link

github-actions bot commented Feb 24, 2026

Performance Benchmark Results

⚠️ 6 regressions detected out of 103 benchmarks (threshold: 25%)

Benchmark Median Baseline Delta
KeySpec — simple key (name) 405.3µs 218.9µs +85.1% 🔴
KeySpec — nested key (address/zip) 1.06ms 556.1µs +91.1% 🔴
collate — 100 records (count by city) 392.9µs 290.9µs +35.1% 🔴
chain — 5 ops (grep eval grep eval
Record.get — 10K records × 3 fields 100.8µs 52.6µs +91.4% 🔴
implicit — 2 ops (grep eval), 100 records 118.8µs 80.8µs

103 benchmarks: 15 faster, 14 slower, 74 within noise (10%)

ℹ️ Note: Benchmarks are advisory-only. GitHub Actions shared runners have variable performance, so results may fluctuate ±25% between runs. For reliable benchmarking, run locally with bun run bench.

Full benchmark results

JSON Parsing

Benchmark Median Baseline Delta Throughput
Record.fromJSON — 100 lines 154.6µs 152.5µs +1.4% 646.83K rec/s
Record.fromJSON — 10K lines 14.24ms 12.84ms +10.9% 🔴 702.26K rec/s, 208.0 MB/s
InputStream.fromString — 100 records 222.4µs 218.9µs +1.6% 449.73K rec/s
InputStream.fromString — 10K records 18.86ms 18.09ms +4.3% 530.19K rec/s, 157.0 MB/s
JSON.parse baseline — 10K lines (no Record) 12.97ms 12.72ms +2.0% 770.78K rec/s, 228.3 MB/s
JSON.parse single array — 10K records 12.78ms 13.27ms -3.7% 782.48K rec/s, 231.7 MB/s

JSON Serialization

Benchmark Median Baseline Delta Throughput
Record.toString — 100 records 87.1µs 84.5µs +3.1% 1.15M rec/s
Record.toString — 10K records 8.49ms 8.55ms -0.7% 1.18M rec/s, 348.9 MB/s
Record.toJSON — 10K records 293.5µs 280.6µs +4.6% 34.07M rec/s
JSON.stringify baseline — 10K objects (no Record) 7.95ms 7.75ms +2.7% 1.26M rec/s, 372.4 MB/s
Batch join — 10K records (map+join) 8.56ms 8.34ms +2.7% 1.17M rec/s, 345.9 MB/s

KeySpec Access

Benchmark Median Baseline Delta Throughput
KeySpec — simple key (name) 405.3µs 218.9µs +85.1% 🔴 24.67M rec/s
KeySpec — nested key (address/zip) 1.06ms 556.1µs +91.1% 🔴 9.41M rec/s
KeySpec — deep nested (address/coords/lat) 652.0µs 1.06ms -38.8% 🟢 15.34M rec/s
KeySpec — array index (tags/#0) 435.6µs 881.6µs -50.6% 🟢 22.96M rec/s
Direct property access baseline (rec['name']) 91.8µs 75.1µs +22.3% 🔴 108.89M rec/s
Direct nested access baseline (rec.address.coords.lat) 65.5µs 113.3µs -42.2% 🟢 152.62M rec/s
KeySpec construction — cached (same spec 10K times) 288.3µs 349.6µs -17.6% 🟢 34.69M rec/s
KeySpec construction — unique specs (10K different) 2.31ms 2.26ms +1.9% 4.34M rec/s
Compiled KeySpec.resolveValue — nested (address/zip) 168.0µs 312.3µs -46.2% 🟢 59.53M rec/s
Compiled KeySpec.resolveValue — deep (address/coords/lat) 127.1µs 231.1µs -45.0% 🟢 78.68M rec/s
Compiled KeySpec.resolveValue — array (tags/#0) 152.1µs 277.1µs -45.1% 🟢 65.73M rec/s
Compiled KeySpec.setValue — nested (address/zip) 144.8µs 144.2µs +0.4% 69.05M rec/s

Core Operations

Benchmark Median Baseline Delta Throughput
grep — 10K records (r.age > 50) 477.8µs 448.4µs +6.5% 20.93M rec/s
grep — 10K records (string match) 465.0µs 440.2µs +5.6% 21.51M rec/s
eval — 10K records (add computed field) 2.45ms 2.42ms +1.3% 4.08M rec/s
xform — 10K records (push each record) 2.03ms 3.00ms -32.3% 🟢 4.93M rec/s
sort — 100 records (by score, numeric) 144.8µs 149.5µs -3.1% 690.41K rec/s
sort — 10K records (by score, numeric) 18.36ms 18.25ms +0.6% 544.73K rec/s
sort — 10K records (by name, lexical) 12.04ms 12.40ms -2.9% 830.74K rec/s
collate — 100 records (count by city) 392.9µs 290.9µs +35.1% 🔴 254.51K rec/s
collate — 10K records (count by city) 12.34ms 12.17ms +1.4% 810.35K rec/s
fromcsv — 10K rows (parse CSV to records) 14.82ms 15.19ms -2.4% 674.55K rec/s, 44.3 MB/s

Pipeline Overhead

Benchmark Median Baseline Delta Throughput
chain — single op (grep), 10K records 8.10ms 7.51ms +7.8% 1.23M rec/s
chain — 3 ops (grep eval grep), 10K records 7.51ms 7.53ms
chain — 5 ops (grep eval grep eval grep), 10K records
passthrough baseline — 10K records (direct collector) 6.30ms 6.03ms +4.5% 1.59M rec/s

Record Creation & Serialization

Benchmark Median Baseline Delta Throughput
new Record() — 10K objects 507.7µs 497.4µs +2.1% 19.70M rec/s
new Record() empty — 10K 113.0µs 110.1µs +2.6% 88.52M rec/s
Record.get — 10K records × 3 fields 100.8µs 52.6µs +91.4% 🔴 297.76M rec/s
Record.set — 10K records × 1 field 125.1µs 125.4µs -0.3% 79.92M rec/s
Record.toJSON — 10K records 291.6µs 280.6µs +3.9% 34.29M rec/s
Record.toString — 10K records 8.74ms 8.55ms +2.2% 1.14M rec/s
Record.clone — 10K records 6.33ms 6.04ms +4.8% 1.58M rec/s
Record.fromJSON — 10K lines 13.16ms 12.84ms +2.4% 760.16K rec/s, 225.1 MB/s
Record.dataRef — 10K records (zero-copy) 38.1µs 81.5µs -53.2% 🟢 262.39M rec/s
Record.sort — 10K records (numeric field) 11.49ms 11.51ms -0.2% 870.10K rec/s
Record.sort — 10K records (lexical field) 6.11ms 6.08ms +0.6% 1.64M rec/s
Record.cmp — 1M comparisons (single field) 104.38ms 102.54ms +1.8% 9.58M rec/s
Record.sort — 10K records (nested field numeric) 15.50ms 15.45ms +0.3% 645.19K rec/s
Record.cmp — 1M comparisons (multi-field cached) 87.12ms 86.08ms +1.2% 11.48M rec/s
Record.sort — 10K records (cached comparator reuse) 11.56ms 11.90ms -2.9% 864.88K rec/s

Chain vs Pipe

Benchmark Median Baseline Delta Throughput
chain — 2 ops (grep eval), 100 records 148.1µs 145.1µs +2.1%
pipe — 2 ops (grep eval), 100 records 524.43ms 506.69ms +3.5%
implicit — 2 ops (grep eval), 100 records 118.8µs 80.8µs +47.2% 🔴
chain — 2 ops (grep eval), 1K records 247.2µs 207.2µs +19.3% 🔴
pipe — 2 ops (grep eval), 1K records 503.80ms 524.55ms -4.0%
implicit — 2 ops (grep eval), 1K records 304.6µs 295.3µs +3.1%
chain — 2 ops (grep eval), 10K records 1.06ms 1.38ms -23.0% 🟢
pipe — 2 ops (grep eval), 10K records 506.62ms 509.32ms -0.5%
implicit — 2 ops (grep eval), 10K records 1.19ms 1.08ms +9.9%
chain — 3 ops (grep eval grep), 100 records 157.5µs 152.8µs
pipe — 3 ops (grep eval grep), 100 records 752.27ms 761.42ms
implicit — 3 ops (grep eval grep), 100 records 96.7µs 97.1µs
chain — 3 ops (grep eval grep), 1K records 195.4µs 211.4µs
pipe — 3 ops (grep eval grep), 1K records 751.42ms 770.06ms
implicit — 3 ops (grep eval grep), 1K records 220.8µs 206.6µs
chain — 3 ops (grep eval grep), 10K records 1.07ms 1.07ms
pipe — 3 ops (grep eval grep), 10K records 771.08ms 777.19ms
implicit — 3 ops (grep eval grep), 10K records 1.11ms 1.07ms
chain — 5 ops (grep eval grep eval grep), 100 records
pipe — 5 ops (grep eval grep eval grep), 100 records
implicit — 5 ops (grep eval grep eval grep), 100 records
chain — 5 ops (grep eval grep eval grep), 1K records
pipe — 5 ops (grep eval grep eval grep), 1K records
implicit — 5 ops (grep eval grep eval grep), 1K records
chain — 5 ops (grep eval grep eval grep), 10K records
pipe — 5 ops (grep eval grep eval grep), 10K records
implicit — 5 ops (grep eval grep eval grep), 10K records

Line Reading

Benchmark Median Baseline Delta Throughput
InputStream.fromFile — 100 lines 493.1µs 502.3µs -1.8% 202.79K rec/s, 59.9 MB/s
InputStream.fromString — 100 lines 188.5µs 188.9µs -0.2% 530.64K rec/s, 156.7 MB/s
manual buffer (isolated) — 100 lines 245.8µs 288.2µs -14.7% 🟢 406.78K rec/s, 120.2 MB/s
bulk text + split — 100 lines 87.5µs 82.4µs +6.2% 1.14M rec/s, 337.5 MB/s
node readline — 100 lines 541.1µs 434.4µs +24.6% 🔴 184.81K rec/s, 54.6 MB/s
TextDecoderStream — 100 lines 258.7µs 258.4µs +0.1% 386.58K rec/s, 114.2 MB/s
binary newline scan — 100 lines 268.6µs 274.1µs -2.0% 372.35K rec/s, 110.0 MB/s
bun native stdin — 100 lines 24.43ms 26.37ms -7.4% 4.09K rec/s, 1.2 MB/s
InputStream.fromFile — 10K lines 23.91ms 23.40ms +2.1% 418.27K rec/s, 123.9 MB/s
InputStream.fromString — 10K lines 18.37ms 19.01ms -3.4% 544.23K rec/s, 161.2 MB/s
manual buffer (isolated) — 10K lines 6.65ms 6.56ms +1.3% 1.50M rec/s, 445.4 MB/s
bulk text + split — 10K lines 2.42ms 2.58ms -6.5% 4.14M rec/s, 1225.4 MB/s
node readline — 10K lines 9.11ms 9.65ms -5.6% 1.10M rec/s, 325.0 MB/s
TextDecoderStream — 10K lines 4.67ms 4.23ms +10.3% 🔴 2.14M rec/s, 634.7 MB/s
binary newline scan — 10K lines 9.89ms 8.74ms +13.2% 🔴 1.01M rec/s, 299.4 MB/s
bun native stdin — 10K lines 43.46ms 44.82ms -3.0% 230.09K rec/s, 68.1 MB/s
InputStream.fromFile — 100K lines 258.50ms 260.12ms -0.6% 386.85K rec/s, 115.0 MB/s
InputStream.fromString — 100K lines 208.78ms 219.89ms -5.0% 478.96K rec/s, 142.3 MB/s
manual buffer (isolated) — 100K lines 32.28ms 38.01ms -15.1% 🟢 3.10M rec/s, 920.5 MB/s
bulk text + split — 100K lines 25.86ms 29.24ms -11.6% 🟢 3.87M rec/s, 1149.1 MB/s
node readline — 100K lines 84.95ms 87.25ms -2.6% 1.18M rec/s, 349.8 MB/s
TextDecoderStream — 100K lines 39.27ms 37.01ms +6.1% 2.55M rec/s, 756.7 MB/s
binary newline scan — 100K lines 74.31ms 66.37ms +12.0% 🔴 1.35M rec/s, 399.9 MB/s
bun native stdin — 100K lines 120.69ms 125.47ms -3.8% 828.59K rec/s, 246.2 MB/s

benbernard and others added 3 commits February 24, 2026 16:02
Add missing core record manipulation methods to bring Python and Perl
snippet SDKs to parity with the JS Record API. Also adds keys() to
the Perl SDK. Includes tests for all new methods in both languages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Man pages were committed to man/man1/ and regenerated every test run,
causing constant git status noise from date-stamp changes. Now they are
gitignored and generated on demand.

- Extract generateManPages(outDir) from scripts/generate-manpages.ts
- Add `recs install-manpages` subcommand that installs to ~/.local/share/man/man1/
- Update manpages.test.ts to use a temp directory instead of repo man/
- Add man/ to .gitignore and untrack existing files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@benbernard benbernard changed the title Add snippet method/SDK tests and make Perl $r a RecsSDK object Add snippet method/SDK tests, Perl RecsSDK, and install-manpages command Feb 25, 2026
@benbernard benbernard merged commit a83cd7a into master Feb 25, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant