Runtime validation for static types
—
Validates template literal strings with typed interpolation. Template runtypes allow validation of strings that follow specific patterns with variable parts validated by their own runtypes.
Creates a validator for template literal strings with interpolated runtypes.
/**
* Creates a validator for template literal strings with typed interpolation
* @param strings - String literal parts of the template
* @param runtypes - Runtypes for interpolated values
* @example Template(['Hello ', '!'], String).check("Hello World!") // "Hello World!"
* @example Template("user_", Number).check("user_123") // "user_123"
*/
function Template<A extends readonly [string, ...string[]], B extends readonly Runtype<LiteralValue>[]>(
strings: A,
...runtypes: B
): TemplateRuntype<A, B>;
// Alternative call signature for convenience
function Template<A extends readonly (LiteralValue | Runtype<LiteralValue>)[]>(
...args: A
): TemplateRuntype<ExtractStrings<A>, ExtractRuntypes<A>>;
interface TemplateRuntype<A, B> extends Runtype<TemplateType<A, B>> {
tag: "template";
strings: A;
runtypes: B;
}
type LiteralValue = undefined | null | boolean | number | bigint | string;Usage Examples:
import { Template, String, Number, Literal, Union } from "runtypes";
// Basic template validation
const Greeting = Template("Hello ", String, "!");
const greeting = Greeting.check("Hello World!"); // "Hello World!"
// ID patterns
const UserId = Template("user_", Number);
const PostId = Template("post_", String);
const userId = UserId.check("user_123"); // "user_123"
const postId = PostId.check("post_abc123"); // "post_abc123"
// Multiple interpolations
const LogEntry = Template("[", String, "] ", String, ": ", String);
const log = LogEntry.check("[INFO] 2024-01-01: Application started");
// Array-style syntax (for better type inference)
const VersionString = Template(["v", "."], Number, Number);
const version = VersionString.check("v1.2"); // "v1.2"import { Template, String, Number, Union, Literal } from "runtypes";
// API endpoint patterns
const UserEndpoint = Template("/api/users/", Number);
const PostEndpoint = Template("/api/posts/", String, "/comments/", Number);
// Validate API paths
const userPath = UserEndpoint.check("/api/users/123");
const commentPath = PostEndpoint.check("/api/posts/my-post/comments/456");
// File path patterns
const ImagePath = Template("images/", String, ".", Union(Literal("jpg"), Literal("png"), Literal("gif")));
const imagePath = ImagePath.check("images/avatar.jpg");
// Query string patterns
const SearchQuery = Template("q=", String, "&page=", Number);
const query = SearchQuery.check("q=javascript&page=2");import { Template, Number, String, Union, Literal } from "runtypes";
// CSS values
const PixelValue = Template(Number, "px");
const PercentValue = Template(Number, "%");
const EmValue = Template(Number, "em");
const CSSSize = Union(PixelValue, PercentValue, EmValue);
const width = PixelValue.check("100px"); // "100px"
const height = PercentValue.check("50%"); // "50%"
// Color patterns
const HexColor = Template("#", String); // Simplified - would need constraint for valid hex
const RgbColor = Template("rgb(", Number, ", ", Number, ", ", Number, ")");
const color1 = HexColor.check("#ff0000");
const color2 = RgbColor.check("rgb(255, 0, 0)");
// CSS class patterns
const BemClass = Template(String, "__", String, "--", String);
const bemClass = BemClass.check("button__text--highlighted");import { Template, Number, String } from "runtypes";
// ISO date pattern (simplified)
const ISODate = Template(Number, "-", Number, "-", Number, "T", Number, ":", Number, ":", Number, "Z");
const timestamp = ISODate.check("2024-01-15T14:30:45Z");
// Custom date formats
const DateSlash = Template(Number, "/", Number, "/", Number);
const DateDash = Template(Number, "-", Number, "-", Number);
const date1 = DateSlash.check("12/31/2024");
const date2 = DateDash.check("2024-12-31");
// Time patterns
const TimeHM = Template(Number, ":", Number);
const TimeHMS = Template(Number, ":", Number, ":", Number);
const time1 = TimeHM.check("14:30");
const time2 = TimeHMS.check("14:30:45");import { Template, String, Number, Boolean } from "runtypes";
// Environment variable patterns
const DatabaseUrl = Template("postgresql://", String, ":", String, "@", String, ":", Number, "/", String);
const dbUrl = DatabaseUrl.check("postgresql://user:pass@localhost:5432/mydb");
// Configuration keys
const ConfigKey = Template("app.", String, ".", String);
const configKey = ConfigKey.check("app.database.host");
// Log levels with context
const LogLevel = Template("[", String, "] ", String);
const logEntry = LogLevel.check("[ERROR] Database connection failed");import { Template, String, Number, Union } from "runtypes";
// Combine templates for complex patterns
const Protocol = Union(Literal("http"), Literal("https"));
const Domain = Template(String, ".", String); // simplified domain pattern
const Url = Template(Protocol, "://", Domain, "/", String);
const url = Url.check("https://example.com/path");
// Reusable template components
const Prefix = Template("prefix_", String);
const Suffix = Template(String, "_suffix");
const Combined = Template(Prefix, "_", Suffix);import { Template, String, Number, Union, Literal } from "runtypes";
// API version patterns
const ApiV1 = Template("/api/v1/", String);
const ApiV2 = Template("/api/v2/", String);
const ApiPath = Union(ApiV1, ApiV2);
// Resource patterns with actions
const Action = Union(Literal("create"), Literal("read"), Literal("update"), Literal("delete"));
const ResourceAction = Template("/api/", String, "/", Action);
const resourcePath = ResourceAction.check("/api/users/create");
// Multi-tenant patterns
const TenantResource = Template("/tenant/", String, "/", String, "/", Number);
const tenantPath = TenantResource.check("/tenant/acme/users/123");import { Template, String, Number, Constraint } from "runtypes";
// Email pattern (simplified)
const EmailLocal = String.withConstraint(s => s.length > 0 && !s.includes("@"));
const EmailDomain = String.withConstraint(s => s.includes("."));
const Email = Template(EmailLocal, "@", EmailDomain);
const email = Email.check("user@example.com");
// Phone number pattern
const AreaCode = Number.withConstraint(n => n >= 100 && n <= 999);
const PhoneNumber = Template("(", AreaCode, ") ", Number, "-", Number);
const phone = PhoneNumber.check("(555) 123-4567");
// Social security number pattern
const SSN = Template(Number, "-", Number, "-", Number);
const SsnWithConstraints = SSN.withConstraint(ssn => {
const parts = ssn.split("-");
return parts[0].length === 3 && parts[1].length === 2 && parts[2].length === 4;
});import { Template, String, Number, Parser } from "runtypes";
// Parse template results into structured data
const VersionTemplate = Template("v", Number, ".", Number, ".", Number);
const VersionParser = VersionTemplate.withParser(versionString => {
const match = versionString.match(/v(\d+)\.(\d+)\.(\d+)/);
return {
major: parseInt(match![1]!),
minor: parseInt(match![2]!),
patch: parseInt(match![3]!)
};
});
const version = VersionParser.parse("v1.2.3");
// { major: 1, minor: 2, patch: 3 }
// Coordinate parsing
const CoordinateTemplate = Template("(", Number, ",", Number, ")");
const CoordinateParser = CoordinateTemplate.withParser(coordString => {
const match = coordString.match(/\(([^,]+),([^)]+)\)/);
return {
x: parseFloat(match![1]!),
y: parseFloat(match![2]!)
};
});
const coord = CoordinateParser.parse("(10.5,20.3)");
// { x: 10.5, y: 20.3 }import { Template, String, Constraint } from "runtypes";
// Problem: String runtypes are greedy and match too much
// Template(String, String) creates pattern ^(.*)(.*)$ where first .* wins all
// Solution: Use constraints for specific patterns
const WordBoundary = String.withConstraint(s => /^\w+$/.test(s));
const TwoWords = Template(WordBoundary, " ", WordBoundary);
const twoWords = TwoWords.check("hello world");
// Or use more specific runtypes
const AlphaString = String.withConstraint(s => /^[a-zA-Z]+$/.test(s));
const NumericString = String.withConstraint(s => /^\d+$/.test(s));
const AlphaNumeric = Template(AlphaString, NumericString);import { Constraint, String } from "runtypes";
// For very complex patterns, use single constraint instead of template
const ComplexPattern = String.withConstraint(s => {
// Custom validation logic for complex patterns
const regex = /^[A-Z]{2}\d{4}[A-Z]{2}$/;
return regex.test(s) || "Must match pattern: 2 letters, 4 digits, 2 letters";
});
const code = ComplexPattern.check("AB1234CD");Install with Tessl CLI
npx tessl i tessl/npm-runtypes