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 🙏

© 2025 – Pkg Stats / Ryan Hefner

tsa-composer

v3.0.3

Published

A lightweight, flexible TypeScript utility for composing template string tag functions with custom parsers and transformations.

Readme

TSA Composer

A lightweight, flexible TypeScript utility for composing template string tag functions with custom parsers and transformations.

Overview

TSA Composer allows you to create custom tagged template literal functions that process template strings and their interpolated values. It provides a clean, composable API for building powerful string processing utilities with full type safety.

Key features:

  • Type-safe template literal tag composition
  • Built-in string joiner for common use cases
  • Custom parser support for advanced transformations
  • Curried API for elegant function composition
  • Zero dependencies
  • Tiny footprint

Installation

# Using npm
npm install tsa-composer

# Using yarn
yarn add tsa-composer

# Using bun
bun add tsa-composer

The Problem TSA Composer Solves

Ever wanted to use your existing functions as tagged template literals without rewriting them? TSA Composer makes it trivial.

Without TSA Composer - You'd need to refactor your function:

// Your existing function
function greet(name: string) {
  return `Hello, ${name}!`;
}

// Call it normally
greet("Alice"); // ✓ Works

// Want to use it as a tag? You need to rewrite it!
greet`Alice`; // ✗ Doesn't work

// You'd have to create a new function:
function greetTag(tsa: TemplateStringsArray, ...values: string[]) {
  const name = tsa.reduce((acc, str, i) => acc + str + (values[i] || ''), '');
  return greet(name); // Lots of boilerplate!
}

With TSA Composer - Just wrap it:

// Your existing function (unchanged!)
function greet(name: string) {
  return `Hello, ${name}!`;
}

// Make it accept template literals - one line!
const greetTag = tsaComposer()(greet);

// Now it works both ways:
greet("Alice");        // ✓ Original still works
greetTag`Alice`;       // ✓ Tagged template works too!
greetTag`${"Alice"}`;  // ✓ With interpolation!

The key insight: TSA Composer handles the template string parsing for you, so your function just receives the data it expects. No refactoring required!

Usage

Basic Usage (with default string joiner)

import tsaComposer from 'tsa-composer';

// Simple string transformation
const greet = tsaComposer()((name: string) => `Hello, ${name}!`);
const greeting = greet`Alice`;
// Result: "Hello, Alice!"

// Uppercase transformer
const shout = tsaComposer()((text: string) => text.toUpperCase());
const loud = shout`Hello ${"world"}!`;
// Result: "HELLO WORLD!"

// Async operations
const prompt = tsaComposer()(async (question: string) => {
  // Your async logic here
  return await getUserInput(question);
});
const answer = await prompt`What is your name? `;

Using the Built-in String Joiner

import tsaComposer, { tsaStringJoiner } from 'tsa-composer';

// Explicitly use tsaStringJoiner (same as default)
const format = tsaComposer(tsaStringJoiner)((text: string) => {
  return text.trim().replace(/\s+/g, ' ');
});

const clean = format`Hello    ${"world"}   and   ${"everyone"}!`;
// Result: "Hello world and everyone!"

Custom Parser Functions

import tsaComposer from 'tsa-composer';

// Custom parser that returns multiple arguments
const customParser = (
  tsa: TemplateStringsArray, 
  ...slots: number[]
): [number, number] => {
  const sum = slots.reduce((a, b) => a + b, 0);
  const avg = slots.length ? sum / slots.length : 0;
  return [sum, avg];
};

const calculate = tsaComposer(customParser)((sum: number, avg: number) => {
  return `Sum: ${sum}, Average: ${avg.toFixed(2)}`;
});

const result = calculate`Values: ${10}, ${20}, ${30}`;
// Result: "Sum: 60, Average: 20.00"

Real-World Examples

// SQL query builder
const sql = tsaComposer()((query: string) => ({
  query: query.trim(),
  execute: () => executeQuery(query),
  explain: () => explainQuery(query)
}));

const userQuery = sql`SELECT * FROM users WHERE id = ${"123"}`;
// userQuery.query: "SELECT * FROM users WHERE id = 123"
// userQuery.execute() - runs the query
// userQuery.explain() - shows query plan

// Logger with metadata
const log = tsaComposer()((message: string) => ({
  message,
  timestamp: new Date().toISOString(),
  level: 'info',
  write: () => console.log(message)
}));

const entry = log`User ${"Alice"} logged in`;
// entry.message: "User Alice logged in"
// entry.timestamp: "2025-10-06T..."
// entry.write() - outputs to console

// HTML sanitizer
const safe = tsaComposer()((html: string) => {
  return html
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;');
});

const safeHtml = safe`<div>${"<script>alert('xss')</script>"}</div>`;
// Result: "&lt;div&gt;&lt;script&gt;alert('xss')&lt;/script&gt;&lt;/div&gt;"

Wrapping Existing Functions (Zero Refactoring!)

Turn any existing function into a tagged template literal with zero changes:

// You already have these functions
function fetchUser(id: string) {
  return fetch(`/api/users/${id}`).then(r => r.json());
}

function validateEmail(email: string): boolean {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

function logError(message: string) {
  console.error(`[ERROR] ${new Date().toISOString()}: ${message}`);
}

// Wrap them with tsaComposer - no changes to originals!
const getUser = tsaComposer()(fetchUser);
const checkEmail = tsaComposer()(validateEmail);
const error = tsaComposer()(logError);

// Now use them as tagged templates:
await getUser`123`;
// Same as: fetchUser("123")

checkEmail`[email protected]`;
// Same as: validateEmail("[email protected]")

error`Failed to load user ${"Alice"}`;
// Same as: logError("Failed to load user Alice")

// The original functions still work normally!
fetchUser("123");
validateEmail("[email protected]");
logError("Some error");

Real-world benefit: Add template literal support to your entire API without touching existing code. Perfect for:

  • Making existing utility functions work as tag functions
  • Adding DSL-like syntax to legacy codebases
  • Creating backward-compatible APIs
  • Gradual migration to tagged template patterns

API## API

tsaComposer()

Creates a template literal tag function using the default tsaStringJoiner parser.

Signature:

tsaComposer(): <Fn extends (arg: string) => any>(
  fn: Fn
) => (tsa: TemplateStringsArray, ...slots: string[]) => ReturnType<Fn>

Returns:

  • A function that takes a transformation function and returns a tagged template literal

Example:

const greet = tsaComposer()((name: string) => `Hello, ${name}!`);
const result = greet`World`;
// Result: "Hello, World!"

tsaComposer(parse)

Creates a template literal tag function with a custom parser.

Signature:

tsaComposer<Parse extends (tsa: TemplateStringsArray, ...slots: any[]) => any[]>(
  parse: Parse
): <Fn extends (...args: any[]) => any>(
  fn: Fn
) => (tsa: TemplateStringsArray, ...slots: Parameters<Parse>[1][]) => ReturnType<Fn>

Parameters:

  • parse - A function that takes TemplateStringsArray and slot values, returns an array of arguments for the transformation function

Returns:

  • A curried function that takes a transformation function and returns a tagged template literal

Example:

const customParser = (tsa: TemplateStringsArray, ...slots: number[]) => {
  return [slots.reduce((a, b) => a + b, 0)];
};

const sum = tsaComposer(customParser)((total: number) => {
  return `Total: ${total}`;
});

const result = sum`${10} + ${20} + ${30}`;
// Result: "Total: 60"

tsaStringJoiner(tsa, ...slots)

Built-in parser that joins template strings and slots into a single string.

Signature:

tsaStringJoiner(
  tsa: TemplateStringsArray,
  ...slots: string[]
): [string]

Parameters:

  • tsa - The template strings array
  • ...slots - The interpolated values

Returns:

  • An array containing the joined string

Example:

import { tsaStringJoiner } from 'tsa-composer';

const tsa = Object.assign(["Hello, ", "!"], { raw: ["Hello, ", "!"] });
const result = tsaStringJoiner(tsa, "World");
// Result: ["Hello, World!"]

Advanced Usage

Custom Parsers for Complex Data

Create parsers that extract and transform data in sophisticated ways:

// Parser that extracts key-value pairs
const kvParser = (
  tsa: TemplateStringsArray,
  ...slots: string[]
): [Map<string, string>] => {
  const text = tsa.reduce((acc, s, i) => acc + s + (slots[i] ?? ""), "");
  const map = new Map<string, string>();
  
  const pairs = text.split(',').map(p => p.trim());
  for (const pair of pairs) {
    const [key, value] = pair.split(':').map(s => s.trim());
    if (key && value) map.set(key, value);
  }
  
  return [map];
};

const config = tsaComposer(kvParser)((settings: Map<string, string>) => ({
  get: (key: string) => settings.get(key),
  has: (key: string) => settings.has(key),
  all: () => Object.fromEntries(settings)
}));

const app = config`host: ${"localhost"}, port: ${"3000"}, env: ${"dev"}`;
// app.get('host') -> "localhost"
// app.all() -> { host: "localhost", port: "3000", env: "dev" }

Composing Multiple Parsers

Build complex transformations by composing parsers:

// Number array parser
const numArrayParser = (
  tsa: TemplateStringsArray,
  ...slots: number[]
): number[] => slots;

const stats = tsaComposer(numArrayParser)((...nums: number[]) => ({
  sum: nums.reduce((a, b) => a + b, 0),
  avg: nums.reduce((a, b) => a + b, 0) / nums.length,
  min: Math.min(...nums),
  max: Math.max(...nums),
  count: nums.length
}));

const analysis = stats`${10} ${20} ${30} ${40} ${50}`;
// Result: { sum: 150, avg: 30, min: 10, max: 50, count: 5 }

Type-Safe Transformations

Leverage TypeScript's type system for compile-time safety:

type User = { id: string; name: string; role: string };

const userParser = (
  tsa: TemplateStringsArray,
  ...slots: User[]
): [User[]] => [slots];

const formatUsers = tsaComposer(userParser)((users: User[]) => {
  return users
    .map(u => `${u.name} (${u.role})`)
    .join(', ');
});

const alice: User = { id: '1', name: 'Alice', role: 'Admin' };
const bob: User = { id: '2', name: 'Bob', role: 'User' };

const team = formatUsers`Team: ${alice}, ${bob}`;
// Result: "Team: Alice (Admin), Bob (User)"
// Type-safe: only User objects can be interpolated

Development

To install dependencies:

bun install

To run tests:

bun test

To build the package:

bun run build

Why Use TSA Composer?

  • Type Safety: Full TypeScript support with proper type inference for inputs and outputs
  • Curried API: Elegant composition pattern separating parsing from transformation
  • Default Convenience: Built-in string joiner for common use cases
  • Custom Parsers: Full control over how template strings are processed
  • Flexibility: Transform template literals into any data type or structure
  • Composability: Build complex transformations from simple building blocks
  • Zero Dependencies: Minimal overhead with no external dependencies
  • Tiny Footprint: Lightweight library that won't bloat your bundle

How It Works

TSA Composer uses a two-step curried approach:

  1. Parser Stage: tsaComposer(parser) - Defines how to extract/transform the template string and its interpolated values into function arguments
  2. Transform Stage: (fn) - Defines what to do with those arguments

This separation allows for:

  • Reusable parsers across different transformations
  • Type-safe composition with full inference
  • Clear separation of concerns between extraction and transformation

Example flow:

const parser = (tsa: TemplateStringsArray, ...slots: string[]) => [joined_string];
const transform = (text: string) => processedResult;
const tagged = tsaComposer(parser)(transform);
const result = tagged`template ${value} string`;

License

MIT © snomiao