WebAssembly (WASM) Usage Guide
The GTFS Validator can run entirely in the browser using WebAssembly. No server required - all validation happens locally in the user's browser.
Installation
npm / yarn / pnpm
npm install @abasisltd/gtfs-guru-wasm
# or
yarn add @abasisltd/gtfs-guru-wasm
# or
pnpm add @abasisltd/gtfs-guru-wasm
CDN
<script type="module">
import init, { validate_gtfs } from 'https://unpkg.com/@abasisltd/gtfs-guru-wasm/gtfs_guru_wasm.js';
</script>
Or use jsDelivr:
<script type="module">
import init, { validate_gtfs } from 'https://cdn.jsdelivr.net/npm/@abasisltd/gtfs-guru-wasm/gtfs_guru_wasm.js';
</script>
Quick Start
Browser (ES Modules)
import init, { validate_gtfs, version } from '@abasisltd/gtfs-guru-wasm';
async function main() {
// Initialize WASM module (required once)
await init();
console.log('Validator version:', version());
// Get file from input
const input = document.getElementById('gtfs-file');
const file = input.files[0];
const bytes = new Uint8Array(await file.arrayBuffer());
// Validate
const result = validate_gtfs(bytes, 'US');
console.log('Valid:', result.is_valid);
console.log('Errors:', result.error_count);
console.log('Warnings:', result.warning_count);
// Parse detailed notices
const notices = JSON.parse(result.json);
notices.forEach(notice => {
console.log(`[${notice.severity}] ${notice.code}: ${notice.title}`);
});
}
main();
Node.js
import { readFile } from 'node:fs/promises';
import { createRequire } from 'node:module';
import init, { validate_gtfs } from '@abasisltd/gtfs-guru-wasm';
async function main() {
const require = createRequire(import.meta.url);
const wasmPath = require.resolve(
'@abasisltd/gtfs-guru-wasm/gtfs_guru_wasm_bg.wasm'
);
await init({ module_or_path: await readFile(wasmPath) });
const bytes = await readFile('gtfs.zip');
const result = validate_gtfs(new Uint8Array(bytes));
console.log('Valid:', result.is_valid);
console.log(JSON.parse(result.json));
}
main();
Using Web Worker (Recommended for UI)
For better user experience, use the Web Worker to avoid blocking the main thread:
import { GtfsValidator } from '@abasisltd/gtfs-guru-wasm/index.js';
const validator = new GtfsValidator();
// Wait for initialization
await validator.waitUntilReady();
// Validate a file (non-blocking)
const result = await validator.validate(file, { countryCode: 'US' });
console.log('Valid:', result.isValid);
console.log('Errors:', result.errorCount);
console.log('Time:', result.validationTimeMs, 'ms');
// Clean up when done
validator.terminate();
API Reference
init(): Promise<void>
Initialize the WASM module. Must be called once before using other functions.
version(): string
Returns the validator version string.
validate_gtfs(bytes, countryCode?): ValidationResult
Validates a GTFS ZIP file.
Parameters:
bytes: Uint8Array- Raw bytes of the GTFS ZIP filecountryCode?: string- Optional ISO 3166-1 alpha-2 country code (e.g., "US", "DE", "RU")
Returns: ValidationResult object with:
json: string- Full validation report as JSONerror_count: number- Number of errorswarning_count: number- Number of warningsinfo_count: number- Number of info noticesis_valid: boolean- True if no errorstimings_json: string- Aggregate loading, per-table parsing, indexing, and per-validator timings as JSON
validate_gtfs_json(bytes, countryCode?): string
Same as validate_gtfs but returns only the JSON string.
GtfsValidator Class
Web Worker wrapper for non-blocking validation:
class GtfsValidator {
constructor(workerUrl?: string);
waitUntilReady(): Promise<void>;
validate(input: File | Blob | ArrayBuffer | Uint8Array, options?: { countryCode?: string }): Promise<ValidationResult>;
version(): Promise<string>;
terminate(): void;
}
Bundler Configuration
Webpack 5
Vite
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
optimizeDeps: {
exclude: ['@abasisltd/gtfs-guru-wasm']
}
});
Next.js
// next.config.js
module.exports = {
webpack: (config) => {
config.experiments = {
...config.experiments,
asyncWebAssembly: true,
};
return config;
}
};
Rollup
// rollup.config.js
import { wasm } from '@rollup/plugin-wasm';
export default {
plugins: [wasm()]
};
TypeScript Support
TypeScript definitions are included. Import types like this:
Memory Considerations
WASM runs in a limited memory environment. For large GTFS feeds:
-
File Size Limits: Browser validation accepts up to 150 MB compressed and 700 MB total uncompressed data. Both limits are checked before parsing.
-
Web Worker: Always use the Web Worker for files over 10MB to avoid UI freezing.
-
Server-Side: For very large feeds (>200MB), consider server-side validation with the CLI or REST API.
The uncompressed limit remains essential because ZIP size alone is misleading. For example, a feed near the compressed-size ceiling may still exceed the 700 MB uncompressed limit. The central-directory guard rejects it before parsing, including smaller but highly compressed archives.
CSV members are streamed from the ZIP directly into the sequential parser, so
the browser does not retain a second full uncompressed copy of large tables.
Validation notices are bounded to 1,000 retained samples per code and severity;
totalNotices and the summary severity counts remain exact.
Threads benchmark
On Apple M3 Pro / Chrome 150, three runs of paris.zip (10 MB compressed,
89 MB uncompressed) averaged 2.242 s single-threaded and 1.818 s with parallel
validators, a 1.23x speedup. Reports matched exactly by SHA-256. Parallel CSV
deserialization was disabled for WASM after it exceeded a five-minute timeout
on the same feed due to shared interner contention; native builds retain it.
Browser Compatibility
| Browser | Minimum Version |
|---|---|
| Chrome | 57+ |
| Firefox | 52+ |
| Safari | 11+ |
| Edge | 16+ |
Error Handling
try {
await init();
const result = validate_gtfs(bytes);
// ...
} catch (error) {
if (error instanceof WebAssembly.RuntimeError) {
console.error('WASM runtime error:', error.message);
} else if (error.message.includes('memory')) {
console.error('Out of memory - file too large');
} else {
console.error('Validation error:', error);
}
}
Building from Source
# Install wasm-pack
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
# Build for web
wasm-pack build crates/gtfs_validator_wasm --target web --release
# Build for browsers with WebAssembly threads (nightly + rust-src required)
RUSTFLAGS='-C target-feature=+atomics,+bulk-memory,+mutable-globals,+simd128 -C link-arg=--shared-memory -C link-arg=--max-memory=4294901760 -C link-arg=--import-memory -C link-arg=--export=__wasm_init_tls -C link-arg=--export=__tls_size -C link-arg=--export=__tls_align -C link-arg=--export=__tls_base' \
rustup run nightly-2026-03-01 wasm-pack build crates/gtfs_validator_wasm --target web \
--release --out-dir pkg-mt -- \
--features threads -Z build-std=panic_abort,std
# Build for Node.js
wasm-pack build crates/gtfs_validator_wasm --target nodejs --release --out-dir pkg-node
# Or use the build script
./scripts/build-wasm.sh
The threaded package is selected automatically when the page is cross-origin
isolated (COOP: same-origin and COEP: require-corp) and
SharedArrayBuffer is available. Otherwise the worker uses the portable
single-threaded package. Use the included Caddy, Nginx, or Cloudflare Pages
headers to enable threads.
Demo
See the interactive demo at crates/gtfs_validator_wasm/demo/index.html or try it online (after npm publish).