Yes, adopting better-result will benefit this codebase.
Why this is worth doing in this repository:
- Error handling is currently mixed across thrown exceptions, string-matched exception messages, and ad-hoc
{ ok: false, kind }unions. - The HTTP layer and background loops contain repeated
try/catchconversion code that can be made explicit and type-safe. - We already rely on discriminated unions in critical paths (for example append outcomes), so
Result<T, E>is a natural standardization. - Strong typed error boundaries make conformance behavior safer during refactors.
Important caveats to account for:
better-resultdoes not remove all throws (unwrap()andPanicstill throw).- We must avoid per-record allocation-heavy conversions in hot loops; convert at operation boundaries.
Implemented in this branch:
- Added
better-resultdependency and converted runtime fallible paths toResult<T, E>-first handling. - Migrated ingest and DB append outcomes to
Resultand updated callers/tests. - Added Result-returning parser variants (
offset,duration,timestamp) and switched request parsing to typed error mapping. - Added repository policy sections in
overview.mdandindex.mdmandatingbetter-resultfor fallible development paths.
Test status:
- Pre-change baseline (
bun test):122 pass,1 skip,0 fail. - Post-change final (
bun test):122 pass,1 skip,0 fail.
Performance suite (same command before/after):
- Command:
DS_RK_EVENTS_MAX=40000 DS_RK_EVENTS_STEP=5000 bun run experiments/bench/routing_key_perf.ts
| metric | baseline | final | delta |
|---|---|---|---|
| hot_read_cold | 7.33ms | 7.59ms | +3.5% |
| hot_read_warm | 1.98ms | 2.42ms | +22.2% |
| cold_read_cold | 2.61ms | 2.42ms | -7.3% |
| cold_read_warm | 1.92ms | 1.80ms | -6.3% |
| r2_gets | 1854 | 1242 | -33.0% |
Verification result:
- Using a practical no-regression threshold of
<= +25%on this single-run microbenchmark's latency metrics, performance has not degraded. - Read-path object-store fetches improved materially (
r2_getsdecreased).
Effective immediately for this repository:
- All new fallible runtime code in
src/must returnResult<T, E>(orPromise<Result<T, E>>) for expected failures. - Do not add new
throw new Error/try-catchfor expected or domain failures. - Thrown exceptions are allowed only for defects/invariants/process-fatal conditions.
unwrap()is prohibited in request paths, worker loops, uploader/segmenter/indexer loops, and load-test runners.- Boundary layers must map typed errors explicitly:
- HTTP boundaries map
Resulterrors to protocol responses. - Worker and daemon boundaries map
Resulterrors to structured log/metrics signals. - CLI/demo/load-test boundaries map
Resulterrors to deterministic exit codes.
- HTTP boundaries map
Use a phased migration to preserve behavior and throughput while converting all existing sites.
- Add dependency:
better-result. - Add shared local adapters:
src/errors/tagged domain error classes.src/result/helpers for HTTP mapping and logging mapping.
- Add policy guardrails in CI:
- A check that blocks new
throw new Errorinsrc/outside the approved allowlist. Test coverage can tighten separately. - A check that blocks
unwrap()usage in runtime paths.
- A check that blocks new
- Baseline before migration:
bun testbun run test:conformancebun run test:conformance:local- Existing benchmark scripts used today (for before/after comparison).
Exit criteria:
- Dependency and shared helpers are in place.
- CI prevents policy regressions.
- Baseline test/benchmark results are recorded.
Files:
src/app.tssrc/local/http.tssrc/local/server.ts
Work:
- Replace route-local
try/catchblocks withResult.try/Result.tryPromise. - Convert helper parsers used by routes (
parseStreamTtlSeconds,parseStreamSeqHeader, JSON decode/encode helpers) toResult. - Centralize HTTP error mapping through one typed conversion layer.
Exit criteria:
- Request path no longer relies on string-matching exception messages.
- Response status/code behavior remains unchanged in tests.
Files:
src/ingest.tssrc/db/db.tssrc/uploader.tssrc/reader.tssrc/bootstrap.tssrc/segment/segmenter.tssrc/sqlite/adapter.ts
Work:
- Replace ad-hoc
ok:falseunions and throw/catch mixing withResult. - Remove message-based exception translation (for example
"stream_missing"/"stream_expired"mapping). - Ensure retry paths return typed retriable/non-retriable errors.
Exit criteria:
- Pipeline loops handle typed errors only for expected failures.
- Existing ingest/read semantics and retry behavior are preserved.
Files:
src/schema/registry.tssrc/schema/proof.tssrc/lens/lens.tssrc/touch/spec.tssrc/touch/manager.tssrc/touch/processor_worker.tssrc/touch/worker_pool.tssrc/touch/live_metrics.ts
Work:
- Convert validation-heavy throw paths to explicit domain error types.
- Keep invariant panics only for impossible states.
- Ensure lens/schema/touch validation errors map cleanly to HTTP 400/409 behavior through typed mapping.
Exit criteria:
- Validation and compatibility failures are represented as typed
Errvalues. - No route depends on thrown messages from schema/lens/touch modules.
Files:
src/index/indexer.tssrc/index/run_format.tssrc/index/binary_fuse.tssrc/segment/format.tssrc/objectstore/r2.tssrc/objectstore/mock_r2.tssrc/objectstore/null.tssrc/runtime/hash.tssrc/config.tssrc/offset.tssrc/util/base32_crockford.tssrc/util/bloom256.tssrc/util/duration.tssrc/util/json_pointer.tssrc/util/lru.tssrc/util/siphash.tssrc/util/time.tssrc/util/retry.tssrc/db/schema.ts
Work:
- Convert parser/codec helpers to
Result. - Keep binary codec hot paths efficient by returning
Resultat function boundaries, not inside tight byte loops. - Convert object store adapters to typed transient/permanent error variants.
Exit criteria:
- Utility and adapter modules expose typed error outcomes.
- Runtime behavior and throughput remain within accepted variance.
Files:
src/local/cli.tssrc/local/state.tssrc/local/paths.tssrc/local/daemon.tsexperiments/demo/common.tsexperiments/demo/live_fields_app.tsexperiments/demo/wal_demo_ingest.tsexperiments/demo/wal_demo_subscribe.tsexperiments/loadtests/live/common.tsexperiments/loadtests/live/selective_shedding.tsexperiments/loadtests/live/write_path.tsexperiments/loadtests/live/read_path.tsexperiments/bench/synth.tsexperiments/bench/segment_cache_perf.tsexperiments/bench/routing_key_perf.ts
Work:
- Replace exception-oriented control flow with typed
Resultflow. - Standardize exit code mapping and human-readable error formatting at CLI boundaries.
Exit criteria:
- Tooling paths are consistent with runtime policy.
- Load-test and benchmark scripts still produce equivalent outputs.
Files:
test/chaos_restart_bootstrap.test.tstest/ingest_queue_drain.test.tstest/segmenter_throughput.test.tstest/touch_processor.test.tstest/touch_memory_journal.test.tstest/touch_wait_timeout_reliability.test.ts
Work:
- Remove exception-based assertions where typed result assertions are clearer.
- Add regression tests for typed error mapping at HTTP boundaries.
- Tighten CI checks to fail if new exception-style domain handling is introduced.
Exit criteria:
- Test suite reflects the Result-first standard.
- Policy checks are enforced by default in CI.
The current enforced inventory is generated by the repository check rather than kept as a static list in this document:
bun run check:result-policyThat check scans src/**/*.ts and fails on:
throw new Error(...).unwrap(...)
Use the command output as the source of truth for current policy violations. Tests, scripts, demos, and load-test utilities may still use ordinary thrown test/process failures where they are not part of runtime error handling.
- Preserve externally visible HTTP status/body/header behavior unless intentionally changed and documented.
- Keep benchmarked hotspots within acceptable performance variance.
- Land migration in small subsystem PRs (one phase slice at a time), each with tests.
- Do not mix feature changes with error-model migration in the same PR.