@ferscloud/fers-calculation-web
v0.2.41
Published
High-performance structural engineering FEM solver compiled from Rust to WebAssembly. Runs entirely in the browser — no server round-trip per calculation.
Readme
@ferscloud/fers-calculation
High-performance structural engineering FEM solver compiled from Rust to WebAssembly. Runs entirely in the browser — no server round-trip per calculation.
This README covers both published packages:
@ferscloud/fers-calculation(Node.js / server-side,--target nodejs) and@ferscloud/fers-calculation-web(browser / bundler,--target bundler). The API is identical; only installation and bundler setup differ.
Installation
# Browser apps (Vite, Next.js, webpack):
npm install @ferscloud/fers-calculation-web
# Node.js / server-side:
npm install @ferscloud/fers-calculationBundler setup (browser build)
@ferscloud/fers-calculation-web is a WASM ES module that initialises via top-level await. Most bundlers need a one-time config:
Vite
npm i -D vite-plugin-wasm vite-plugin-top-level-await// vite.config.ts
import wasm from "vite-plugin-wasm";
import topLevelAwait from "vite-plugin-top-level-await";
export default { plugins: [wasm(), topLevelAwait()] };Next.js / webpack
// next.config.js
module.exports = {
transpilePackages: ["@ferscloud/fers-calculation-web"],
webpack(config) {
config.experiments = { ...config.experiments, asyncWebAssembly: true };
config.output.environment = { ...config.output.environment, asyncFunction: true };
return config;
},
};The solver is synchronous and CPU-bound; for large models run it inside a Web Worker so the UI thread stays responsive. The Node.js package (@ferscloud/fers-calculation) needs no bundler config.
Quick start — free tier (up to 100 members)
import { calculate_from_json } from "@ferscloud/fers-calculation";
// No init() call needed — the nodejs and bundler builds initialise the
// WASM module automatically on import.
const res = JSON.parse(calculate_from_json(JSON.stringify(myModel)));
if (res.ok) {
const data = res.result; // displacements, member_results, unity_checks, …
} else {
console.error(res.error.code, res.error.message);
}No API key required. Works for any model with up to 100 members.
Response shape
Every solver call returns a JSON envelope, so you JSON.parse once and branch on ok — you never have to sniff whether the returned string "looks like" an error:
// success
{ "ok": true, "result": { /* the full result document */ } }
// failure (invalid model, over the member limit, malformed JSON, …)
{ "ok": false, "error": { "code": "LimitExceeded",
"message": "Number of members (250) exceeds allowed maximum of 100" } }error.code is one of InvalidJson, LimitExceeded, SolveError, InternalPanic, InternalSerialization. The return value is always valid JSON.
Authenticated tier — Pro (up to 10 000 members)
Pro limits are unlocked by passing a short-lived signed token issued by the FERS Cloud server. The token is verified inside the WASM using an Ed25519 public key baked into the binary — it cannot be forged.
How it works
Your server → POST https://ferscloud.com/api/solver/token → signed 30-min token
(X-API-Key: <your-api-key>)
Browser → calculate_from_json_with_token(model, token) → Pro limits unlocked1. Get an API key
Log in at ferscloud.com, go to Profile → API Keys, and create a key. Store it as an environment variable on your server — never in browser code.
2. Backend: fetch a solve token
Your server fetches and caches the token. Example for a Next.js API route:
// pages/api/solve-token.ts
const FERS_API_KEY = process.env.FERS_API_KEY!;
let cached: { token: string; expiresAt: number } | null = null;
export default async function handler(req, res) {
const now = Date.now();
// Re-use if more than 5 minutes remain
if (cached && cached.expiresAt - now > 5 * 60 * 1000) {
return res.json({ token: cached.token });
}
const resp = await fetch("https://ferscloud.com/api/solver/token", {
method: "POST",
headers: { "X-API-Key": FERS_API_KEY },
});
if (!resp.ok) return res.status(502).json({ error: "Token fetch failed" });
const { token, expiresAt } = await resp.json();
cached = { token, expiresAt: new Date(expiresAt).getTime() };
return res.json({ token });
}Same pattern works for Express, Deno, Bun, Cloudflare Workers, or any server-side runtime.
3. Frontend: call the solver with the token
import { calculate_from_json_with_token } from "@ferscloud/fers-calculation";
const { token } = await fetch("/api/solve-token").then(r => r.json());
const res = JSON.parse(calculate_from_json_with_token(JSON.stringify(myModel), token));
if (!res.ok) throw new Error(`${res.error.code}: ${res.error.message}`);
const data = res.result;If the token is missing, expired, or invalid, the solver falls back to the free 100-member limit — it never throws (a genuine solve failure comes back as { ok: false, error }, not an exception).
Tier comparison
| Feature | Free | Pro |
|---|---|---|
| Max members | 100 | 10 000 |
| WASM function | calculate_from_json | calculate_from_json_with_token |
| Requires token | No | Yes |
| Token TTL | — | 30 minutes |
Security note
Keep your FERS_API_KEY server-side only. The solve token it fetches is safe to pass to the browser — it is short-lived and cryptographically signed. Only FERS Cloud can issue valid tokens; the browser WASM can only verify them.
TypeScript types
The browser package ships generated model types alongside the function signatures:
import type { FERS, ResultsBundle } from "@ferscloud/fers-calculation-web/fers-models";FERS is the input model; ResultsBundle is the result payload of a successful envelope. Both are generated from the engine's OpenAPI schema, so they track the published version.
