Choose a run workflow
All workflows execute the solver in a worker. Pick one advancement owner for a simulation and await each operation before starting another.
| Workflow | Use when | Completion and cleanup |
|---|---|---|
runSwmm(input, files, options) |
You only need a complete batch result | Returns RunResults and closes the owner |
Simulation.open() then run() |
You need configuration before a batch run or final object reads afterward | Returns RunResults; close the owner yourself |
simulation.steps(options) |
You need progress, observations, or controls | Exhaustion leaves complete; read statistics, finish, then close |
start() then step() or stride() |
Your application owns every advance | null means completion; finish and close explicitly |
Batch execution
run() and runSwmm() start the model, advance it to completion, write the
requested report and binary output, and return one RunResults record. They use
one worker request for the native batch operation. There is no progress callback,
AbortSignal, or hard interrupt while that native call is running.
saveResults defaults to true on run() and start(). Pass
{ saveResults: false } when report-period binary data is not needed. The
returned output is then an empty Uint8Array; detailed report tables are not
written. Report text still contains the finalized native report content.
Use Simulation.open() and run() after configuration edits. Use iteration when
you need progress, intermediate results, quality reads, statistics, or controls.
Interactive execution
for await (const time of simulation) observes each routing step. Pass an
interval to steps() for fewer observations:
import { Simulation, type FileContents } from "@swmmrs/swmmrs";
export async function observe(input: FileContents) {
const simulation = await Simulation.open(input, {}, { threads: 1 });
try {
for await (const time of simulation.steps({ seconds: 60, strict: true })) {
const status = await simulation.status();
console.log(time, status.percentComplete);
}
const statistics = await simulation.statistics();
return { statistics, files: await simulation.finish() };
} finally {
await simulation.close();
}
}
strict defaults to true for both steps() and stride(). Exact observation
boundaries can shorten the final routing step in an interval. Pass
strict: false explicitly to use whole routing steps that may reach or pass the
requested interval. Observation cadence is separate from report cadence.
Read object results and apply controls inside the loop. Do not call another
advance or finish() while the iterator owns advancement. An early break
releases the iterator without finalizing the owner, so you can resume, finish a
partial run, or close it.
To stop an active iterator from another controller, call terminate(). It is a
synchronous request, not a native interrupt. The iterator ends at its next
observation boundary and the owner remains available for reads or cleanup:
import { Simulation, type FileContents } from "@swmmrs/swmmrs";
export async function stopAtFirstObservation(input: FileContents) {
const simulation = await Simulation.open(input, {}, { threads: 1 });
try {
const iterator = simulation.steps({ seconds: 60, strict: true });
const first = await iterator.next();
if (!first.done) simulation.terminate();
await iterator.next();
return await simulation.finish();
} finally {
await simulation.close();
}
}
Native lifecycle controls
Use the lower-level controls when an application needs to separate native stages:
end()closes an active run and entersendedwithout releasing the owner.report()writes requested detailed report tables for an ended run whensaveResultsis enabled. It is idempotent.resetSolver()clears run, result, and output state and returns anopenowner while retaining declared configuration.sleepWorkers()asks active Dynamic Wave workers to sleep. It is valid only whilerunning.close()releases workers and files. It does not interrupt an in-flight native operation.
For valid states and transitions, see lifecycle and ownership.
Compare scenarios
Open a separate owner for each independent model. A rerun after finish() starts
at the configured model start time, not at the previous endpoint. Configuration
and persistent forcings remain until changed.
Use the scenario methods when the starting state matters:
saveHotstart()anduseHotstart(bytes)exchange selected EPA.hsfphysical state with a new compatible run.saveCheckpoint()returns a completeCheckpointBundle; keep its manifest, sidecars, and dependency bytes together.Simulation.resume(bundle)creates a new owner with fuller continuation state.fork()creates an independent in-process child at a quiescent running or complete boundary. Later controls and results do not share state.loadCheckpointState(bundle)loads classified warm state into an already open compatible receiver. The receiver keeps its own dates, declarations, forcing, accounting, and output history. It is not the same operation as resume.
See hotstarts, checkpoints, and forks for the restoration differences.
Each simulation owns its worker. In Node.js 22 or later, threads: 1 is the
default and explicit thread counts use worker_threads. In a browser,
threads: 1 works without isolation; multiple threads require COOP/COEP and
SharedArrayBuffer. Set explicit budgets when running several owners.