UNPKG

typelit

Version:

A type-safe string templating library for TypeScript

232 lines (214 loc) 7.02 kB
/** * Creates a nested object type based on an array of property names and a value type. * Each level of nesting corresponds to one name in the array. * * ```ts * type User = Nested<['user'], string> // { user: string } * type UserWithName = Nested<['user', 'name'], string> // { user: { name: string } } * type SessionWithUserWithId = Nested<['session', 'user', 'id'], number> * // { * // session: { * // user: { * // id: number * // } * // } * // } * ``` */ export type Nested<Names extends string[], T> = Names extends [ infer Head extends string, ] ? { [K in Head]: T } : Names extends [infer Head extends string, ...infer Rest extends string[]] ? { [K in Head]: Nested<Rest, T> } : never; /** * Core prompt variable type that can extract a value from a nested context and * stringify it. */ export type Var<Names extends string[], T> = { _extract: (ctx: Nested<Names, T>) => T; stringify: (value: T) => string; }; /** * Options for creating a prompt variable factory function. */ export type CreateOptions<T> = { /** * Optional function to convert a value of type T to a string. * If not provided, the default `String` function will be used. */ stringify?: (value: T) => string; }; /** * Creates a prompt variable factory function for creating strongly-typed variable references. * Used to create type-specific variable creators (e.g., `string`, `number`, `boolean`). * * @param options - Configuration options for the variable type * @param options.stringify - Custom function to convert values to strings. Useful when the default * `String()` conversion isn't suitable (e.g., for formatting numbers or dates). * If not provided, the standard `String()` function is used. * * Returns a function that creates variable references with nested paths. * * ```ts * // Basic usage with default string conversion * const stringVar = createType<string>(); * const myVar = stringVar("user", "name"); // Var<["user", "name"], string> * * // Custom string conversion for numbers * const numberVar = createType<number>({ * stringify: (n) => n.toFixed(2) // Convert numbers to strings with 2 decimal places * }); * const priceVar = numberVar("product", "price"); // Var<["product", "price"], number> * ``` */ export function createType<T>(options: CreateOptions<T> = {}) { return function <Names extends string[]>(...names: Names): Var<Names, T> { return { _extract: (ctx) => names.reduce((obj, n) => obj[n], ctx as any), stringify: options.stringify ?? String, }; }; } /** * Extracts the first name in the path of a variable type. * Used for creating context object keys. * * ```ts * type Name = VarName<Var<["user", "name"], string>>; // "user" * ``` */ export type VarName<T> = T extends Var<infer VN, infer _VT> ? VN[0] : never; /** * Extracts the type structure of a variable, maintaining the nested path except for the first name. * * ```ts * type Structure = VarType<Var<["user", "name"], string>>; * // { name: string } * ``` */ export type VarType<T extends Var<string[], any>> = T extends Var<infer VN, infer VT> ? VN extends [infer _Head extends string] ? VT : VN extends [infer _Head extends string, ...infer Rest extends string[]] ? Nested<Rest, VT> : never : never; /** Base type that all arrays of Var extend */ type VarList = Var<string[], any>[]; /** * Converts a union type to an intersection type. * Used internally to combine multiple variable types in a context. * * ```ts * type Union = { a: string } | { b: number }; * type Intersection = UnionToIntersection<Union>; // { a: string } & { b: number } * ``` */ export type UnionToIntersection<U> = ( U extends object ? (k: U) => void : U ) extends (k: infer I) => void ? I : U; /** * Helper type to "prettify" complex Context types */ type Prettify<T> = { [K in keyof T]: T[K] extends Context<infer _Vars> ? Prettify<T[K]> : T[K]; } & {}; /** * Represents a context object that contains all variables needed for a prompt template. * Variables with the same first path segment are grouped together under a single property. * * ```ts * type Ctx = Context<[Var<["user", "name"], string>, Var<["user", "age"], number>]>; * // { user: { name: string; age: number } } * ``` */ export type Context<Vars extends VarList> = Prettify<{ [K in Vars[number] as VarName<K>]: UnionToIntersection<VarType<K>>; }>; /** * Represents a function that takes a context object and returns a string. * Used as the type for compiled prompt templates. */ export type TemplateFn<Vars extends VarList> = (ctx: Context<Vars>) => string; /** * Creates a type-safe prompt template function from a template literal and variables. * The resulting function accepts a context object and returns the formatted string. * * ```ts * const template = typelit`Hello ${typelit.string("user", "name")}!`; * const result = template({ user: { name: "Alice" } }); // "Hello Alice!" * ``` */ export function typelit<Vars extends VarList>( strings: TemplateStringsArray, ...vars: Vars ): TemplateFn<Vars> { return (ctx: Context<Vars>) => vars.reduce<string>( // @ts-expect-error type of `v` loses specificity during iteration (acc, v, i) => acc + v.stringify(v._extract(ctx)) + strings[i + 1], strings[0], ); } /** * Variable creator for boolean values. * ```ts * const template = typelit`The switch is ${typelit.boolean("enabled")}` * template({ enabled: true }) // "The switch is true" * ``` */ typelit.boolean = createType<boolean>(); /** * Variable creator for string values. * ```ts * const template = typelit`Hello ${typelit.string("name")}!` * template({ name: "Alice" }) // "Hello Alice!" * ``` */ typelit.string = createType<string>(); /** * Variable creator for number values. * ```ts * const template = typelit`You are number ${typelit.number("position")} in line` * template({ position: 3 }) // "You are number 3 in line" * ``` */ typelit.number = createType<number>(); /** * Variable creator for bigint values. * ```ts * const template = typelit`Transaction ID: ${typelit.bigint("id")}` * template({ id: 123456789n }) // "Transaction ID: 123456789" * ``` */ typelit.bigint = createType<bigint>(); /** * Variable creator for Date values. * * ```ts * const template = typelit`Event date: ${typelit.date("eventDate")}` * template({ eventDate: new Date("2023-05-15") }) // "Event date: Mon May 15 2023 00:00:00 GMT+0000 (Coordinated Universal Time)" * ``` */ typelit.date = createType<Date>(); /** * Variable creator for any value type. * Stringifies the value to JSON with 2-space indentation. * * ```ts * const template = typelit`Data: ${typelit.json("data")}` * template({ data: { name: "Alice", age: 30 } }) * // "Data: { * // "name": "Alice", * // "age": 30 * // }" * ``` */ typelit.json = createType<any>({ stringify: (obj) => JSON.stringify(obj, null, 2), });