npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@swmmrs/swmmrs

v0.2.0

Published

`@swmmrs/swmmrs` runs SWMM in a dedicated worker from a browser or from Node.js 22 and later. A `Simulation` owns its worker, solver state, and in-memory project files. The package is private and is built from this repository.

Readme

swmmrs for JavaScript and TypeScript

@swmmrs/swmmrs runs SWMM in a dedicated worker from a browser or from Node.js 22 and later. A Simulation owns its worker, solver state, and in-memory project files. The package is private and is built from this repository.

See the JavaScript / TypeScript documentation for usage guides and the API reference for all public signatures. After building the package, npm run test:docs type-checks the documentation examples.

Build the browser package

Install Node.js 22 or later, wasm-pack, and the Rust toolchain used by this repository. Then run these commands in js:

npm ci
npm run build
npm start

Open http://127.0.0.1:8080/. npm run build and npm run build:wasm produce threaded WASM in dist/ and serial WASM with unshared memory in dist/serial/. The pinned Rust nightly toolchain rebuilds the standard library for WASM threads. npm run build:wasm -- --dev builds both modules without optimization, and npm run build:ts rebuilds JavaScript and declarations after TypeScript changes.

Alternatively, with Just installed, use the shared build recipes from the repository root:

just js-deps
just js-debug      # Debug threaded and serial WASM, then TypeScript.
just js-release    # Optimized threaded and serial WASM, then TypeScript.

Both profiles write to the same js/dist/ and js/lib/ package paths; the latest build replaces those assets. Cargo dependencies are locked in both modes.

The package exports ES modules. Import index.js when serving this directory, or install @swmmrs/swmmrs as a local package dependency. Node uses node:worker_threads; it does not need browser isolation headers.

Run a model

The API accepts file contents, not host paths or URLs. Valid values are strings, Blob, ArrayBuffer, and array-buffer views. Supporting-file keys are relative paths from the INP.

In Node.js, read files yourself and pass the resulting bytes:

import { readFile } from "node:fs/promises";
import { runSwmm } from "@swmmrs/swmmrs";

const input = await readFile("model.inp");
const files = { "rain.dat": await readFile("rain.dat") };
const { report, output } = await runSwmm(input, files, { threads: 1 });
console.log(report, output.byteLength);

In a browser, fetch contents or pass File and Blob values:

import { runSwmm } from "@swmmrs/swmmrs";

export async function runBrowserModel() {
	const response = await fetch("/models/model.inp");
	if (!response.ok) throw new Error(`INP request failed: ${response.status}`);
	const input = await response.text();
	const rainfall = await fetch("/models/rain.dat");
	if (!rainfall.ok) throw new Error(`Rainfall request failed: ${rainfall.status}`);
	const files = { "rain.dat": await rainfall.blob() };
	return runSwmm(input, files, { threads: 1 });
}

runSwmm() starts the model, runs it to completion, finalizes report text and binary output, and closes the owner. Set saveResults: false in the third argument when report-period binary results are not needed. The returned output is then an empty Uint8Array; detailed report tables are not written.

Inspect and control a run

Use Simulation.open() when the application needs configuration, progress, quality reads, statistics, or runtime controls:

import { Simulation, type FileContents } from "@swmmrs/swmmrs";

export async function inspectAndControl(
	input: FileContents,
	files: Readonly<Record<string, FileContents>> = {},
) {
	const simulation = await Simulation.open(input, files, { threads: 1 });
	try {
		const node = simulation.nodes.get("J1");
		const link = simulation.links.get("OR1");
		await node.configure({ fullDepth: 6 });
		await simulation.options.update({ reportStepSeconds: 60 });

		for await (const time of simulation.steps({ seconds: 60, strict: true })) {
			const { depth } = await node.results();
			await link.setTargetSetting(depth > 2 ? 1 : 0.25);
			console.log(time, depth, (await simulation.status()).percentComplete);
		}

		const statistics = await simulation.statistics();
		const files = await simulation.finish();
		return { statistics, ...files };
	} finally {
		await simulation.close();
	}
}

steps() and stride() default to strict: true, so interval observations request exact boundaries. Pass positive whole seconds. Set strict: false explicitly when whole routing-step boundaries are preferred. step() and stride() return null at natural completion. Collection lookup is synchronous, but configuration, results, quality, statistics, and controls are asynchronous. Returned records are frozen copies. Handles remain owner-bound and expose canonical IDs; related object references are IDs, not foreign handles.

start(), run(), and steps() default to saveResults: true. finish() ends the run, flushes binary output, writes requested detailed report tables when results are enabled, finalizes the report, and retains the owner for reads or reruns. end() writes summary statistics, finalizeReport() appends only the runtime footer, and report() appends the requested detailed tables and footer.

Specialized configuration

Node and link handles do not have public subtype constructors. Their configuration records carry a kind; link records also carry a kind-tagged subtype. The package exposes sparse configuration for node, link, subcatchment, options, aquifer, snowmelt, AMM, RTK, and LID families.

import { type Simulation } from "@swmmrs/swmmrs";

export async function configureSpecialized(simulation: Simulation) {
	await simulation.aquifers.get("A1").configure({ waterTableElevation: 10 });
	await simulation.snowmeltSets.get("Snow").configureSurface("pervious", {
		baseTemperature: 0,
	});
	await simulation.ammModels.get("M1").configure({ hotTemperature: 30 });
	await simulation.unitHydrographs.get("UH1").configure({ rainGage: "Rain" });
	await simulation.replaceAmmAssignments([
		{ node: "J1", model: "M1", area: 1 },
	]);
	await simulation.replaceRdiiAssignments([
		{ node: "J1", unitHydrograph: "UH1", area: 1 },
	]);
	await simulation.lidControls.get("Bio").configure("surface", {
		roughness: 0.1,
	});
	const unit = await simulation.subcatchments.get("S1").lidUnits.access(0);
	await unit.configure({ count: 2 });
}

Definition collections such as timeSeries expose read-only identity handles. They do not provide editable time-series definitions. Change those definitions in the INP and open a new owner. See specialized families.

Quality, statistics, and output

Use node.quality(), link.quality(), or subcatchment.quality() for current pollutant values. The matching collections provide quality snapshots. Per-object statistics and family statistics snapshots are available while the run is running or complete. Capture simulation.statistics() before finish() for system totals, diagnostics, continuity errors, and pollutant balances.

For report-period time series, pass finalized or valid incomplete output bytes to the standalone OutputReader. It provides immutable metadata, exact-case name selectors or integer indexes, integer or ModelTime half-open bounds, lowMemory reads, duplicate selections, and system or family series:

import { OutputReader, type FileContents } from "@swmmrs/swmmrs";

export async function readOutput(output: FileContents) {
	const reader = await OutputReader.open(output);
	try {
		return await reader.nodeSeries("J1", "invert_depth");
	} finally {
		await reader.close();
	}
}

See binary output reader.

Lifecycle and scenarios

resetSolver() clears stale run and output state while retaining declarations and returns the owner to open. sleepWorkers() asks active Dynamic Wave workers to sleep while running. terminate() cooperatively ends an active iterator at its next observation boundary. run() and runSwmm() are single native worker requests with no progress callback, AbortSignal, or hard interrupt. close() is cleanup, not interruption.

For continuation, use saveHotstart() and useHotstart(bytes) for selected EPA .hsf physical state. Use saveCheckpoint() for a complete CheckpointBundle containing a manifest and sidecar/dependency bytes, then Simulation.resume(bundle) for fuller continuation. fork() creates an independent in-process child. loadCheckpointState(bundle) loads classified warm state into an already opened compatible receiver, which keeps its own dates, declarations, forcings, accounting, and output history. The native checkpoint JSON alone is not a complete JavaScript bundle. See hotstarts, checkpoints, and forks.

Browser assets and threads

Serve index.js, worker.js, the generated lib/ directory, and all of dist/, including dist/snippets/ and dist/serial/, from the same build. Serve .wasm with Content-Type: application/wasm. Both WASM builds expose the same API.

Serial browser execution with threads: 1 needs no cross-origin isolation. Browser threads > 1 requires these response headers and globalThis.crossOriginIsolated === true:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: same-origin

Browser defaults are navigator.hardwareConcurrency || 1 on an isolated page and 1 otherwise. Node defaults to 1; explicit counts use worker_threads when within host capacity. The browser setup has been tested with Chromium, not every browser or bundler.

Handle errors

Native failures preserve code, operation, and partial report when available. Catch SwmmError or a specific SolverError, LifecycleError, ValidationError, ObjectNotFoundError, WorkerError, or OutputError. JavaScript-side type and range failures use TypeError or RangeError. Close failed owners and open a new one for retries.

Run checks

The commands below build their WASM prerequisites:

npm run typecheck
npx playwright install chromium
npm test
npm run test:package
npm run test:docs

Set CHROMIUM_PATH to use an installed Chromium executable. Browser tests run WASM and include serial and parallel paths. The documentation checker type-checks all typescript fences as standalone modules.

Find the implementation

src/swmmrs holds the public TypeScript API, including simulation ownership, objects, snapshots, output, and exceptions. protocol.ts, client.ts, and worker.ts are private worker plumbing. src/native contains Rust adapters organized by object family. The root index.d.ts re-exports declarations built from the TypeScript source.