@synstack/text
v1.3.0
Published
String templating as it was meant to be
Maintainers
Readme
@synstack/text
String templating as it was meant to be
This is a strongly opinionated implementation of string templating. It's basically JSX for text and solves many quirks of string interpolation and formatting.
What is it for ?
Turns this:
import { t } from "@synstack/text";
const items = ["Hello", "World"];
const text: string = await t`
Value: ${items.join(", ")}
Promise: ${Promise.resolve(items.join(", "))}
Callables:
Callable: ${() => items.join(", ")}
Callable Promise: ${() => Promise.resolve(items.join(", "))}
List of items:
${() => Promise.resolve(items.map((item) => `- ${item}`))}
`;Into this:
Value: Hello, World
Promise: Hello, World
Callables:
Callable: Hello, World
Callable Promise: Hello, World
List of items:
- Hello
- WorldWhat's baked in ?
- Promises even nested in arrays are resolved in parallel
- Array values are joined with a newline
- Text is trimmed
- Base indentation is removed
- Nested indentation is preserved for multi-line values
- Returned value is either a string or a promise of a string based on interpolated values
Installation
npm install @synstack/text
# or
yarn add @synstack/text
# or
pnpm add @synstack/textFeatures
Text formatting
twill automatically trim unecessary whitespaces and indentations. This allows your code to remain indented and readable while still being able to use the template.twill auto-join array values with a newline.twill propagate indentation to each line of the nested values.
const text: string = t`
Hello
${"- Item 1\n- Item 2"}
World
`;Will be transformed into:
Hello
- Item 1
- Item 2
WorldAsync support
tautomatically detects if the template is async or not and handles it accordingly.- When
tis async, it resolves all values in parralel with aPromise.all - If you want to force serial execution, use an
awaitexpression.
const sync: string = t`Hello ${"World"}`;
const async: Promise<string> = t`Hello ${Promise.resolve("World")}`;
/*
Asuming retrieveUserName and retrieveUserEmail are async functions
Both queries will be resolved in parallel
*/
const async: Promise<string> = t`Hello ${retrieveUserName()} ${retrieveUserEmail()}`;Callable values
- You can use any function without argument as a template value.
twill call the function and then handle it'ssyncorasyncstate through the async support logic.
/*
Asuming retrieveUserName and retrieveUserEmail are async functions with no arguments
Both queries will be called and resolved in parallel
*/
const async: Promise<string> = t`Hello ${retrieveUserName} ${retrieveUserEmail}`;Arrays
- Array values are resolved in parrallel with
Promise.all - The resulting strings are joined with a newline
- The indentation is preserved for each line
const items = [Promise.resolve("Hello"), Promise.resolve("World")];
const text: Promise<string> = t`
This is a list of items:
${items}
`;
console.log(await text);Will output:
This is a list of items:
Hello
WorldExtra objects
[!NOTE] This feature was built to seemlessly integrate inline content blocks in LLM messages. e.g. Adding images, tool responses, etc.
tis able to handle non-string objects as values. As long as they have atypeproperty.- The value will be
JSON.stringifyed and added to the template. - The returned value will be
string & { __extra: TExtraObject }
- The value will be
- The value can then be accessed through the
tParse(resultingString)property. - You can constrain the type of the extra object by using a type assertion from
Text.String - You can infer the value type of the extra object by using a type assertion from
Text.ExtraObject.Infer
import { t, tParse, type Text } from "@synstack/text";
// @ts-expect-error - The non-matching extra object will be rejected
const textFail: Text.String<{ type: "extra"; value: string }> =
t`Hello ${{ type: "other-type" as const, value: "Hello" }} World`;
// string & { __extra: { type: "extra"; value: string } } is equivalent to Text.String<{ type: "extra"; value: string }>
const text: string & { __extra: { type: "extra"; value: string } } =
t`Hello ${{ type: "extra" as const, value: "Hello" }} World`;
console.log(tParse(text));
// ["Hello ", { type: "extra", value: "Hello" }, " World"]Configuration Options
You can configure text processing behavior using Text.options():
import { Text } from "@synstack/text";
// Custom join string for arrays
const customText = Text.options({ joinString: " | " });
const result = customText.t`Items: ${["A", "B", "C"]}`;
// "Items: A | B | C"
// Chain options
const text = Text.options()
.options({ joinString: ", " })
.t`List: ${["item1", "item2"]}`;
// "List: item1, item2"API Reference
Core Functions
t- Main templating function for creating formatted texttParse- Parse text with extra objects back into components
Classes & Types
Text- Main text processing class with configurable optionsText.Options- Configuration options interfaceText.String<T>- String type with extra object informationText.ExtraObject.Base- Base type for extra objectsTextParseExtraItemException- Exception thrown during parsing errors
Configuration
type Text.Options = {
joinString?: string; // Default: "\n"
}