๐Ÿ”ง Make Utility

Create type-safe schema values

Overview

The Make utility provides helper methods for creating type-safe schema values. It ensures proper type inference and validation for constants, unions, and other special values.

๐Ÿ’ก Pro Tip: Use Make for values that need strict type safety and Mod for transforming existing schemas.

Make.const()

Method

Create a constant value that must match exactly. Safer than using raw values because it provides better type inference and validation.

Signature

static const<const T extends string | number | boolean>(
  value: T
): ConstantValue & { const: T }

Basic Usage

import { Interface, Make } from "fortify-schema";

const ConfigSchema = Interface({
  // String constants
  apiVersion: Make.const("v2.1"),
  environment: Make.const("production"),
  
  // Numeric constants
  maxRetries: Make.const(3),
  timeout: Make.const(5000),
  
  // Boolean constants
  enableLogging: Make.const(true),
  debugMode: Make.const(false)
});

// Valid data
ConfigSchema.parse({
  apiVersion: "v2.1",      // โœ… Matches exactly
  environment: "production", // โœ… Matches exactly
  maxRetries: 3,           // โœ… Matches exactly
  timeout: 5000,           // โœ… Matches exactly
  enableLogging: true,     // โœ… Matches exactly
  debugMode: false         // โœ… Matches exactly
});

// Invalid data
ConfigSchema.parse({
  apiVersion: "v2.0",      // โŒ Must be "v2.1"
  environment: "staging",  // โŒ Must be "production"
  maxRetries: 5,           // โŒ Must be 3
  timeout: 3000,           // โŒ Must be 5000
  enableLogging: false,    // โŒ Must be true
  debugMode: true          // โŒ Must be false
});

Use Cases

๐Ÿ”’ API Versioning

Enforce specific API versions in requests

โš™๏ธ Configuration

Lock down critical config values

๐ŸŽฏ Feature Flags

Validate feature flag states

๐Ÿ“ Protocol Compliance

Ensure protocol-specific values

Advanced Example

// Complex configuration with constants
const DatabaseConfigSchema = Interface({
  driver: Make.const("postgresql"),
  version: Make.const("14.5"),
  ssl: Make.const(true),
  poolSize: Make.const(20),
  
  // Mix with other types
  host: "string",
  port: "number",
  database: "string",
  username: "string",
  password: "string"
});

// Type inference works perfectly
type DatabaseConfig = InferType<typeof DatabaseConfigSchema>;
// {
//   driver: "postgresql";  // Literal type
//   version: "14.5";       // Literal type
//   ssl: true;             // Literal type
//   poolSize: 20;          // Literal type
//   host: string;
//   port: number;
//   database: string;
//   username: string;
//   password: string;
// }

Make.union()

Method

Create a union type with multiple allowed values. Provides proper type inference for all possible values.

Signature

static union<const T extends readonly string[]>(
  ...values: T
): UnionValue<T>

Basic Usage

import { Interface, Make } from "fortify-schema";

const UserSchema = Interface({
  // Status with 3 possible values
  status: Make.union("pending", "accepted", "rejected"),
  
  // Priority levels
  priority: Make.union("low", "medium", "high", "critical"),
  
  // User roles
  role: Make.union("admin", "moderator", "user", "guest")
});

// Valid data
UserSchema.parse({
  status: "pending",    // โœ… One of the allowed values
  priority: "high",     // โœ… One of the allowed values
  role: "admin"         // โœ… One of the allowed values
});

// Invalid data
UserSchema.parse({
  status: "approved",   // โŒ Not in union
  priority: "urgent",   // โŒ Not in union
  role: "superadmin"    // โŒ Not in union
});

Type Inference

const StatusSchema = Interface({
  status: Make.union("pending", "accepted", "rejected")
});

type Status = InferType<typeof StatusSchema>;
// { status: "pending" | "accepted" | "rejected" }

// TypeScript autocomplete works perfectly
const handleStatus = (data: Status) => {
  if (data.status === "pending") {
    // Handle pending
  } else if (data.status === "accepted") {
    // Handle accepted
  } else if (data.status === "rejected") {
    // Handle rejected
  }
  // TypeScript knows all possible values!
};

Real-World Example

// E-commerce order schema
const OrderSchema = Interface({
  orderId: "uuid",
  
  // Order status workflow
  status: Make.union(
    "cart",
    "pending_payment",
    "payment_failed",
    "paid",
    "processing",
    "shipped",
    "delivered",
    "cancelled",
    "refunded"
  ),
  
  // Payment method
  paymentMethod: Make.union(
    "credit_card",
    "debit_card",
    "paypal",
    "stripe",
    "bank_transfer"
  ),
  
  // Shipping method
  shippingMethod: Make.union(
    "standard",
    "express",
    "overnight",
    "pickup"
  ),
  
  items: "array",
  total: "number",
  createdAt: "date"
});

Make.unionOptional()

Method

Create an optional union type. The field can be one of the specified values or undefined.

Signature

static unionOptional<const T extends readonly string[]>(
  ...values: T
): UnionValue<T> & { optional: true }

Basic Usage

import { Interface, Make } from "fortify-schema";

const ProductSchema = Interface({
  id: "uuid",
  name: "string",
  
  // Optional shipping type
  shippingType: Make.unionOptional("standard", "express", "overnight"),
  
  // Optional gift wrapping
  giftWrap: Make.unionOptional("basic", "premium", "deluxe")
});

// All valid
ProductSchema.parse({
  id: "123",
  name: "Product",
  shippingType: "express"  // โœ… Specified
});

ProductSchema.parse({
  id: "123",
  name: "Product"
  // โœ… shippingType and giftWrap omitted
});

ProductSchema.parse({
  id: "123",
  name: "Product",
  shippingType: "standard",
  giftWrap: "premium"
  // โœ… Both specified
});

Type Inference

const Schema = Interface({
  type: Make.unionOptional("standard", "express", "overnight")
});

type Inferred = InferType<typeof Schema>;
// { type?: "standard" | "express" | "overnight" | undefined }

// TypeScript handles optional correctly
const process = (data: Inferred) => {
  if (data.type) {
    // data.type is "standard" | "express" | "overnight"
    console.log(`Shipping type: ${data.type}`);
  } else {
    console.log("No shipping type specified");
  }
};

Method Comparison

Method Purpose Required Example
Make.const() Single exact value Yes Make.const("v1.0")
Make.union() Multiple allowed values Yes Make.union("a", "b", "c")
Make.unionOptional() Optional multiple values No Make.unionOptional("x", "y")

โœจ Best Practices

1. Use Make.const() for Fixed Values

When a value must never change (API versions, protocol identifiers), use Make.const().

2. Prefer Make.union() Over String Literals

Use Make.union() instead of "a|b|c" for better type inference and IDE support.

3. Keep Union Values Descriptive

Use clear, descriptive values: "pending_payment" instead of "pp".

4. Document Complex Unions

Add comments explaining what each union value represents in your business logic.