Context: Working with tosijs-schema

You are an expert in tosijs-schema, a lightweight, schema-first, LLM-native replacement for Zod. Use this guide to generate correct code, migrate from Zod, and understand the library's design philosophy.

1. Core Philosophy & Design

2. Basic Setup & Syntax

Imports

import { s, type Infer } from 'tosijs-schema'

Defining Schemas

Use the s proxy to define schemas. The syntax is property-based and chainable.

const UserSchema = s.object({
  id: s.string.uuid, // Format shorthand
  username: s.string.min(3).max(20), // Chainable constraints
  email: s.email, // First-class email type (no .string wrapper needed)
  age: s.integer.min(0).optional, // Optional integer
  tags: s.array(s.string).min(1), // Array with constraints
  role: s.enum(['admin', 'user']), // Enums
  meta: s.record(s.string), // Record/Dictionary
})

Type Inference

Inference works similarly to Zod but exports Infer directly.

type User = Infer<typeof UserSchema>

Accessing the JSON Schema

You can access the raw JSON schema object via the .schema property.

console.log(UserSchema.schema)
// Outputs standard JSON Schema object: { type: "object", properties: { ... } }

3. Validation API

Crucial Difference from Zod:

const data = { ... };

// Fast validation (returns true/false)
if (UserSchema.validate(data)) {
  // logic here
} else {
  // Handle invalid data
}

4. Migration Guide (Zod vs. tosijs-schema)

Feature Zod (z) tosijs-schema (s)
String z.string() s.string
Email z.string().email() s.email (First-class citizen)
UUID z.string().uuid() s.string.uuid or s.uuid
Optional schema.optional() schema.optional (Property, not method)
Objects z.object({...}) s.object({...})
Strict Mode z.object({...}).strict() Default (No method needed)
Arrays z.array(schema) s.array(schema)
Enums z.enum(['a', 'b']) s.enum(['a', 'b'])
Unions z.union([a, b]) s.union([a, b])
Inference z.infer<typeof T> Infer<typeof T>
Metadata .describe("...") .describe("...") / .title("...")

5. Monadic Pipelines (M)

tosijs-schema includes a "Railway Oriented Programming" module for building type-safe tool chains. This is especially useful for AI Agents, ensuring that hallucinations or bad data are caught immediately at the source (Input vs Output) rather than cascading.

1. Guarded Functions (M.func)

Create functions that enforce schemas on both input and output.

import { M, s } from 'tosijs-schema'

// M.func(InputSchema, OutputSchema, Implementation)
const getSize = M.func(s.string, s.number, (str) => {
  return str.length
})

// Usage:
const len = getSize('hello') // Returns 5
// getSize(123) // Throws SchemaError (Input mismatch)

2. Execution Contexts (new M)

Chain multiple functions together. The execution context handles error propagation automatically.

const pipeline = new M({
  getSize,
  isEven: M.func(s.number, s.boolean, (n) => n % 2 === 0),
})

const result = pipeline
  .getSize('hello') // Output: 5
  .isEven() // Input: 5 -> Output: false
  .result() // Returns false | Error

// If any step fails schema validation, .result() returns the specific error.

6. Advanced Features

Ghost Constraints

Constraints that are computationally expensive (O(N)) are documented in the schema but skipped by the runtime validator for performance (O(1)).

Metadata & LLM Optimization

Use metadata methods to enrich schemas for LLMs or OpenAPI docs without affecting runtime validation.

const ApiKey = s.string
  .min(32)
  .describe("The user's secret API key") // standard JSON schema "description"
  .title('API Key')
  .default('sk-...')

Date Handling

tosijs-schema treats dates as strings with format validation, aligning with JSON transport.

const Timestamp = s.string.datetime // Validates ISO string format

6. Common Patterns & Gotchas

  1. Chaining Order: Primitives (like s.string) start the chain. Constraints (.min()) and metadata (.describe()) follow. The .optional flag can be placed anywhere in the chain but usually goes last for readability.
  2. No Transformers: Unlike Zod, tosijs is a pure validation/schema library. It does not "transform" data (e.g., string to number coercion) during validation.
  3. Strict Objects: Remember that s.object() disallows unknown keys by default. If you need a flexible object, use s.record() or explicitly allow additional properties if the API supports it (though strict is preferred for LLM outputs).
  4. Tuples: Use s.tuple([s.string, s.number]) for fixed-length arrays.

7. Example: LLM Structured Output

When defining a response format for an LLM:

const ResponseSchema = s.object({
  reasoning: s.string.describe('Step-by-step thinking process'),
  final_answer: s.string.describe('The concise final answer'),
  confidence: s.number
    .min(0)
    .max(1)
    .describe('Confidence score between 0 and 1'),
})

// Pass to LLM
const jsonSchema = ResponseSchema.schema