Usar FERS desde JavaScript

El solver de FERS se publica como compilación WebAssembly del mismo motor en Rust que impulsa la aplicación web. Se ejecuta en el navegador o en Node, calcula sin ida y vuelta al servidor y no necesita clave de API en el plan gratuito.

Instalación

Del mismo motor se publican dos paquetes con una API idéntica: solo cambian la instalación y la configuración del empaquetador. Elija -web para todo lo que pase por un empaquetador y el paquete sin sufijo para Node en el servidor.

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

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

Ambos paquetes llevan la versión del motor y avanzan juntos. Fíjelos a la misma versión exacta en lugar de a un rango con acento circunflejo.

Configurar el empaquetador

@ferscloud/fers-calculation-web es un módulo ES de WASM que se inicializa mediante un await de nivel superior. La mayoría de los empaquetadores necesitan un ajuste único de configuración; el paquete de Node no necesita ninguno.

Vite

Añada el plugin y defina después un target de compilación que admita await de nivel superior.

npm i -D vite-plugin-wasm

Configuración de 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 y webpack

Transpile el paquete y active WebAssembly asíncrono. asyncFunction es necesario porque el módulo se inicializa con un await de nivel superior.

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

El solver es síncrono y consume CPU. Para modelos grandes, ejecútelo dentro de un Web Worker para que el hilo de interfaz siga respondiendo.

Calcular un modelo

Sin llamada a init() y sin clave de API. El plan gratuito cubre cualquier modelo de hasta 100 barras.

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

El envoltorio de respuesta

Cada llamada al solver devuelve un envoltorio JSON, de modo que analiza una vez y bifurca según ok: nunca tiene que olfatear si la cadena devuelta parece un error. El valor devuelto siempre es JSON válido.

// 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.codeSignificado
InvalidJsonLa cadena del modelo no era JSON analizable.
LimitExceededMás barras de las que permite el plan activo (100 gratis, 10,000 Pro).
SolveErrorEl modelo se leyó pero no pudo resolverse; casi siempre una matriz de rigidez singular por un apoyo ausente o insuficiente.
InternalPanicUn fallo del motor. Le agradecemos el aviso.
InternalSerializationLos resultados no pudieron serializarse. También es un fallo que conviene reportar.

Tipos de TypeScript

El paquete de navegador incluye tipos de modelo generados junto a las firmas de las funciones. Se generan a partir del esquema OpenAPI del motor, de modo que siguen a la versión publicada en lugar de mantenerse a mano.

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.

Acreditar a FERS en el plan gratuito

Los resultados del plan gratuito llevan un objeto attribution generado dentro del solver. Los resultados Pro, calculados con un token válido, no lo llevan: el mismo código muestra el crédito en el plan gratuito y queda en blanco con Pro, sin ninguna opción que activar.

Junto a él se exportan getFersAttribution, fersAttributionText y createFersBadge. Todos aceptan el envoltorio, el modelo analizado o el conjunto de resultados, y todos devuelven vacío para un resultado Pro o un envoltorio de error.

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 usa el plan gratuito en una aplicación, muestre el crédito. Es lo único que el plan gratuito le pide. Disponible desde el motor 0.2.61.

Límites Pro con un token de cálculo

Los límites Pro se desbloquean pasando un token firmado de corta duración emitido por el servidor de FERS Cloud. El token se verifica dentro del módulo WebAssembly contra una clave pública Ed25519 incrustada en el binario, así que no puede falsificarse.

Su servidor guarda la clave de API y la intercambia por un token de 30 minutos; el navegador solo ve el token. Mantenga FERS_API_KEY en el servidor, nunca en código de navegador.

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

Llamar al solver con un token

Si el token falta, ha caducado o no es válido, el solver vuelve al límite gratuito de barras: nunca lanza una excepción. Un fallo real de cálculo sigue llegando como { ok: false, error } y no como excepción.

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;
GratisPro
Barras máximas10010,000
Funcióncalculate_from_jsoncalculate_from_json_with_token
Requiere tokenNo
Validez del token30 minutos

Deformada

Active include_member_deflected_shape en las opciones de análisis del modelo para obtener por barra una deformada lista para dibujar y exacta respecto a la carga — el desplazamiento global de la barra muestreado a lo largo de su longitud — en lugar de reconstruir la curva usted mismo.

Está desactivada por defecto para que la respuesta sea ligera, y se omite de member_results cuando no se solicita.

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.

Otras vías

Páginas relacionadas

Véase también

Preguntas frecuentes

¿Necesito clave de API para calcular en el navegador?

No. calculate_from_json funciona sin clave y sin cuenta para modelos de hasta 100 barras. La clave solo hace falta para emitir el token de cálculo Pro.

¿El modelo sale del navegador?

No. El solver está compilado a WebAssembly y se ejecuta en el cliente, de modo que el modelo y los resultados permanecen en la máquina del usuario. No se envía nada a FERS Cloud.

¿Por qué Vite necesita configuración adicional?

El paquete de navegador es un módulo ES de WASM que se inicializa con un await de nivel superior. vite-plugin-wasm se encarga de la importación de WebAssembly, y build.target: "esnext" mantiene ese await en la salida — requiere Chrome, Edge o Firefox 89+, o Safari 15+. webpack necesita asyncWebAssembly y asyncFunction en sus ajustes de experiments y output. Si vite-plugin-top-level-await sigue en tu configuración por una versión anterior de esta página, puedes quitarlo: con versiones recientes de @swc/core hace fallar vite build.

¿Debo ejecutar el solver en un Web Worker?

A partir de unas pocas barras, sí. El solver es síncrono y consume CPU, así que llamarlo en el hilo de interfaz bloqueará el renderizado durante todo el cálculo.

¿Necesito una llamada a init()?

No. Tanto la compilación de Node como la de empaquetador inicializan el módulo WebAssembly automáticamente al importarlo. Documentación antigua mostraba await init(); ya no es necesario.

¿Qué ocurre si el token de cálculo ha caducado?

El solver vuelve silenciosamente a los límites gratuitos. Nunca lanza excepción por un token inválido, así que un modelo dentro del límite gratuito sigue calculándose y uno mayor devuelve LimitExceeded.

¿Qué paquete uso en el servidor?

@ferscloud/fers-calculation, compilado para Node. No necesita configuración de empaquetador y se inicializa solo al importarlo.

¿Los resultados son los mismos que en la aplicación web?

Sí: es el mismo motor en Rust, compilado para otro destino. La propia aplicación web usa esta misma compilación WebAssembly.