CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-runtypes

Runtime validation for static types

Pending
Overview
Eval results
Files

templates.mddocs/

Template Literals

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.

Capabilities

Template

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"

URL and Path Patterns

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");

CSS and Styling Patterns

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");

Date and Time Patterns

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");

Configuration and Environment Patterns

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");

Advanced Template Patterns

Nested Templates

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);

Dynamic Validation with Templates

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");

Template with Constraints

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;
});

Template Parsing and Transformation

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 }

Template Limitations and Workarounds

Greedy Matching

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);

Complex Patterns

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

docs

composite.md

constraints.md

contracts.md

index.md

literals.md

primitives.md

results.md

templates.md

union-intersect.md

utilities.md

validation.md

tile.json