This is the abridged developer documentation for Loom # LOOM > lets you build 𝙛𝙖𝙨𝙩 agents 𝙛𝙖𝙨𝙩𝙚𝙧 ## Get Started ```bash $ bun i loom-agents ``` ```typescript import { Agent } from "loom-agents"; const agent = new Agent({ name: "Loomie", purpose: "You're an AI agent made to automate tasks simply and fast!", }); const result = await agent.run("Ready to get building?"); console.log(result.final_message); ``` ```bash $ bun index.ts 🚀 Pfft, you kidding? Hell yeah, let's build this thing! 🔥😎 ``` ## Key Features Hierarchical Agent Structure Create specialized agents that can collaborate to solve complex problems. Advanced Tracing Track every step of your agent’s execution with detailed, hierarchical traces. MCP Integration Leverage third party integrations to take your agents to the next level Tool Integration Extend your agents with custom tools for database access, API calls, and more. OpenAI Compatible Built to work seamlessly with OpenAI’s latest models and capabilities. ## Core Components Agent The core component that interacts with AI models and tools. [Learn more](/docs/reference/agent/) Trace Track execution with detailed hierarchical logging. [Learn more](/docs/reference/trace/) Runner Execute agents with consistent configuration and error handling. [Learn more](/docs/reference/runner/) Loom Global configuration for OpenAI API settings. [Learn more](/docs/reference/loom/) # Agent > Core component for building AI assistants with specific purposes and capabilities The `Agent` class is the primary building block in the Loom framework. It represents an AI assistant with a specific purpose, capable of using tools and generating responses. ## Creating an Agent ```typescript import { Agent } from "loom-agents"; const agent = new Agent({ name: "Researcher", purpose: "Find and summarize information", model: "gpt-4o", tools: [ // Tool definitions ], web_search: { enabled: true, config: { search_context_size: "medium", }, }, }); ``` ## Agent Configuration The `AgentConfig` interface defines the available configuration options: ```typescript interface AgentConfig { name: string; // Required: Unique name for the agent purpose: string; // Required: Defines the agent's objective sub_agents?: Agent[]; // Optional: Sub-agents that can be called tools?: ToolCall[]; // Optional: Tools the agent can use model?: string; // Optional: Default is 'gpt-4o' mcp_servers?: (MCPServerSSE | MCPServerStdio)[]; web_search?: WebSearchConfig; // Optional: Web search configuration timeout_ms?: number; // Optional: Default is 60000ms (1 minute) client_config?: ClientOptions; // Optional: A custom OpenAI ClientOptions object for this Agent api?: "completions" | "responses"; // Optional: Force this client to use a different API than the Loom global config } ``` ## Agent Request and Response When running an agent, you can use a simple string or a request object: ```typescript // AgentRequest interface interface AgentRequest { context: T[]; // Conversation context } // AgentResponse interface interface AgentResponse { status: string; // "completed", "error", etc. final_message: string; // The final output message context: T[]; // The updated conversation context } ``` ## Running an Agent Agents can process string inputs or more complex request objects: ```typescript // Simple string input const result = await agent.run("Find information about quantum computing"); // Context-aware request const result = await agent.run( { context: [ { role: "user", content: "What can you tell me about quantum computing?", }, { role: "assistant", content: "Quantum computing is a type of computing that uses quantum phenomena...", }, { role: "user", content: "How does it compare to classical computing?" }, ], }, optionalTraceObject // Optional: For observability ); ``` ## Working with MCP Servers Loom supports integrating with external tools using the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction), a standard that allows third-party applications and services to provide context and capabilities to LLMs. You can use two types of MCP connections: * [`MCPServerSSE`](../reference/mcp) – Connects to an MCP tool server over **Server-Sent Events** * [`MCPServerStdio`](../reference/mcp) – Connects to a local MCP tool server via **stdio** ### Example: Connecting to MCP Servers ```ts import { Agent, MCPServerSSE, MCPServerStdio } from "loom-agents"; const researchAgent = new Agent({ name: "MCP Tool Runner", purpose: "Run a connected MCP tool!", mcp_servers: [ new MCPServerSSE(new URL("http://localhost:3001/sse")), new MCPServerStdio("bun", ["stdio.ts"]), ], }); ``` With this configuration, your agent can call tools exposed by the connected MCP servers seamlessly. ## Defining Tools Tools allow agents to interact with external systems or perform specialized tasks: ```typescript const tools = [ { name: "fetchWeather", description: "Get current weather for a location", parameters: { location: { type: "string", description: "City name or coordinates", }, }, callback: async ({ location }) => { // Implementation to fetch weather data return { temperature: 72, conditions: "sunny" }; }, }, ]; ``` ## Agents as Tools Sub agents are great, but sometimes we need to leverage the power of an Agent, while not fully influencing the context of our whole orchestration, that’s where using Agents as Tools comes in. ```typescript const translationAgent = new Agent({ name: "Translation Agent", purpose: "I translate text into different languages, just let me know the language you want to translate to.", }); const greetingAgent = new Agent({ name: "Greeting Agent", purpose: "Generate a greeting", tools: [ translationAgent.asTool({ request: { type: "string", description: `The text to translate`, }, language: { type: "string", description: `The language to translate to`, }, }), ], }); ``` ## Advanced Agent Model Configuration Some model providers make models that are better for certain tasks, so we need a way to Loom configurations to work with these providers. ```typescript const deepseekAgent = new Agent({ name: "DeekSeek", purpose: "Are you deepseek or chatgpt? You tell me!", model: "deepseek-chat", api: "completions", // we need to override this specific model to use the completions api since deekseek doesn't support responses. client_config: { // this will create a deepseekAgent specific OpenAI client. baseURL: "https://api.deepseek.com", apiKey: "sk-", }, }); ``` ## Using Web Search Agents can perform web searches when configured: ```typescript const agent = new Agent({ // ... other config web_search: { enabled: true, config: { search_context_size: "high", // 'high', 'medium', or 'low' user_location: { type: "approximate", country: "US", city: "San Francisco", region: "CA", }, }, }, }); ``` ## Composing with Sub-agents Agents can delegate tasks to specialized sub-agents: ```typescript const mathAgent = new Agent({ name: "MathExpert", purpose: "Solve complex math problems", // ... other config }); const researchAgent = new Agent({ name: "Researcher", purpose: "Find and summarize information", sub_agents: [mathAgent], // ... other config }); // The researchAgent can now call mathAgent for math problems ``` ## Related * [Runner](/docs/reference/runner): Orchestrates agent execution with enhanced context management * [Trace](/docs/reference/trace): Provides structured observability for agent operations * [Loom](/docs/reference/loom): Global configuration for OpenAI settings and API preferences # Loom > Global configuration for OpenAI API settings The `Loom` module provides global configuration for OpenAI API settings. It’s implemented as a singleton that maintains consistent settings across your application. ## Basic Usage ```typescript import { Loom } from 'loom-agents'; // Configure OpenAI API Loom.api = 'completions'; // or 'responses' Loom.openai_config = { apiKey: process.env.OPENAI_API_KEY, organization: process.env.OPENAI_ORG_ID }; // Access OpenAI client const completion = await Loom.openai.chat.completions.create({ model: 'gpt-4o', messages: [{ role: 'user', content: 'Hello!' }] }); ``` ## Configuration Options The `ConfigOptions` interface defines available configuration: ```typescript interface ConfigOptions { api?: 'completions' | 'responses'; // API mode to use openai_config?: ClientOptions; // OpenAI client configuration } ``` ## Setting API Mode The `api` property controls which OpenAI endpoint to use: ```typescript // Use OpenAI's Chat Completions API Loom.api = 'completions'; // Use OpenAI's Responses API Loom.api = 'responses'; ``` The selected API mode affects how [Agents](/docs/reference/agent) process requests. ## Configuring OpenAI Client The `openai_config` property accepts any options supported by the OpenAI Node.js client: # MCPServer > Base class for connecting to Model Context Protocol-compatible tool servers over SSE or stdio The `MCPServer` system provides a standardized way to connect to tool servers that implement the [Model Context Protocol (MCP)](https://modelcontext.org). It supports both **SSE (Server-Sent Events)** and **stdio (standard input/output)** transports. ## Creating a Server Connection There are two built-in classes for connection: * `MCPServerSSE`: Connects to a remote MCP server using Server-Sent Events. * `MCPServerStdio`: Launches and communicates with a local MCP server using standard input/output. ```ts import { MCPServerSSE, MCPServerStdio } from "loom-agents/mcp"; // Using SSE const mcpSSE = new MCPServerSSE(new URL("http://localhost:3000/mcp")); // Using stdio const mcpStdio = new MCPServerStdio("node", ["./tool-server.js"]); ``` ## Listing Available Tools Each server may expose a set of tools that can be queried: ```ts const tools = await mcpSSE.getTools(); console.log(tools); // { // tools: [ // { // name: "calculate-bmi", // inputSchema: { // type: "object", // properties: [Object], // required: [Array], // additionalProperties: false, // $schema: "http://json-schema.org/draft-07/schema#", // }, // }, // { // name: "fetch-weather", // inputSchema: { // type: "object", // properties: [Object], // required: [Array], // additionalProperties: false, // $schema: "http://json-schema.org/draft-07/schema#", // }, // }, // ]; // } ``` This will automatically connect the client if it hasn’t already. ## Calling a Tool Once connected, tools can be called directly by name: ```ts const result = await mcpSSE.callTool({ name: "calculate-bmi", arguments: { "weightKg":70, "heightM":1.75 } }); console.log(result); // { // content: [ // { // type: "text", // text: "22.857142857142858", // }, // ]; // } // Note, Loom parses MCP results for errors and sends the content / output off `.content` // [ // { // type: "text", // text: "22.857142857142858", // }, //] ``` ## Extending MCPServer If you want to implement a custom transport or extend tool behaviors, you can subclass `MCPServerBase`. ```ts class CustomMCPServer extends MCPServerBase { constructor(myTransport: CustomTransport) { super(myTransport); } // Custom behavior } ``` ## Internals The `MCPServerBase` manages client connection state and handles listing and calling tools. It lazily initializes the client only when needed: ```ts await this.client.connect(this.transport); ``` It caches tool metadata after the first call to `getTools()` to avoid redundant lookups. ## Related * [Agent](/docs/reference/agent): Interacts with tools through an MCPServer * [Tool Definition](/docs/reference/tool): Learn how tools are defined and exposed * [Trace](/docs/reference/trace): Structured tracking of tool calls and agent interactions * [Loom Configuration](/docs/reference/loom): Customize global AI behavior and context handling # Runner > Orchestrates agent execution and manages multi-turn interactions The `Runner` class orchestrates the execution of [Agents](/docs/reference/agent), handling multi-turn interactions and providing execution context. ## Creating a Runner ```typescript import { Agent, Runner } from "loom-agents"; const agent = new Agent({ name: "Assistant", purpose: "Help with general tasks", // ... other config }); const runner = new Runner(agent, { name: "MainRunner", max_depth: 5, context: { user_id: "user-123", session_start: Date.now(), }, }); ``` ## Runner Configuration The `RunnerConfig` interface defines available configuration options: ```typescript interface RunnerConfig { max_depth?: number; // Optional: Maximum number of turns (default: 10) name?: string; // Optional: Runner name (default: "Runner") id?: string; // Optional: Unique ID (default: auto-generated UUID) context?: Record; // Optional: Additional context data } ``` ## Running an Agent The `run` method executes the agent and manages the interaction flow: ```typescript // Simple string input const result = await runner.run("Help me write a resume"); // Rich context input const result = await runner.run({ context: [ { role: "user", content: "I need help with my resume" }, { role: "assistant", content: "I can help! What industry are you in?" }, { role: "user", content: "Software engineering" }, ], }); ``` ## Managing Execution Flow The Runner handles: 1. Executing the agent with the provided input 2. Processing agent responses and tool calls 3. Managing multi-turn conversations up to `max_depth` 4. Tracing execution with the integrated [Tracer](/docs/reference/trace) ## Handling Results The `run` method returns a `RunnerResponse` which is a souped-up [AgentResponse](/docs/reference/agent) ```typescript interface RunnerResponse extends AgentResponse { trace: Trace; // The execution trace } ``` ## Accessing Execution Data The Runner provides methods to access execution details: ```typescript // Get the most recent trace const lastTrace = runner.getLastTrace(); // Get all traces from this runner const traces = runner.getTraces(); // Render the last trace for debugging console.log(lastTrace.render()); ``` ## Execution Depth and Handling The Runner prevents infinite loops by enforcing a maximum execution depth: ```typescript const runner = new Runner(agent, { max_depth: 5, // Agent will stop after 5 interaction turns }); ``` If the maximum depth is reached before the agent completes its task, the runner will return the current state with the status from the agent’s last response. ## Related * [Agent](/docs/reference/agent): The core AI component orchestrated by Runners * [Trace](/docs/reference/trace): Provides detailed execution logs * [Loom](/docs/reference/loom): Global configuration for OpenAI settings # Trace > Structured logging and performance monitoring for agent operations The `Trace` module provides structured logging and performance tracking for agent operations, making it easier to debug, analyze, and optimize agent behavior. ## Trace Classes The module consists of two main classes: * `Trace`: Represents an individual trace entry with hierarchical structure * `TraceDetails`: Interface defining the structure of trace data ## Creating a Trace Traces can be created directly for standalone use: ```typescript import { Trace } from 'loom-agents'; const rootTrace = new Trace('main-operation', { input: 'user query', timestamp: Date.now() }); ``` ## Adding Sub-traces Traces can be nested to represent hierarchical operations: ```typescript // Start a child trace const parseTrace = rootTrace.start('parse-input', { parser: 'default', input_length: input.length }); // Perform operation const result = parseInput(input); // End the trace (returns duration in ms) const duration = parseTrace.end(); console.log(`Parsing took ${duration}ms`); ``` ## The TraceDetails Interface The `TraceDetails` interface defines the structure of trace data: ```typescript interface TraceDetails { name: string; // Operation name uuid: string; // Unique trace identifier data: any; // Associated metadata startTime: number; // Timestamp when trace started endTime?: number; // Timestamp when trace ended (if completed) children: TraceDetails[]; // Child traces } ``` ## Getting Trace Details You can retrieve the full trace data structure: ```typescript // Get the complete trace hierarchy const traceData = rootTrace.getDetails(); // The structure includes all child traces console.log(JSON.stringify(traceData, null, 2)); ``` ## Rendering Traces The `render` method produces a formatted tree representation of the trace: ```typescript // Generate human-readable trace output const traceOutput = rootTrace.render(); console.log(traceOutput); ``` Example output: ```plaintext [trace.123e4567-e89b-12d3-a456-426614174000] main-operation (1200 ms) - {"input":"query"} └─ [trace.234e5678-e89b-12d3-a456-426614174001] parse-input (50 ms) - {"parser":"default"} ├─ [trace.345e6789-e89b-12d3-a456-426614174002] tokenize (20 ms) - {"tokens":15} └─ [trace.456e7890-e89b-12d3-a456-426614174003] process (1000 ms) - {"model":"gpt-4o"} ``` ## Integration with Agents The trace system is integrated with [Agent](/docs/reference/agent) for detailed operation tracking: ```typescript // Create a trace for agent operations const trace = new Trace('agent-execution', { agent: agent.uuid }); // Pass the trace to the agent's run method const result = await agent.run(input, trace); // Analyze the trace after execution console.log(trace.render()); ``` ## Integration with Runner The [Runner](/docs/reference/runner) class automatically creates and manages traces: ```typescript // Create a runner const runner = new Runner(agent); // Run the agent const result = await runner.run(input); // Get the runs trace const trace = result.trace; // Or get the last trace for the runner const lastTrace = runner.getLastTrace(); console.log(lastTrace.render()); // Or get all traces the runner has made const allTraces = runner.getTraces(); ``` ## Performance Analysis Traces can be used to identify performance bottlenecks: ```typescript // Get total execution time const totalTime = rootTrace.end(); // Returns total duration in ms ``` ## Related * [Agent](/docs/reference/agent): Core component that generates traces during execution * [Runner](/docs/reference/runner): Orchestrates agents and manages trace hierarchy * [Loom](/docs/reference/loom): Global configuration # Examples > Usage examples for the Loom framework This page provides practical examples of using the Loom framework for various agent-based applications. ## Basic Agent ```typescript import { Agent, Runner, Loom } from 'loom-agents'; // Configure Loom Loom.api = 'completions'; Loom.openai_config = { apiKey: process.env.OPENAI_API_KEY }; // Create an agent const agent = new Agent({ name: 'Assistant', purpose: 'Help with general questions', model: 'gpt-4o' }); // Create a runner const runner = new Runner(agent); // Execute the agent const result = await runner.run('What is the capital of France?'); console.log(result.final_message); // "The capital of France is Paris." ``` ## Using Tools ```typescript import { Agent, Runner } from 'loom-agents'; // Define tool const weatherTool = { name: 'getWeather', description: 'Get current weather for a location', parameters: { location: { type: 'string', description: 'City name or coordinates' } }, callback: async ({ location }) => { // In a real implementation, this would call a weather API return { temperature: 72, conditions: 'sunny' }; } }; // Create an agent with the tool const agent = new Agent({ name: 'WeatherAssistant', purpose: 'Provide weather information', model: 'gpt-4o', tools: [weatherTool] }); const runner = new Runner(agent); const result = await runner.run('What\'s the weather like in San Francisco?'); console.log(result.final_message); ``` ## Composing Agents ```typescript import { Agent, Runner } from 'loom-agents'; // Create specialized agents const mathAgent = new Agent({ name: 'MathExpert', purpose: 'Solve math problems', model: 'gpt-4o' }); const weatherAgent = new Agent({ name: 'WeatherExpert', purpose: 'Provide weather information', model: 'gpt-4o', tools: [/* weather tools */] }); // Create a coordinator agent const coordinatorAgent = new Agent({ name: 'Coordinator', purpose: 'Route requests to appropriate specialized agents', model: 'gpt-4o', sub_agents: [mathAgent, weatherAgent] }); // Run the coordinator const runner = new Runner(coordinatorAgent); const result = await runner.run('Can you tell me the weather in NYC and also calculate 15% of 85?'); console.log(result.final_message); ``` ## Using Traces for Debugging ```typescript import { Agent, Runner } from 'loom-agents'; // Create agent and runner const agent = new Agent({ /*...*/ }); const runner = new Runner(agent); // Execute with tracing const result = await runner.run('Process this data'); // Get and display the trace const lastTrace = runner.getLastTrace(); console.log(lastTrace.render()); // Or get all traces from this runner const allTraces = runner.getTraces(); ``` ## Web Search Agent ```typescript import { Agent, Runner } from 'loom-agents'; // Create an agent with web search enabled const researchAgent = new Agent({ name: 'Researcher', purpose: 'Find information online', model: 'gpt-4o', web_search: { enabled: true, config: { search_context_size: 'high', user_location: { type: 'approximate', country: 'US' } } } }); const runner = new Runner(researchAgent); const result = await runner.run('What were the major tech news stories this week?'); console.log(result.final_message); ``` ## Related * [Agent](/docs/reference/agent): Core agent implementation * [Runner](/docs/reference/runner): Agent execution orchestration * [Trace](/docs/reference/trace): Execution tracing and debugging * [Loom](/docs/reference/loom): Global configuration # Introduction to Loom > An overview of the Loom framework for building intelligent agents Loom is a TypeScript framework for building, orchestrating, and tracing AI agents with OpenAI’s API. ## Core Concepts Loom provides several key abstractions for building agent-based applications: * **Agent**: A configurable entity that can process inputs, use tools, and produce outputs * **Runner**: Orchestrates execution of agents, handling multi-turn interactions * **Trace**: Provides structured logging and performance tracking * **Loom**: Global configuration for OpenAI API settings ## Getting Started ```typescript import { Agent, Runner, Loom } from 'loom-agents'; // Configure global settings Loom.api = 'completions'; // or 'responses' Loom.openai_config = { apiKey: process.env.OPENAI_API_KEY }; // Create an agent const agent = new Agent({ name: 'Calculator', purpose: 'Solve mathematical problems', model: 'gpt-4o', tools: [ // Tool definitions ] }); // Run the agent const runner = new Runner(agent); const result = await runner.run('Calculate 23 * 17'); console.log(result.final_message); ``` ## Framework Architecture Loom is designed around a modular architecture: * **[Agent](/docs/reference/agent)**: The primary abstraction for AI capabilities * **[Runner](/docs/reference/runner)**: Manages execution flow and handles agent communication * **[Trace](/docs/reference/trace)**: Provides detailed execution logs and timing information * **[Loom](/docs/reference/loom)**: Global configuration singleton for OpenAI settings ## Key Features * Support for both OpenAI’s Chat Completions and Responses APIs * Tool definition and execution * Sub-agent composition * Tracing and observability * Web search integration # Loom Schema > A concise yet complete guide to building and validating JSON Schemas with Loom Schema. **Loom Schema** is a lightweight toolkit for composing [JSON Schema](https://json-schema.org/) definitions in a fluent manner and validating data against those schemas. It uses [AJV](https://ajv.js.org/) under the hood to perform validation, but abstracts away much of the complexity. You can define schemas for your data, then either compile and validate against them or export them as standard JSON Schemas. ## Features * **Easy Schema Composition**: Write and reuse fragments for strings, numbers, arrays, objects, etc. * **Built-in Validation**: Any schema fragment can validate data directly, returning helpful errors if the validation fails. * **AJV Integration**: Fine-tune validation with AJV’s options, custom keywords, or custom vocabularies. * **TypeScript-Friendly**: Infer TypeScript types from your Loom schemas, ensuring that your data usage aligns with the rules you set. ## Installation ```bash bun install loom-schema ``` ## Basic Usage Example ```ts import { object, string, number, Infer } from "loom-schema"; // Define a schema fragment for a user object const userSchema = object({ properties: { name: string({ minLength: 1 }), age: number({ minimum: 0 }), }, required: ["name", "age"], }); // Validate some data async function runExample() { const result = await userSchema.validate({ name: "Alice", age: 25 }); if (result.valid) { console.log("Data is valid!"); } else { console.error("Validation errors:", result.errors); } } runExample(); // Infer TypeScript types from your schema type User = Infer; ``` In the snippet above: * We create a simple user schema describing an object with two properties (`name` and `age`). * We call `.validate(...)` on the schema to check if a sample data object is valid. * We also demonstrate how to use the `Infer` utility type to derive a TypeScript type from our schema. Continue to the [API Reference](./api) for details on each helper function. # API Reference > Detailed reference for Loom Schema's public API. This document details each publicly exposed function and type in **Loom Schema**. You will see how to create different schema fragments, compose them, and perform validation. *** ## Core Concepts ### Schema Fragments Each function (e.g., `string()`, `object()`, `array()`) returns an object we call a **schema fragment**. A **schema fragment** has two main methods: 1. **`validate(data, ajvOptions?)`**\ Validates the input `data` against this schema. Returns a Promise resolving to: ```ts { valid: boolean; errors: ErrorObject[] | null | undefined; } ``` If `valid` is `false`, `errors` will contain an array of detailed validation issues. 2. **`toSchema(name?)`**\ Generates a valid JSON Schema (as a plain JavaScript object) from this fragment. The optional `name` parameter sets a `$id` and a `title` in the resulting JSON Schema to help identify it. #### TypeScript Integration Most schema-building functions allow you to infer the TypeScript type for your schema using: ```ts type MyType = Infer; ``` This helps ensure your data usage remains consistent with the validation rules you’ve defined. *** ## Base Schema Types Every schema fragment in **Loom Schema** inherits a common set of base properties defined in the `BaseSchemaOptions` interface. These properties provide metadata, identification, and additional capabilities to your schemas. They are available regardless of the specific type (e.g. string, number, object, etc.) you create, ensuring a consistent foundation across all schema fragments. **Inherited Base Properties:** * **`$schema`**: *(optional)* The JSON Schema version identifier (e.g. `"http://json-schema.org/draft/2020-12/schema"`). * **`$id`**: *(optional)* A unique identifier for the schema, generated automatically if not provided. * **`$vocabulary`**: *(optional)* Specifies which vocabularies the schema uses. * **`$comment`**: *(optional)* A comment for documentation purposes. * **`$anchor`**: *(optional)* Defines a URI fragment identifier to reference this schema. * **`$recursiveAnchor`**: *(optional)* Indicates if recursive references are allowed. * **`$recursiveRef`**: *(optional)* A reference to a schema for recursive definitions. * **`title`**: *(optional)* A human-friendly title for the schema. * **`description`**: *(optional)* A detailed explanation of what the schema represents. * **`examples`**: *(optional)* An array of sample values conforming to the schema. * **`deprecated`**: *(optional)* A boolean flag indicating whether the schema is deprecated. * **`readOnly`**: *(optional)* A boolean flag indicating the schema is read-only. * **`writeOnly`**: *(optional)* A boolean flag indicating the schema is write-only. * **`default`**: *(optional)* A default value for the schema. * **Composition Keywords**: * **`allOf`**, **`anyOf`**, **`oneOf`**: Allow combining multiple schemas. * **`not`**: Excludes a specific schema. * **`if`**, **`then`**, **`else`**: Enable conditional schema definitions. * **`$ref`**: *(optional)* A reference to another schema. * **`$defs`**: *(optional)* An object containing subschemas. * **`enum`**: *(optional)* An array of possible constant values. * **`const`**: *(optional)* A constant value that the data must match. These base properties are automatically mixed into every fragment you build (using helpers like `string()`, `number()`, `object()`, etc.), ensuring that your generated JSON Schemas carry all the necessary metadata and compositional abilities. ## Schema-Building Functions ### `string(options?)` Creates a **string** schema fragment. ```ts import { string } from "loom-schema"; const nameSchema = string({ minLength: 1, maxLength: 100 }); ``` **Options** (selected common fields): * `type?: "string"` (internally set to `"string"`) * `format?: string` — e.g., `"email"`, `"uri"`, etc. * `minLength?: number` * `maxLength?: number` * `pattern?: string` *** ### `number(options?)` Creates a **number** schema fragment. ```ts import { number } from "loom-schema"; const scoreSchema = number({ minimum: 0, maximum: 100 }); ``` **Options** (selected common fields): * `type?: "number"` (internally set to `"number"`) * `minimum?: number` * `maximum?: number` * `exclusiveMinimum?: number` * `exclusiveMaximum?: number` * `multipleOf?: number` *** ### `integer(options?)` Creates an **integer** schema fragment (same as number, but `type` set to `"integer"`). ```ts import { integer } from "loom-schema"; const countSchema = integer({ minimum: 0, maximum: 10 }); ``` **Options** (similar to `number`, except `type` is forced to `"integer"`). *** ### `boolean(options?)` Creates a **boolean** schema fragment. ```ts import { boolean } from "loom-schema"; const toggleSchema = boolean(); ``` **Options** (selected common fields): * `type?: "boolean"` (internally set to `"boolean"`) *** ### `nil(options?)` Creates a **null** schema fragment. ```ts import { nil } from "loom-schema"; const nullOnlySchema = nil(); ``` **Options** (selected common fields): * `type?: "null"` (internally set to `"null"`) *** ### `array(options)` Creates an **array** schema fragment. ```ts import { array, number } from "loom-schema"; // An array of numbers const numberArraySchema = array({ items: number() }); ``` **Options**: * `type?: "array"` (internally set to `"array"`) * `items?: JsonSchemaInput` — A fragment or a plain object describing the array items. * `minItems?: number` * `maxItems?: number` * `uniqueItems?: boolean` * And other JSON Schema keywords relevant to arrays. You can pass another Loom fragment to `items` (like `number()`) or use any other schema you have created. *** ### `object(options)` Creates an **object** schema fragment. ```ts import { object, string, number } from "loom-schema"; const userSchema = object({ properties: { name: string({ minLength: 1 }), age: number({ minimum: 0 }), }, required: ["name", "age"], }); ``` **Options**: * `type?: "object"` (internally set to `"object"`) * `properties?: Record` * `required?: string[]` * `additionalProperties?: JsonSchemaInput | boolean` * And other JSON Schema keywords relevant to objects. If you simply pass key-value pairs (without an explicit `properties` field) and do not use reserved keywords, Loom automatically treats them as `properties`. *** ### `schema(options?)` Creates an **object** schema fragment with an easy way to add multiple possible keywords. It can handle advanced usage (like adding `allOf`, `anyOf`, `oneOf`, etc.) while giving you a convenient single entry point. ```ts import { schema } from "loom-schema"; const advancedObject = schema({ type: "object", properties: { exampleKey: { type: "string" }, }, required: ["exampleKey"], }); ``` Most of the time, you will prefer using the dedicated `object`, `string`, `number`, etc. helpers. But `schema()` is a good fallback if you need full manual control. * `schema` accepts fragments just like any other fragment would, as-well. *** ## Composition Functions Sometimes, your data may need to satisfy multiple schemas at once or any of several schemas. Loom offers convenient wrappers for these cases. ### `allOf([...fragments], options?)` Creates a fragment that requires **all** of the given fragments to be satisfied.\ Returns a single fragment that merges the constraints of each included fragment. ```ts import { allOf, string, object } from "loom-schema"; // A string that must be at least 3 characters, and must match a pattern const allOfSchema = allOf([ string({ minLength: 3 }), string({ pattern: "^[a-zA-Z0-9]+$" }), ]); ``` ### `anyOf([...fragments], options?)` Creates a fragment that requires **at least one** of the given fragments to be satisfied.\ Useful for data that can be of multiple permitted shapes. ```ts import { anyOf, string, number } from "loom-schema"; const multiTypeSchema = anyOf([string(), number()]); ``` ### `oneOf([...fragments], options?)` Creates a fragment that requires **exactly one** of the given fragments to be satisfied.\ This is stricter than `anyOf`, enforcing that only one fragment may match. ```ts import { oneOf, object, string } from "loom-schema"; const exclusiveTypeSchema = oneOf([ string({ format: "email" }), object({ properties: { localPart: string(), domain: string() }, required: ["localPart", "domain"], }), ]); ``` ### `conditional({ if, then, else }, options?)` Creates a fragment that conditionally applies different schemas based on an `if` condition.\ If data matches the `if` schema, it must also match `then`. Otherwise, it must match `else`. ```ts import { conditional, object, number, string } from "loom-schema"; const conditionalSchema = conditional({ if: object({ required: ["isSpecial"] }), then: object({ properties: { isSpecial: number() }, required: ["isSpecial"], }), else: string({ format: "email" }), }); ``` *** ## Validation Every fragment has a `validate(data, ajvOptions?)` method: ```ts const result = await myFragment.validate(someData); if (!result.valid) { console.error(result.errors); } else { console.log("Data is valid!"); } ``` * **`data`**: The data you want to validate. * **`ajvOptions`**: Optional settings to pass through to AJV, including: * `customKeywords` — An array of custom keyword definitions or names. * `customVocabularies` — Additional vocabularies to include in validation. * Most other standard AJV options like `allErrors`, `strict`, etc. *** ## Generating JSON Schemas Each fragment also has a `toSchema(name?)` method that outputs a valid JSON Schema. The optional `name` gets used as `$id` and `title`. ```ts const rawSchema = myFragment.toSchema("MyAwesomeSchema"); console.log(JSON.stringify(rawSchema, null, 2)); ``` This is especially useful when you need to store or share the generated JSON Schema, or integrate with other tooling that expects a standard JSON Schema. *** ## Type Utilities ### `Infer` A TypeScript helper that extracts the type from any Loom schema fragment. For instance: ```ts import { object, string, Infer } from "loom-schema"; const userSchema = object({ properties: { name: string() }, required: ["name"], }); // The TS type is { name: string } type User = Infer; ``` This ensures that when you use `userSchema` for validation, you also get a consistent TypeScript type for that data. *** ## Example: Putting It All Together ```ts import { object, string, number, array, anyOf, Infer } from "loom-schema"; // Define a user schema const userSchema = object({ properties: { username: string({ minLength: 3 }), favoriteThings: array({ items: string() }), age: anyOf([ number({ minimum: 13 }), number({ multipleOf: 2, maximum: 12 }), // Possibly allow even ages under 13 (just a silly example) ]), }, required: ["username", "age"], }); // Validate a data sample async function example() { const sample = { username: "abc", age: 14, favoriteThings: ["pizza"] }; const result = await userSchema.validate(sample); if (result.valid) { console.log("Data is valid!"); } else { console.error("Validation errors:", result.errors); } } example(); // Infer the TS type type User = Infer; // { username: string; age: number; favoriteThings?: string[] } ``` *** ## Summary Loom Schema empowers you to define modular, composable JSON Schemas in TypeScript or JavaScript. Each fragment can be validated independently or turned into a standard JSON Schema. Combining fragments is straightforward with functions like `allOf`, `anyOf`, `oneOf`, and `conditional`. For more advanced usage, you can pass AJV-specific options to `validate(...)` or generate named schemas via `toSchema("MyName")`. If you are looking to unify your data models and validations, Loom Schema provides a flexible, type-safe solution.