TypeScript-first access to NASA’s SPICE geometry toolkit — in Node.js and the browser (via WebAssembly).
- Docs: https://rybosome.github.io/tspice
- Live demo (WebGL + WASM): https://orrery.ryboso.me/
SPICE is a toolkit (and data format ecosystem) developed by NAIF (NASA’s Navigation and Ancillary Information Facility) for space-mission geometry. It computes positions, orientations, frames, and time conversions using mission-provided “kernels” (ephemerides, constants, pointing, etc.). It’s widely used across NASA and the planetary science community to make geometry calculations reproducible and shareable.
tspice is a set of TypeScript packages that let you load SPICE kernels and execute common SPICE workflows (time conversion, ephemerides, frames, geometry) from modern JS runtimes.
- CSPICE is the official NAIF toolkit, battle-tested and widely used in mission operations and research. It’s the reference implementation and the right choice if you’re working directly in C/C++/Fortran or maintaining your own bindings.
- SpiceyPy is a mature, well-maintained Python wrapper around CSPICE. It’s widely adopted in the planetary science community and excellent for Python-first analysis workflows.
- ANISE is a modern Rust-based reimplementation of key SPICE capabilities, focused on performance, concurrency, and a strongly typed API. It offers an alternative design philosophy and is well suited for high-performance or cloud-scale workloads.
- tspice targets a different niche: TypeScript-first, browser-capable, and designed for app-style workloads (interactive visualization, UI tooling, Web Workers).
If your target runtime is a browser, or your application is already TypeScript/Node and you want a first-class, typed API over CSPICE, tspice is designed to fill that gap.
tspice links against CSPICE (or a CSPICE-derived build) behind a TypeScript API. If you need CSPICE directly, download it from NAIF.
- Node.js: 20+ (tested on Node 20 and Node 22).
- Module format:
@rybosome/tspiceis ESM-only. - Browsers (WASM backend): a modern browser with WebAssembly, plus a bundler/dev server that can serve
.wasmassets (you may need to provide an explicitwasmUrldepending on your tooling). - Kernel hosting / CORS: browsers can only fetch kernels from a CORS-enabled origin; NAIF’s kernel servers typically don’t set CORS headers, so for production you’ll need to self-host kernels (or proxy them).
SPICE is stateful: loading kernels mutates a process-wide global kernel pool inside SPICE, and many routines read from (and sometimes affect) that shared state.
Isolation (separating kernel-sets and workloads) can be achieved by creating multiple process or WebWorker instances, depending on runtime.
pnpm add @rybosome/tspice
# or: npm i @rybosome/tspiceimport { kernels, spiceClients } from "@rybosome/tspice";
async function main() {
// A small, CORS-enabled catalog hosted for quickstart/testing.
// Not suitable for production.
const kernelPack = kernels.tspice().pick(
"lsk/naif0012.tls",
"pck/pck00011.tpc",
"spk/planets/de432s.bsp",
);
// Build the client and load the given kernels
const { spice, dispose } = await spiceClients
.withKernels(kernelPack)
.toAsync({ backend: "wasm" });
try {
const et = await spice.kit.utcToEt("2000 JAN 01 12:00:00");
const state = await spice.kit.getState({ target: "EARTH", observer: "SUN", at: et });
console.log(state.position, state.velocity);
} finally {
await dispose();
}
}
main().catch(console.error);In a production application, it is recommended to host kernels yourself or enable a proxy.
The kernels.naif() catalog can be configured to point at your hosted entries, or you can load bespoke kernels.
import { kernels } from "@rybosome/tspice";
// Hosting a mirror of NAIF's catalog at https://your-app.example/kernels/naif
const naifKernels = kernels
.naif({
origin: "https://your-app.example/kernels/naif/",
pathBase: "naif/",
})
.pick(
"lsk/naif0012.tls",
"pck/pck00011.tpc",
"spk/planets/de432s.bsp",
);
// Hosting a custom catalog at https://your-app.example/kernels/custom
const customKernels = kernels
.custom({
origin: "https://your-app.example/kernels/custom/",
pathBase: "custom/",
})
.pick("planets/my_custom_kernel.bsp");import { kernels, spiceClients } from "@rybosome/tspice";
async function main() {
// We can download kernels directly from NAIF servers in Node.
const kernelPack = kernels.naif()
.pick(
"lsk/naif0012.tls",
"pck/pck00011.tpc",
"spk/planets/de432s.bsp",
);
// Use the builder to create a synchronous, caching client.
// `dispose()` is still async and should be awaited.
const { spice, dispose } = await spiceClients
.caching({ maxEntries: 10_000, ttlMs: null })
.withKernels(kernelPack)
.toSync({ backend: "node" });
try {
const et = spice.kit.utcToEt("2000 JAN 01 12:00:00");
const state = spice.kit.getState({ target: "EARTH", observer: "SUN", at: et });
console.log(state.position, state.velocity);
} finally {
await dispose();
}
}
main().catch(console.error);CSPICE exposes 652 public routines; 162 are currently implemented in tspice.
- Detailed function inventory:
docs/cspice-function-inventory.md
tspice exposes a stable backend contract that models the CSPICE routine surface. That contract is designed to evolve conservatively as additional CSPICE functions are implemented.
tspice follows SemVer. As a pre-1.0.0 project, minor versions may introduce breaking changes. Within a given minor line (e.g. 0.1.x), patch releases are guaranteed not to break the public API.
Because CSPICE itself is a mature and stable toolkit, long-term API churn in tspice is expected to be low.
In addition to typical unit testing, tspice runs parity tests with CSPICE as the reference, and also checks that the Node and WASM backends stay consistent for the same kernels and inputs. tspice preserves CSPICE double-precision semantics; numeric comparisons are verified against CSPICE with defined tolerances.
- CSPICE reference parity: the YAML-driven verification harness (
packages/parity-checking) executes the same scenarios against raw CSPICE andtspice(Node/WASM), comparing results with numeric tolerances instead of baked-in “golden” answers. - Method-level contract coverage: the backend contract is documented method-by-method against CSPICE in
docs/parity/spicebackend-cspice-mapping.md. - Unit + cross-backend tests: per-package tests cover API behavior, error handling, and a growing set of direct Node ↔ WASM parity cases.
@rybosome/tspice(facade) — the user-facing entrypoint (packages/tspice/).SpiceBackend(contract) — the shared TypeScript interface all backends implement (packages/backend-contract/).- Backend implementations — concrete runtimes that satisfy the contract:
- Node native addon:
packages/backend-node/ - WASM (Emscripten):
packages/backend-wasm/
- Node native addon:
backend-shim-c(shared C shim) — a shared C integration layer reused by both backends (packages/backend-shim-c/).- CSPICE — the NAIF toolkit, linked into the native addon or compiled into the
.wasm.
flowchart TD
Facade["@rybosome/tspice (facade)"] --> Contract["SpiceBackend (contract)"]
Contract --> Node["backend-node (native addon)"]
Contract --> Wasm["backend-wasm (Emscripten)"]
Node --> Shim["backend-shim-c (shared C shim)"]
Wasm --> Shim
Shim --> CSPICE["CSPICE (NAIF toolkit)"]
Node native (backend: "node") |
WASM (backend: "wasm") |
|
|---|---|---|
| Runs in | Node.js only | Browsers + Node.js |
| Artifact | Native addon (.node) |
Prebuilt WebAssembly (.wasm) + JS glue |
| Best for | Node services, local kernel archives, potential performance wins | Browsers, Web Workers, portability |
| Kernel I/O shape | OS filesystem paths (plus optional byte staging) | Byte-backed loads into a virtual filesystem |
| Operational constraints | none | Needs the .wasm asset to be served/bundled correctly (often via bundler asset handling or explicit wasmUrl) |
- Expand parity test coverage (Node ↔ WASM) with more fixtures and scenarios.
- Expand CSPICE implementation coverage (see
docs/cspice-function-inventory.md) - Performance measurement and improvement
- Additional
kitfunctions and client functionality - e.g. batching
See CONTRIBUTING.md.
MIT — see LICENSE.
