API reference

The full public surface of tprompt is one factory, one default export, and one returned interface (Compiled) with four methods. Everything else is types.

Public exports

ExportFromWhat
prompt'@nkwib/tprompt'The default Compile call, pre-applied to {'{{var}}'}
prompt'@nkwib/tprompt/single-brace'Pre-applied to {'{var}'}
makePromptTag'@nkwib/tprompt'Factory; returns a prompt for any { open, close } pair
Compiled, PartialApplied, Validated, ValidatedSafe, etc.'@nkwib/tprompt'Type-only exports — covered below

prompt

const prompt: <const S extends string>(template: S) => Compiled<readonly [S], '{{', '}}'>;

The prompt import from '@nkwib/tprompt' is makePromptTag({ open: '{{', close: '}}' }). The <const S extends string> is what makes the wedge work — it preserves the literal type of the template through the call so ExtractPlaceholders can run at the type level.

import { prompt } from '@nkwib/tprompt';

const t = prompt('Hi {{name}}');
//        ^? Compiled<readonly ['Hi {{name}}'], '{{', '}}'>

makePromptTag(options)

function makePromptTag<O extends string, C extends string>(
  options: ParserOptions<O, C>
): <const S extends string>(template: S) => Compiled<readonly [S], O, C>;

interface ParserOptions<Open extends string, Close extends string> {
  readonly open: Open;
  readonly close: Close;
}

Returns a prompt-shaped function specialised to a delimiter pair. Used at module top-level for project-wide custom delimiters or per call site for prompts that contain literal {'{{ }}'} content.

import { makePromptTag } from '@nkwib/tprompt';

const angle = makePromptTag({ open: '<<', close: '>>' });
angle('Hi <<name>>').with({ name: 'world' }); // "Hi world"

Compiled<Strings, Open, Close>

The return type of every Compile call. Carries the original template segments and exposes four methods.

interface Compiled<Strings, Open, Close> {
  readonly strings: Strings;
  readonly open: Open;
  readonly close: Close;
  readonly placeholders: ReadonlyArray<ExtractPlaceholders<Strings, Open, Close>>;

  with(vars: VariablesOf<...>): string;
  partial<const Bound extends ...>(vars: { readonly [K in Bound]: string }): PartialApplied<...>;
  validate<TOutput>(schema: SchemaLike<TOutput>): Validated<...>;
  validateSafe<TOutput>(schema: SchemaLike<TOutput>): ValidatedSafe<...>;
}

.with({...})

Renders the template with all placeholders bound. The variables-object type is inferred from the template — any missing or extra key is a tsc error.

const t = prompt('Hi {{name}}, on {{plan}}');
t.with({ name: 'Alice', plan: 'pro' });
// "Hi Alice, on pro"

t.with({ name: 'Alice' });
//      ^^^^^^^^^^^^^^^ Property 'plan' is missing

.partial({...})

Pre-binds a subset of placeholders. Returns a PartialApplied whose .with({...}) requires only the remainder. The return type does not carry .partial — chaining .partial(...).partial(...) is a tsc error by design.

const t = prompt('You are a {{role}} agent for {{userName}}.');

const support = t.partial({ role: 'support' });
support.with({ userName: 'alice' });
// "You are a support agent for alice."

support.partial({ userName: 'alice' });
//      ^^^^^^^ Property 'partial' does not exist on type 'PartialApplied<...>'

.validate(schema)

Wraps the compiled template with a runtime validator that throws on invalid input.

import { z } from 'zod';

const greet = prompt('Hi {{name}}').validate(
  z.object({ name: z.string().min(2) })
);
greet.with({ name: 'a' });   // throws ZodError

The schema must accept an object whose required keys cover the placeholder set. Anything with .parse(value) and .safeParse(value) qualifies — Zod, Valibot, ArkType, or your own.

.validateSafe(schema)

Same shape, but .with({...}) returns a ValidationResult instead of throwing.

type ValidationResult =
  | { readonly ok: true; readonly value: string }
  | { readonly ok: false; readonly error: unknown };
const safe = prompt('Hi {{name}}').validateSafe(
  z.object({ name: z.string().min(2) })
);
const r = safe.with({ name: 'a' });
if (r.ok) console.log(r.value); else console.error(r.error);

.validate and .validateSafe produce mutually exclusive return shapes — pick one per call site. Mixing them produces dead branches.

Type helpers

These are all export type — zero runtime cost.

ExtractPlaceholders<Strings, Open, Close>

Recursive template-literal type that extracts placeholder names from a tuple of template segments. Identifiers must match [A-Za-z_][A-Za-z0-9_]* — non-identifier matches are skipped (so {'{{ user.name }}'} is not a placeholder).

type T = ExtractPlaceholders<readonly ['Hi {{name}}, on {{plan}}'], '{{', '}}'>;
//   ^? "name" | "plan"

VariablesOf<P>

Maps a placeholder union into the required variables-object type.

type V = VariablesOf<'name' | 'plan'>;
//   ^? { readonly name: string; readonly plan: string }

SchemaLike<TOutput>

The structural validator interface — what .validate and .validateSafe accept.

interface SchemaLike<TOutput> {
  parse(value: unknown): TOutput;
  safeParse(value: unknown):
    | { readonly success: true; readonly data: TOutput }
    | { readonly success: false; readonly error: unknown };
}

ValidationResult

type ValidationResult =
  | { readonly ok: true; readonly value: string }
  | { readonly ok: false; readonly error: unknown };

Other interfaces

  • PartialApplied<Strings, Open, Close, Bound> — return of .partial({...}). Same as Compiled minus .partial.
  • Validated<Strings, Open, Close> — return of .validate(schema).
  • ValidatedSafe<Strings, Open, Close> — return of .validateSafe(schema).
  • ValidatedPartial<Strings, Open, Close, Bound> — partial-then-validate or validate-then-partial.
  • ValidatedSafePartial<Strings, Open, Close, Bound> — same, safe variant.

All five share .strings, .open, .close, .placeholders, and .with — the differences are visible only in the type-level set of methods that remain valid.

Module entry points

// Default — {{var}} (LangChain / BAML / OpenAI / Anthropic convention)
import { prompt, makePromptTag } from '@nkwib/tprompt';

// Pre-applied — {var} (Python f-string-style)
import { prompt } from '@nkwib/tprompt/single-brace';

// Type-only
import type {
  Compiled,
  PartialApplied,
  Validated,
  ValidatedSafe,
  ExtractPlaceholders,
  VariablesOf,
  SchemaLike,
  ValidationResult
} from '@nkwib/tprompt';

The subpath namespace is reserved for behaviour variants (delimiter, escape policy, future flavours). ESM/CJS interop is handled by conditional exports — there is no tprompt/compat. See ADR-0002 and ADR-0003.

tprompt Type-safe prompt templates for TypeScript