WASM API
The npm package @stormlightlabs/lectito exposes Lectito to JavaScript through
wasm-bindgen.
It supports browser, web worker, bundler, and Node.js use.
npm install @stormlightlabs/lectito
The Rust crate is still named lectito-wasm.
Build Targets
wasm-pack build crates/wasm --target bundler
wasm-pack build crates/wasm --target web
wasm-pack build crates/wasm --target nodejs
wasm-pack writes lectito_wasm.d.ts with the public TypeScript API.
Initialization
Bundler builds initialize when imported:
import { extract } from "@stormlightlabs/lectito";
const article = extract(html, "https://example.com/post");
The web target needs the async initializer:
import init, { extract } from "./lectito_wasm.js";
await init();
const article = extract(html, "https://example.com/post");
The nodejs target initializes when loaded:
const { extract } = require("./lectito_wasm.js");
const article = extract(html, "https://example.com/post");
Functions
export function extract(
html: string,
baseUrl?: string | null,
options?: ReadabilityOptions | null,
): Article | null;
export function extractWithDiagnostics(
html: string,
baseUrl?: string | null,
options?: ReadabilityOptions | null,
): ExtractionReport;
export function isProbablyReadable(html: string, options?: ReadableOptions | null): boolean;
export function cleanHtml(
html: string,
baseUrl?: string | null,
options?: CleanHtmlOptions | null,
): string | null;
export function htmlToMarkdown(html: string): string;
export function markdownToHtml(markdown: string, options?: MarkdownOptions | null): string;
export function analyzeTropes(text: string, options?: TropeOptions | null): TropeReport;
Types
Option fields use camelCase. Returned article fields keep the core Rust snake_case names.
export type MediaRetention = "none" | "conservative" | "article" | "all";
export interface ReadabilityOptions {
maxElemsToParse?: number | null;
nbTopCandidates?: number;
charThreshold?: number;
contentSelector?: string | null;
siteProfiles?: string[];
mobileViewportWidth?: number | null;
classesToPreserve?: string[];
keepClasses?: boolean;
disableJsonLd?: boolean;
linkDensityModifier?: number;
mediaRetention?: MediaRetention;
}
export interface ReadableOptions {
minContentLength?: number;
minScore?: number;
}
export interface MarkdownOptions {
gfm?: boolean;
footnotes?: boolean;
math?: boolean;
allowRawHtml?: boolean;
}
export type TropePreset = "lenient" | "balanced" | "strict";
export type TropeCategory =
| "word_choice"
| "sentence_structure"
| "paragraph_structure"
| "tone"
| "formatting"
| "composition";
export type TropeSignal = "low" | "medium" | "high" | "very_high";
export type Severity = "low" | "medium" | "high";
export interface TropeOptions {
preset?: TropePreset;
minSeverity?: Severity;
includeSuggestions?: boolean;
}
export interface TextRange {
start_byte: number;
end_byte: number;
start_utf16: number;
end_utf16: number;
start_line: number;
start_column: number;
end_line: number;
end_column: number;
}
export interface RuleSource {
file: string;
heading: string;
}
export interface TropeFinding {
rule_id: string;
category: TropeCategory;
severity: Severity;
range: TextRange;
matched_text: string;
message: string;
suggestion: string | null;
source: RuleSource | null;
}
export interface TropeSummary {
word_count: number;
finding_count: number;
findings_per_1000_words: number;
category_counts: Partial<Record<TropeCategory, number>>;
severity_counts: Partial<Record<Severity, number>>;
top_categories: TropeCategory[];
repeated_rules: string[];
}
export interface TropeReport {
score: number;
signal: TropeSignal;
findings: TropeFinding[];
summary: TropeSummary;
}
export type CleanHtmlOptions = ReadabilityOptions;
export interface Article {
title?: string | null;
byline?: string | null;
dir?: string | null;
lang?: string | null;
content: string;
markdown: string;
text_content: string;
length: number;
excerpt?: string | null;
site_name?: string | null;
published_time?: string | null;
image?: string | null;
domain?: string | null;
favicon?: string | null;
}
export interface ExtractionReport {
article: Article | null;
diagnostics: unknown;
}
mediaRetention accepts "none", "conservative", "article", or "all".
Trope diagnostics
import { analyzeTropes } from "@stormlightlabs/lectito";
const report = analyzeTropes("It is worth noting that this serves as an example.");
console.log(report.signal, report.findings);
analyzeTropesreturns heuristic writing diagnostics. It reports matched phrases and structures; it does not determine authorship or prove that a person or model wrote the text.
minSeverity filters findings at the requested severity or higher.
includeSuggestions controls the nullable suggestion field. The default
preset is "balanced". The "lenient" preset removes low-severity findings
and requires stronger repeated evidence. The "strict" preset includes
lower-evidence structural and document findings. Report ranges include both
byte and UTF-16 offsets so callers can decorate JavaScript strings without
converting the input first.
The source field points to the vendored tropes.md catalog and its source
heading. Custom rules are not supported by the current WASM API; it uses the
bundled rule catalog only.
Errors
Functions throw JavaScript Error objects for invalid base URLs, oversized
documents, option conversion failures, and serialization failures.
Sanitization
cleanHtml performs Lectito article cleanup. It is not a complete
untrusted-HTML security policy.
Browser integrations that accept arbitrary HTML should run a dedicated sanitizer such as DOMPurify before passing content into Lectito. Sanitize again before rendering returned HTML when the original input is untrusted.
Release Checks
Run the WASM tests and build all supported package targets:
pnpm --dir packages/web exec wasm-pack test --node ../../crates/wasm
pnpm --dir packages/web exec wasm-pack build ../../crates/wasm --target bundler --out-dir ../../target/wasm-pack/bundler
pnpm --dir packages/web exec wasm-pack build ../../crates/wasm --target web --out-dir ../../target/wasm-pack/web
pnpm --dir packages/web exec wasm-pack build ../../crates/wasm --target nodejs --out-dir ../../target/wasm-pack/nodejs
The build commands run wasm-opt; restricted sandboxes may need permission to
execute it.