mrkeyoor.com_
Sat 08 Aug 21:01 UTC
npmUtilsupdated 08 Aug 2026

nlcst-to-string

nlcst-to-string is a one-function serializer for nlcst natural-language syntax trees. Pass one nlcst node or an array of nodes to the named toString export and it concatenates their plain-text values recursively. A node's own value takes priority over its children, while parent nodes without a value are reduced by joining each child with no added separator. It does not parse prose, print syntax, preserve source ranges, or understand language; it is the small text-extraction step inside unified and retext-style AST work.

Verdict

Install it when you already operate on nlcst and want the conventional, unsurprising text helper. Do not install it to parse language or serialize arbitrary unist trees; its entire contract is value-first recursive concatenation.

API stability5/5Version 4 exports one named function with one behavior: prefer value, otherwise recurse through children or an input array, then join with no separator. The tiny surface leaves little room for accidental churn, and it uses standard nlcst node contracts. The last major's ESM-only and Node 16 changes were ecosystem-level compatibility decisions, not ongoing additions to the serialization model.
Docs4/5The README states the single export, lack of a default export, ESM requirement, Node compatibility, browser and Deno import examples, accepted node-or-array input, and value-before-children behavior. That is nearly complete documentation for such a small function. It does not explicitly spell out invalid-input errors, empty arrays, nodes without value or children, or the loss of position metadata, which requires reading the short implementation.
Maintenance3/5The repository is not archived and GitHub reports no open issues or pull requests, but version 4.0.0 was published in July 2023 and the last repository push was April 2024. The code is small and plausibly finished, and it sits under the established unified collective, yet its README still frames Node 16 as the current release floor even though that runtime is no longer maintained in 2026.
Ecosystem4/5npm records 4,881,475 downloads in the latest week, while the repository itself has only 19 stars, a pattern consistent with a small transitive utility inside the unified and retext graph. It consumes standard nlcst types, ships TypeScript declarations, and works in Node, browsers, and Deno through ESM. Its usefulness is intentionally narrow and does not extend to mdast, hast, parsing, or rendering.

Use it if

  • You already have nlcst nodes from retext or another parser and need their plain-text content
  • You are writing an nlcst transform or lint rule and repeatedly need the text represented by a subtree
  • You need the same helper to accept one node or a contiguous array of sibling nodes
  • You want a tiny typed utility that follows unified collective conventions and has no behavioral configuration
Skip it if

Setup reality

Installation is simple, but this is not a standalone natural-language tool. You need an existing nlcst producer, commonly a retext parser or a package that emits @types/nlcst-compatible nodes. Version 4 is ESM-only, uses a named toString export, and documents Node.js 16 or newer; require('nlcst-to-string') is not the supported interface and there is no default export. Type declarations are bundled and the only package dependency is @types/nlcst, so there is no native build, credential, config file, or runtime service. Output fidelity depends completely on tree shape. TextNode, WhiteSpaceNode, PunctuationNode, SymbolNode, and other literal nodes must carry their exact value. Parent nodes contribute no separators or formatting of their own. If a node has both value and children, value wins and the children are ignored. If a typed node has neither, it contributes an empty string. Passing null, undefined, a primitive, or an object without a type throws an Expected node error, while an empty array is valid and returns an empty string. Positions and other metadata do not affect output. This behavior is ideal for reading plain text from a subtree, but it is not round-trip serialization once a transform removes whitespace nodes, normalizes punctuation, or stores significant content outside value and children.

Patterns

Read text from a word subtreeserialize-word

import { toString } from 'nlcst-to-string';

const word = {
  type: 'WordNode',
  children: [
    { type: 'TextNode', value: 'AT' },
    { type: 'SymbolNode', value: '&' },
    { type: 'TextNode', value: 'T' },
  ],
};

console.log(toString(word)); // AT&T

The parent adds nothing; output is the exact concatenation of child values.

Serialize adjacent sibling nodesserialize-node-list

const nodes = [
  { type: 'WordNode', children: [{ type: 'TextNode', value: 'hello' }] },
  { type: 'WhiteSpaceNode', value: ' ' },
  { type: 'WordNode', children: [{ type: 'TextNode', value: 'there' }] },
];

console.log(toString(nodes)); // hello there

Arrays are accepted directly, but the space appears only because a WhiteSpaceNode is present.

Read a complete sentence nodeserialize-sentence

const sentence = {
  type: 'SentenceNode',
  children: [
    { type: 'WordNode', children: [{ type: 'TextNode', value: 'Hello' }] },
    { type: 'PunctuationNode', value: ',' },
    { type: 'WhiteSpaceNode', value: ' ' },
    { type: 'WordNode', children: [{ type: 'TextNode', value: 'world' }] },
    { type: 'PunctuationNode', value: '!' },
  ],
};

toString(sentence); // Hello, world!

Punctuation and spacing are ordinary valued nodes, not formatting inferred by the serializer.

Inspect subtree text in a lint ruleread-rule-target

function checkWord(node, file) {
  const text = toString(node);
  if (text.toLowerCase() === 'utilize') {
    file.message('Prefer use', { ancestors: [node] });
  }
}

toString does not normalize case or Unicode; apply any comparison policy after extracting the exact node text.

Compare text before and after an AST transformread-before-transform

const before = toString(tree);
normalizeQuotes(tree);
const after = toString(tree);

if (before !== after) {
  console.log({ before, after });
}

Only changes to value fields, child membership, or child order can affect output; position changes do not.

Understand value precedencevalue-precedes-children

const unusualNode = {
  type: 'TextNode',
  value: 'preferred',
  children: [{ type: 'TextNode', value: 'ignored' }],
};

console.log(toString(unusualNode)); // preferred

When value exists on a node, the function returns it immediately and never visits children.

Handle an empty selectionserialize-empty-list

const selected = [];
const text = toString(selected);

console.log(text === ''); // true

An empty node array is valid and joins to an empty string; null and undefined are invalid.

Validate unknown input before serializingguard-untrusted-input

function textFromUnknown(value: unknown): string | undefined {
  if (Array.isArray(value)) return toString(value);
  if (value && typeof value === 'object' && 'type' in value) {
    return toString(value);
  }
  return undefined;
}

The package throws for primitives, null, undefined, and non-array objects without a type property.

Alternatives

PackageRegistryPick it when
mdast-util-to-stringnpmUse it to extract text from Markdown mdast nodes, including its mdast-specific fields and options
hast-util-to-textnpmUse it for HTML hast trees when browser-like rendered-text rules matter
unist-util-visitnpmUse it when you need to inspect or collect selected nodes instead of flattening an entire nlcst subtree