Error Handling and Integration Contract¶
This page is the behavioral contract for integrating ifc-lite into another system: what you get when things fail, how to manage memory across the WASM boundary, what is guaranteed to be reproducible, and what stability to expect across versions. It complements the per-feature guides, which cover the happy path.
Parse and geometry errors¶
The Rust crates return typed Results (a thiserror Error enum with variants
such as ParseError, InvalidEntityRef, InvalidIfcType). Failures propagate
as Err, not panics.
The STEP parser degrades safely on malformed input. It rejects a bad record with
an Err, skips unparseable bytes and continues scanning to the next valid
entity, and never panics, hangs, or overflows the stack on hostile bytes. This
is covered by malformed-input regression tests and a coverage-guided fuzz target
(rust/core/fuzz/); see the testing guide for how to run the fuzzer.
At the WASM boundary, a hard failure surfaces as a thrown JavaScript error. Wrap
calls that ingest untrusted files in try/catch.
The WASM cache is fail-fast (long-lived workers and servers)¶
IfcAPI keeps per-load caches (entity index, parts-to-skip, material-layer
index) behind mutexes that fail fast: if an earlier call panicked while
holding a lock, the lock is poisoned and the next call panics rather than
operating on inconsistent cache state. In a long-lived worker or a multi-tenant
server, one malformed file can therefore leave that specific IfcAPI instance
unusable.
The recovery contract is simple: on any thrown error, discard that IfcAPI
instance and create a fresh one. Do not keep using an instance that has thrown.
let api = new IfcAPI();
try {
const result = api.processGeometryBatch(/* ... */);
// ... use result ...
} catch (err) {
// The instance may be poisoned; do not reuse it.
api.free();
api = new IfcAPI();
throw err; // or handle and continue with the fresh instance
}
Memory management (WASM)¶
Memory is managed manually across the WASM boundary. Every handle, mesh,
collection, and the IfcAPI itself implements free() and [Symbol.dispose]().
Call free() (or use a using declaration) to release Rust-side memory,
including on error paths, since a thrown call can still leave allocated
objects.
function withMeshes(collection: MeshCollection) {
try {
for (let i = 0; i < collection.length; i++) {
const mesh = collection.get(i);
try {
upload(mesh);
} finally {
mesh.free();
}
}
} finally {
collection.free();
}
}
Most consumers should not manage this by hand: use
@ifc-lite/geometry's GeometryProcessor, which wraps the
pre-pass and job-batch flow and frees handles for you.
Determinism scope¶
For a critical system that checksums or compares geometry, know exactly what is reproducible:
- The exact-arithmetic predicate / CSG kernel is byte-identical across
x86_64,aarch64, andwasm32. Predicate signs are integer parity over FMA-free IEEE-754 arithmetic (the kernel uses no fused multiply-add), pinned by a cross-platform sign manifest. A scheduled workflow re-runs the predicate battery and a mesh-stat parity check on arm64 against committed x86_64 snapshots, so a platform-dependent regression fails the build. - Everything else is run-deterministic, not cross-platform-guaranteed. Tessellation density, styling and colour-map iteration order, and rounding in non-exact paths can differ between architectures. If you store a geometry checksum as a baseline, compute it on the same architecture you will compare against.
Versioning and stability¶
Packages (@ifc-lite/*) and the Rust crates are versioned with
changesets; a breaking change bumps
the major version. Pin a version for reproducible builds.
One caveat for integrators: the mesh and exported-data formats are not yet semver-guaranteed to be byte-stable across minor versions. If you persist serialized geometry or exports, re-validate them when you upgrade, rather than assuming the bytes are identical.