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

@thi.ng/proctext

v1.0.51

Published

Extensible procedural text generation engine with dynamic, mutable state, indirection, randomizable & recursive variable expansions

Readme

@thi.ng/proctext

npm version npm downloads Mastodon Follow

[!NOTE] This is one of 213 standalone projects, maintained as part of the @thi.ng/umbrella monorepo and anti-framework.

🚀 Please help me to work full-time on these projects by sponsoring me on GitHub. Thank you! ❤️

About

Extensible procedural text generation engine with dynamic, mutable state, indirection, randomizable & recursive variable expansions.

[!NOTE] The generated output of the code examples in this readme might vary due to the inherent randomness in the system. Please consult the controlled randomness section for further information about deterministic outputs.

Features

The generator works with a simple plain text format, supporting the following features:

Line comments

Lines starting with # are comments and completely ignored, and invisible in the output. No block comments are available.

Multi-value variables

Text generation and expansion relies on variables, which are defined inline (inside the text given to the generator). New variables can be defined as follows and each variable can define one or more possible values (one value per line!):

[name]
alice
bob
ciara

If more than a single value is given, a semi-random choice will be made for each instance/occurrence of the var...

Variables are case-sensitive (i.e. NAME is a different var than name) and can also be re-defined overridden in different parts of the document.

Variables are referenced via: <name>. By default, an error will be thrown if a variable cannot be resolved. Via the missing option given to generate() a default fallback value can be defined, which when given will not trigger an error in these cases.

[!NOTE] The special variable reference <empty> can be used to produce an empty string (or used as single value placeholder option inside another variable definition). This variable can not be redefined.

import { generate } from "@thi.ng/proctext";

const { result } = await generate(`
[name]
alice
bob

[action]
walked
cycled
ran
swam

[place]
office
shop
cafe
lake

[time]
this morning
last night
a week ago

# The actual generated text...
<time> <name> <action> to the <place>.
`);

console.log(result);

// last night alice cycled to the cafe.

Recursive variable expansion

Variable values can contain cyclic references and/or references to other variables.

[!IMPORTANT] In the case of cyclic refs, you MUST provide at least one non-cyclic option to avoid infinite recursion!

import { generate } from "@thi.ng/proctext";

const { result } = await generate(`
[activity]
walking and <activity_alt>
hiking and <activity_alt>
# the next option is cyclic...
cycling, <activity>

[activity_alt]
sleeping
coding

I enjoy <activity>.
`);

console.log(result);

// I enjoy cycling, walking and coding.

Variable assignments

New variables can be declared & initialized via <varname=existing>. This will create a new variable varname with a choice value of variable existing (which must already be defined).

import { generate } from "@thi.ng/proctext";

const { result } = await generate(`
[name]
Alice
Bob
Charlie
Dora

Let's pick some random names: <hero1=name> and <hero2=name>.

But now we can use them as constants:
<hero1>, <hero1>, <hero1> & <hero2>, <hero2>, <hero2>
`);

console.log(result);

// Let's pick some random names: Dora and Bob.
//
// But now we can use them as constants:
// Dora, Dora, Dora & Bob, Bob, Bob

Hidden assignments

Normally, variable assignments are also causing the chosen value to be emitted as part of the generated text. Sometimes it's useful to not do so and suppress/hide output by using the prefix ! as part of the assignment, like so:

import { generate } from "@thi.ng/proctext";

const { result } = await generate(`
[name]
Asterix
Obelix

# hidden assignment
<!hero=name>
My name is <hero>.`);

console.log(result);

// My name is Asterix.

Dynamic, indirect variable references

Given a variable assignment like <hero=name>, we can use dots in the name to lookup derived variables with their name based on current value(s).

For the following example:

  • assume the assignment of <hero=name> picked value a
  • now the var lookup <hero.job> will look for a var called a.job
  • there's only one option for a.job, i.e. astronaut
  • the lookup hero.job.desc will then look for astronaut.desc
import { generate } from "@thi.ng/proctext";

const { result } = await generate(`
[name]
A
B

[A.job]
astronaut

[B.job]
baker

[astronaut.desc]
shooting for the stars
flying to the moon

[baker.desc]
baking bread

<hero=name> is a <hero.job>, <hero.job.desc>.`);

console.log(result);

// B is a baker, baking bread.

Modifiers

Variable references can be optionally processed via one or more modifiers via <varname;mod> or <varname;mod1;mod2...>. Each modifier is a simple async function receiving a string and transforming it into another string. These functions are async to allow modifiers consulting external data sources/processes...

The following modifiers are provided by default:

  • uc: uppercase
  • lc: lowercase
  • cap: capitalize

Custom modifiers can be provided via options given to generate():

import { generate } from "@thi.ng/proctext";

const DIRECTIONS = {
    e: "east",
    w: "west",
    n: "north",
    s: "south",
    d: "down",
    u: "up",
};

const ROOMS = {
    house: {
        desc: "very homely",
        exits: { e: "garden", u: "rafters" },
    },
    rafters: {
        desc: "pretty dark",
        exits: { d: "house" },
    },
    garden: {
        desc: "super lush",
        exits: { w: "house" },
    },
};

// partially data-driven template
const { result } = await generate(`
[room]
${Object.keys(ROOMS).join("\n")}

You're in the <here=room> (exits: <here;exits;uc>)...
It feels <here;desc> here.
`, {
    // custom modifiers (will be added to existing defaults)
    mods: {
        // produce a list of exits for given room ID
        exits: async (id) => Object.keys(ROOMS[id].exits).map((dir) => DIRECTIONS[dir]).join(", "),
        // return a room's description
        desc: async (id) => ROOMS[id].desc
    }
});

console.log(result);

// You're in the house (exits: EAST, UP)...
// It feels very homely here.

Controlled randomness

The selection of variable choices is determined by the configured PRNG instance, the number of recent values selected/memorized and the number of attempts made to pick a new unique choice. All of these can be configured via options given to generate():

import { generate } from "@thi.ng/proctext";
import { SYSTEM, XsAdd } from "@thi.ng/random";

// here we show default options used
generate(`...`, {
    rnd: SYSTEM,
    maxHist: 1,
    maxTrials: 10
});

// using a seeded PRNG for deterministic behavior/output
generate(`...`, { rnd: new XsAdd(0xdecafbad) });

The maxHist = 1 means that for each variable only the last value picked will be memorized. If more than 1 value options exists for a variable, successive var expansions will not result in duplicates, e.g.

[name]
a
b

<name> <name> <name>

...will only ever result in one of these two sequences: a b a or b a b.

Status

ALPHA - bleeding edge / work-in-progress

Search or submit any issues for this package

Installation

yarn add @thi.ng/proctext

ESM import:

import * as pro from "@thi.ng/proctext";

Browser ESM import:

<script type="module" src="https://esm.run/@thi.ng/proctext"></script>

JSDelivr documentation

For Node.js REPL:

const pro = await import("@thi.ng/proctext");

Package sizes (brotli'd, pre-treeshake): ESM: 1.34 KB

Dependencies

Note: @thi.ng/api is in most cases a type-only import (not used at runtime)

Usage examples

One project in this repo's /examples directory is using this package:

| Screenshot | Description | Live demo | Source | |:-----------------------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------|:------------------------------------------------------|:-----------------------------------------------------------------------------------| | | Procedural stochastic text generation via custom DSL, parse grammar & AST transformation | Demo | Source |

API

Generated API docs

Authors

If this project contributes to an academic publication, please cite it as:

@misc{thing-proctext,
  title = "@thi.ng/proctext",
  author = "Karsten Schmidt",
  note = "https://thi.ng/proctext",
  year = 2023
}

License

© 2023 - 2026 Karsten Schmidt // Apache License 2.0