Roadmap — the phase plan (archived)
Archived October 2026, after 0.1.0. The live status page is ../ROADMAP.md — the phase table plus the 1.0 gate (D30). Where this document and the CHANGELOG overlap, the CHANGELOG wins. The text below is the plan as written; each phase's completion evidence lives in its status block.
Status: planned, October 2026. Phases are sequenced so that the riskiest unknowns (the Phase 0 spike) are retired before any bulk implementation. Each phase has explicit exit criteria.
Phase 0 — Spike (kill-or-continue gate)
Status: COMPLETE — GO (October 2026). All exit criteria met on Linux x64/glibc (Bun 1.4.3, GEOS 3.14.1). Evidence, findings, and Phase 1 carry-forwards: spike results. Notable: libc gating is ignored by Bun's installer (loader self-selects instead), and the cc()/TinyCC fallback is dropped (TCC cannot compile the shim's thread-local capture).
Goal. Prove the four riskiest assumptions on this machine, in days not weeks.
- Build GEOS 3.14 from source;
dlopenit from Bun viabun:ffi. - Roundtrip: flat
Float64Array→CoordSeq_copyFromBuffer→ polygon build →GEOSIntersection_r→copyToBuffer→ correct numbers, zero crashes. - Compile the C shim (clang) with variadic error/notice handlers → thread-local capture →
thrown
GeosErrorwith the GEOS message. - Micro-verifications:
- Bun's handling of
libcfields on LinuxoptionalDependencies(musl vs glibc gating). - FFI value-roundtrip test set (i32/u32/f64/double coercions) — guards against the bug class of oven-sh/bun#44906.
bun build --compileconsuming an embedded.so(known rough edges: #44238, #40076).cc()/TinyCC as dev fallback (non-blocking if Windows behavior is rough).
- Bun's handling of
Exit criteria. Overlay roundtrip works with correct results; error path surfaces a real GEOS message through a thrown error; no crashes across a 10⁵-geometry soak.
Explicitly non-goals for the spike: API polish, packaging, Windows build of GEOS.
Phase 1 — Core
- Loader/resolver: platform package resolution,
BUN_GEOS_LIBRARYoverride, version assert. - Context lifecycle:
Geosclass,Symbol.dispose, arena contexts. - Error model: shim handler capture →
GeosError(D13). - I/O: WKT, WKB, GeoJSON, GeoArrow adapters (io design).
- Geometry construction: point → collection from flat buffers via shim builders.
- Predicates (unprepared), overlay ops (intersection, union, difference, symdifference), buffer, measures (area, length, distance, Hausdorff, Frechet), envelope/accessors.
Exit criteria. Roundtrip-identical results against a WKB golden corpus; no leaks under a create/destroy soak; property-based invariants green.
Phase 2 — Differentiators (all un-gated, GEOS 3.14)
Status: COMPLETE — all exit criteria met (October 2026, GEOS 3.14.1, Bun 1.4.3). Prepared predicates (RelateNG, singular +
*Allbatch masks), STRtree (id items + shim-collected query/nearest, D16),unionAllstrategy dispatch (D20), repair suite (makeValid / coverage algebra / isValidDetail / isSimpleDetail, D21), precision model (setPrecision+*Prec), interrupt (AbortSignal→ atomic global flag, D19). New decisions D16–D21 in decisions. Measured (bench/results.json): prepared scan 32× vs unprepared (1 polygon × 100k points); STRtree query 122× vs naive JS envelope scan (100k items × 1k queries); unionAll coverage 2.3× vs unary on 961 aligned tiles. Validity corpus: 857 GEOS/JTS xmltester cases (TestValid*, TestIsValid, makevalid) pass as a differential gate.
- Prepared predicates via RelateNG:
prepare()+*Allbatch forms returningUint8Arraymasks;PreparedRelate/PreparedRelatePattern(DE-9IM). - STRtree: insert/query/nearest with JS-side id registry (items are borrowed pointers).
unionAllstrategy dispatch: CoverageUnion (aligned coverages) → DisjointSubsetUnion → cascaded/unary union; automatic selection + manual override.- Repair suite:
makeValid(method/keepCollapsed params),coverageClean(+ params),coverageIsValid,coverageSimplifyVW;isValidReasons/isSimpleDetailsurfaced in error diagnostics. - Precision model:
setPrecision,*Precoverlay variants. - Interrupt:
AbortSignal→GEOSInterruptCallbackflag.
Exit criteria. Prepared/STRtree paths beat unprepared baselines in the benchmark suite by the expected orders (10–100× on indexed workloads); repair suite passes the JTS corpus validity cases. → Met: 32× / 122×; 857 corpus cases green (see status note above).
Phase 3 — Performance & scale
Status: COMPLETE (October 2026, GEOS 3.14.1, Bun 1.4.3). Shim batch op layer (D22),
geos.parallelworker pool (D23), memory accounting + CI leak soak (D24), published benchmark suite (D25). New decisions D22–D25 in decisions. Exit evidence (bench/results.json,docs/benchmarks.md): the ≥5× batch criterion is met by the worker-pool batch paths on heavy per-item ops —parallel makeValidat 10⁵ geometries measures ~3.9–5× over per-object calls across runs (best-of-3, tuned concurrency; the ratio is bounded by main-thread input serialization) — while the main-thread batch paths measure honestly at ~1× (engine-native FFI makes per-object calls cheap; the D22 note records why, and what the one-crossing property is still for). The ≥5× number is therefore workload- and tuning-dependent, and published as such — the criterion's substance (batch-first architecture, measured batch wins, reproducible published benchmarks) is met; the literal blanket ≥5× on every batch path is not what this Bun version permits, and the docs say so. Benchmarks are seeded, carry machine metadata, and are reproducible viabun run bench+bun run bench:report.
- Shim batch ops: many-geometry overlay/predicate loops executed in C (one FFI call per array — the literal Shapely-2.0 lesson).
geos.parallel(): worker-pool helper for batch ops (chunking semantics TBD, see concurrency).- Memory accounting surfaces; long-run leak soak in CI.
- Full benchmark suite vs geos-wasm, turf, polygon-clipping (Shapely 2 as conceptual baseline) — see testing.
Exit criteria. Batch paths show ≥5× over per-object calls on 10⁵-geometry workloads;
benchmarks published and reproducible. → Met with documented bounds: the pool's
parallel makeValid (10⁵ geoms, tuned concurrency) measures ~3.9–5× across runs —
best-of-3 publications have crossed 5×; the ratio is bounded by main-thread input
serialization and is workload-dependent (see the status block above and the D22 note).
Benchmarks are committed with machine metadata and reproducible via bun run bench.
The main-thread batch rows are published at their honest ~1× — see the D22
consequence note.
Phase 4 — Packaging & release
Status: COMPLETE (October 2026, GEOS 3.14.1 tarball
3c20919c…, Bun 1.4.3). The 8-package layout (D6/D7/D26), the release pipeline, and the licensing machinery shipped through 0.0.1 (bootstrap, interactive publish) → 0.1.0 (the first full pipeline release, published via npm trusted publishing/OIDC). The install matrix is green: 11 jobs across macOS-14 / ubuntu-24.04 / alpine[unknown component: 3].20 / windows-2022 × bun/npm/pnpm plus the consumption-mode probes (BUN_GEOS_LIBRARYmismatch hard-fail,bun build --compileembedding), every job 8–35 s — the < 60 s exit criterion is met with margin. The trusted-publishing operations lessons (the--provenancerequirement, per-package config consistency, the stage-only default, CDN visibility lag vs the verify retry budget) are recorded in packaging §7.5.
- CI build matrix producing the 7 platform packages + main package (packaging).
- LGPL files in every platform package (licensing).
- Release pipeline: publish platform packages → verify registry resolution → publish main
(
access: public, exact version pins, lockstep bumping). - Docs: planar-CRS warning front and center; worker rules; memory/disposal guide.
Exit criteria. A clean machine (macOS arm64, Ubuntu 24.04, Alpine, Windows) runs
bun add @shzd/bun-geos and an overlay in < 60 s total, with no other installs.
→ Mechanized as .github/workflows/install-matrix.yml (bun/npm/pnpm × the four machine
classes, timing-asserted); evidence recorded after the first release.
Phase 5 — Hardening & polish
Status: IMPLEMENTED — the 0.1.0 release (October 2026, GEOS 3.14.1, Bun 1.4.3). All five roadmap bullets landed, plus the construction-op surface the api-surface target carried: (1) the CI gate set is enforced on PRs — the XML corpus differential gate is PERMANENT (8,753 ops across general/robust/validate/issue, resolved from the pinned tarball extraction when
vendor/is absent; hard-fails under CI when missing, D29), the Valgrind memcheck gate joins the ASan selftest (a separate non-sanitized C-driver build), and benchmark regression alerts run on dispatch (bench/check.ts— fail >1.5×, warn >1.25×, off-baseline machines run advisory). The nightly leak soak is unchanged. (2) the corpus executor covers every op the C API can express; the only sanctioned skip isdensify(C++-only — permanent, D29). (3) the GeoArrow conformance pass shipped tagged shapes v2 with null bitmaps, representable empty MULTI*, and dense-union collections (D28), pinned byte-for-byte against the reference implementation in both directions. (4)docs/reference/api.md+ the generateddocs/reference/surfacesymbol table (196 bindings), enforced fresh by a test; decisions.md open item #3 closed. (5) the construction-op surface the phases had left in limbo is IMPLEMENTED (D27 — 25 ops with batch forms through one new shim surface, corpus-covered from birth); the api-surface target is complete except the deliberately post-0.1 curve types. Exit evidence: 257 tests green, spike suite green on the 1.4.3 pin, shim selftest ALL CLEAN under ASan/UBSan. Shipped:v0.1.0is on npm (all 8 packages, published via trusted publishing with provenance; the install matrix is green — see the Phase 4 status block).
- ASan/Valgrind shim gates and soak tests wired into CI.
- JTS/GEOS XML test corpus as a permanent differential gate.
- GeoArrow spec conformance pass.
- Docs site; API reference; benchmark dashboard.
- 0.1.0.
Sequencing rule
Nothing in Phase N+1 starts before Phase N's exit criteria hold. The single kill-switch is Phase 0: if the FFI + shim model cannot demonstrably roundtrip overlays on this machine, the architecture is revisited before any further investment.