Utiliser FERS depuis JavaScript

Le solveur FERS est publié sous forme de compilation WebAssembly du même moteur Rust qui anime l’application web. Il s’exécute dans le navigateur ou sous Node, calcule sans aller-retour serveur et ne demande pas de clé d’API dans l’offre gratuite.

Installation

Deux paquets sont publiés depuis le même moteur avec une API identique : seuls l’installation et la configuration du bundler diffèrent. Prenez -web pour tout ce qui passe par un bundler, et le paquet sans suffixe pour Node côté serveur.

# Browser apps (Vite, Next.js, webpack):
npm install @ferscloud/fers-calculation-web

# Node.js / server-side:
npm install @ferscloud/fers-calculation

Les deux paquets portent la version du moteur et avancent ensemble. Épinglez-les à la même version exacte plutôt qu’à une plage avec accent circonflexe.

Configurer le bundler

@ferscloud/fers-calculation-web est un module ES WASM qui s’initialise via un await de niveau supérieur. La plupart des bundlers demandent un réglage unique ; le paquet Node n’en demande aucun.

Vite

Ajoutez le plugin puis définissez une cible de build qui prend en charge l’await de niveau supérieur.

npm i -D vite-plugin-wasm

Configuration Vite

// vite.config.ts
import wasm from "vite-plugin-wasm";

// The package initialises with a top-level await, so the build target
// has to be one that supports it. esnext needs no second plugin.
export default {
  plugins: [wasm()],
  build: { target: "esnext" },
};

Next.js et webpack

Transpilez le paquet et activez le WebAssembly asynchrone. asyncFunction est nécessaire parce que le module s’initialise avec un await de niveau supérieur.

// 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;
  },
};

Le solveur est synchrone et gourmand en CPU. Pour les grands modèles, exécutez-le dans un Web Worker afin que le fil d’interface reste réactif.

Calculer un modèle

Pas d’appel à init() et pas de clé d’API. L’offre gratuite couvre tout modèle jusqu’à 100 barres.

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);
}

L’enveloppe de réponse

Chaque appel au solveur renvoie une enveloppe JSON : vous analysez une fois et vous branchez sur ok, sans jamais avoir à deviner si la chaîne renvoyée ressemble à une erreur. La valeur renvoyée est toujours du JSON valide.

// 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.codeSignification
InvalidJsonLa chaîne du modèle n’était pas du JSON analysable.
LimitExceededPlus de barres que l’offre active n’autorise (100 en gratuit, 10,000 en Pro).
SolveErrorLe modèle a été lu mais n’a pas pu être résolu — le plus souvent une matrice de rigidité singulière due à un appui manquant ou insuffisant.
InternalPanicUn défaut du moteur. Merci de le signaler.
InternalSerializationLes résultats n’ont pas pu être sérialisés. Également un défaut à signaler.

Types TypeScript

Le paquet navigateur fournit des types de modèle générés à côté des signatures de fonctions. Ils sont produits à partir du schéma OpenAPI du moteur : ils suivent donc la version publiée au lieu d’être maintenus à la main.

import type {
  FERS,
  ResultsBundle,
} from "@ferscloud/fers-calculation-web/fers-models";

// FERS         — the input model
// ResultsBundle — the `result` payload of a successful envelope
// Both are generated from the engine's OpenAPI schema, so they track
// the published version.

Créditer FERS sur l’offre gratuite

Les résultats de l’offre gratuite portent un objet attribution produit à l’intérieur du solveur. Les résultats Pro, calculés avec un jeton valide, ne le portent pas : le même code affiche donc un crédit en gratuit et reste en marque blanche en Pro, sans aucun réglage à poser.

getFersAttribution, fersAttributionText et createFersBadge sont exportés à ses côtés. Tous acceptent l’enveloppe, le modèle analysé ou le jeu de résultats, et tous ne renvoient rien pour un résultat Pro ou une enveloppe d’erreur.

import { fersAttributionHtml } from "@ferscloud/fers-calculation-web/badge.js";

// Free-tier results carry result.attribution; this renders the credit.
// With a Pro solve token the field is absent and this returns "" — the
// same code white-labels, with no flag to set.
const credit = fersAttributionHtml(res);

Si vous diffusez l’offre gratuite dans une application, merci d’afficher le crédit. C’est la seule chose que l’offre gratuite vous demande. Disponible à partir du moteur 0.2.61.

Limites Pro avec un jeton de calcul

Les limites Pro se débloquent en passant un jeton signé de courte durée émis par le serveur FERS Cloud. Le jeton est vérifié à l’intérieur du module WebAssembly contre une clé publique Ed25519 intégrée au binaire : il ne peut donc pas être falsifié.

Votre serveur détient la clé d’API et l’échange contre un jeton de 30 minutes ; le navigateur ne voit jamais que le jeton. Gardez FERS_API_KEY côté serveur, jamais dans du code navigateur.

// 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 });
}

Appeler le solveur avec un jeton

Si le jeton est absent, expiré ou invalide, le solveur revient à la limite gratuite de barres : il ne lève jamais d’exception. Un vrai échec de calcul arrive toujours sous la forme { ok: false, error } et non d’une exception.

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;
GratuitPro
Nombre maximal de barres10010,000
Fonctioncalculate_from_jsoncalculate_from_json_with_token
Jeton requisNonOui
Durée du jeton30 minutes

Déformée

Activez include_member_deflected_shape dans les options d’analyse du modèle pour obtenir par barre une déformée prête à tracer et exacte vis-à-vis de la charge — le déplacement global de la barre échantillonné sur sa longueur — au lieu de reconstruire la courbe vous-même.

Elle est désactivée par défaut pour alléger la réponse, et absente de member_results si elle n’est pas demandée.

const model = {
  /* … model + load cases … */
  analysis: {
    /* … */
    options: {
      /* … */
      include_member_deflected_shape: true,
    },
  },
};

const res = JSON.parse(calculate_from_json(JSON.stringify(model)));
const mr = res.result.results.loadcases["…"].member_results["1"];

// mr.member_displacements: [{ x_frac, displacement: [dx, dy, dz] }, …]
//   x_frac 0–1 along the member; displacement in the global input frame.

Autres voies

Pages associées

Voir aussi

Questions fréquentes

Ai-je besoin d’une clé d’API pour calculer dans le navigateur ?

Non. calculate_from_json fonctionne sans clé et sans compte pour des modèles jusqu’à 100 barres. Une clé n’est nécessaire que pour émettre le jeton de calcul Pro.

Le modèle quitte-t-il le navigateur ?

Non. Le solveur est compilé en WebAssembly et s’exécute côté client : le modèle et les résultats restent sur la machine de l’utilisateur. Rien n’est envoyé à FERS Cloud.

Pourquoi Vite a-t-il besoin d’une configuration supplémentaire ?

Le paquet navigateur est un module ES WASM qui s’initialise avec un await de niveau supérieur. vite-plugin-wasm prend en charge l’import WebAssembly, et build.target: "esnext" conserve cet await dans la sortie — cela demande Chrome, Edge ou Firefox 89+, ou Safari 15+. webpack a besoin de asyncWebAssembly et asyncFunction dans ses réglages experiments et output. Si vite-plugin-top-level-await figure encore dans votre configuration à cause d’une version antérieure de cette page, vous pouvez le retirer : avec les versions récentes de @swc/core, il fait échouer vite build.

Dois-je exécuter le solveur dans un Web Worker ?

Au-delà de quelques barres, oui. Le solveur est synchrone et gourmand en CPU : l’appeler sur le fil d’interface bloquera le rendu pendant toute la durée du calcul.

Faut-il un appel à init() ?

Non. Les compilations Node et bundler initialisent toutes deux le module WebAssembly automatiquement à l’import. Une ancienne documentation montrait await init() ; ce n’est plus nécessaire.

Que se passe-t-il si le jeton de calcul a expiré ?

Le solveur revient silencieusement aux limites gratuites. Il ne lève jamais d’exception pour un jeton invalide : un modèle dans la limite gratuite continue de se calculer, un modèle plus grand renvoie LimitExceeded.

Quel paquet utiliser côté serveur ?

@ferscloud/fers-calculation, compilé pour Node. Il ne demande aucune configuration de bundler et s’initialise seul à l’import.

Les résultats sont-ils les mêmes que dans l’application web ?

Oui — c’est le même moteur Rust, compilé pour une autre cible. L’application web elle-même utilise cette compilation WebAssembly.