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

@galitz-matt/ts-struct

v0.1.5

Published

Structured document IR and builder DSL for safe code generation

Readme

ts-struct

Problem Statement

Programmatic generation of TS source code is commonly implemented via string concatenation. This approach does not scale well with complexity. Particularly, this approach:

  • conflates layout management with surface syntax construction
  • requires manual depth tracking
  • introduces fragile formatting logic

This project introduces a minimal structural layout algebra that models document nesting explicitly.

Algebra of Layout

Preliminaries

Let:

  • $String$ be the set of all strings
  • $List(X)$ denote ordered finite lists over set $X$

Def'n 1: Layout Node

A layout node is defined inductively as

$$Node := Line(s) | Block(open, body, close) | Seq(nodes)$$

where:

  • $s \in String$
  • $open, close \in String$
  • $body \in List(Node)$
  • $nodes \in List(Node)$

We define $Line(s)$, $Block(open, body, close)$, $Seq(nodes)$ below

Def'n 2: Line

$$Line(s)$$

A $Line$ represents a single text unit rendered at a given indentation depth. It is a leaf in the layout tree.

Def'n 3: Block

$$Block(open, body, close)$$

A $Block$ introduces structurual nesting. Its body is rendered at indentation depth $d + 1$, its delimiters ($open$, $close$) at depth $d$

Def'n 4: Seq

$$Seq(nodes)$$

A $Seq$ represents ordered sibling composition. It does not introduce indentation; each child is rendered at the same depth.

Def'n 5: Document

A $Document$ is defined as:

$$Document := Node$$

In practice, a file is represented as:

$$Seq(nodes)$$

The root is simply a $Node$. This ensures the layout algebra is closed which enables composability which we see below.

Def'n 5: Rendering

Let:

$$render: Node \times \N \rightarrow String$$

where the second argument denotes indentation depth. Let indent(d) denote a string consisting of d tab characters.

Line $$render(Line(s), d) = indent(d) + s$$

Block $$ render(Block(open, body, close), d) =\ indent(d) + open\ + newline\ render(body, d+1)\ + newline\ indent(d) + close $$

Seq $$ render(Seq(nodes), d) =\ join(render(n, d) \text{ for each } n \in nodes \text{ with newline}) $$

Rendering derives indentation from structural depth.

Lowering

Def'n 6: Surface Syntax Fragment

A surface syntax fragment represents a TS construct at a semantic level. Examples include:

  • Function declarations
  • Conditional chains
  • Loop constructs
  • Parameter lists

Fragments describe semantic structure.

Def'n 7:

A lowering function is a total function:

$$ lower_C: C \rightarrow Node $$

where:

  • C is a TS surface construct.

  • The output is a layout $Node$

Lowering encodes surface syntax into layout nodes.

Design Considerations

Structural Closure

The layout algebra must be closed under lowering.

Formally:

$$\forall C, \exists lower_C : C \rightarrow Node$$

This requirement motivated the inclusion of $Seq$

Without $Seq$, certain constructs (e.g. if-chains) would lower to multiple sibling nodes, breaking closure.

$Seq$ serves as the composition operator that ensures:

$$ Node \times Node \rightarrow Node$$

Why Closure is Useful (in practice)

Recall, closure in this context means:

every lowering function returns a $Node$

Formally: $$lower_C: C \rightarrow Node$$

This ensures every construct is composable.

Without closure some lowering functions return Node, others Node[]

We illustrate why this is problematic with an example:

Suppose we have the lowering function ifChain which lowers to multiple sibling blocks representing the branches:

if (x) { ... }
else if (y) { ... }
else { ... }

Without $Seq$, ifChain must return Node[]. Now our lowering functions must handle two shapes, which forces spreads, flattening and special cases which is bad for ergonomics.

Composition would look like:

fn(signature,
    line("..."),
    ...ifChain(...),
)

The user must know that ifChain returns many which is leaky. With Seq, ifChain returns:

seq(
    block(if ...),
    block(else if ...),
    block(else ...)
)

which is Node. Composition is now uniform:

fn(sig,
    line("..."),
    ifChain(...)
)

The user is free to compose structures without thinking about underlying representation.

Guarantees

The system guarantees

  • Proper indentation
  • Proper nesting
  • Determinstic formatting

It does not validate

  • Type correctness
  • TS grammar correctness
  • Semantic validity

How to Use

Installation

npm install ts-struct

1. Construct Layout Nodes Directly

At the lowest level, you can build layout trees manually:

import { line, braceBlock, seq, render } from "@galitz-matt/ts-struct";

const doc = seq(
  line("const x = 1"),
  braceBlock("if (x > 0)",
    line("console.log(x);")
  )
);

console.log(render(doc));

Output:

const x = 1
if (x > 0) {
	console.log(x);
}

You never manage indentation explicitly, indentation is derived from structure.

2. Using Surface Syntax Fragments

Fragments represent semantic constructs. They are lowered into layout nodes.

Example:

import {
  typeDef,
  unionType,
  objectType,
  typeProp,
  literalType
} from "@galitz-matt/ts-struct";

const state = typeDef(
  "State",
  unionType(
    objectType(
      typeProp("kind", literalType("Idle"))
    ),
    objectType(
      typeProp("kind", literalType("Running"))
    )
  )
);

console.log(render(state));

Output:

type State =
	| {
		kind: "Idle";
	}
	| {
		kind: "Running";
	}

3. Variable Declarations

import {
  constant,
  objectExpr,
  exprProp,
  literalExpr,
  refType
} from "@galitz-matt/ts-struct";

const initialState = constant(
  "initialState",
  objectExpr(
    exprProp("kind", literalExpr("Idle"))
  ),
  refType("State")
);

console.log(render(initialState));

Output:

const initialState: State = {
	kind: "Idle",
}

4. Functions and Control Flow

import {
  fn,
  fnSig,
  params,
  stdParam,
  switchOn,
  caseOf,
  otherwise,
  ret,
  refType
} from "@galitz-matt/ts-struct";

const reducer = fn({
    signature: fnSig({
        export: true,
        name: "reducer",
        params: params(
            stdParam("state", refType("State")),
            stdParam("event", refType("Event"))
        )
    })
},
    switchOn("state.kind",
        caseOf(literalExpr("Idle"),
            ret(literalExpr("state"))
        ),
        otherwise(
            ret(literalExpr("state"))
        )
    )
);

console.log(render(reducer));

Output:

export function reducer(state: State, event: Event): State {
	switch (state.kind) {
		case "Idle":
			return state;
		default:
			return state;
	}
}