data-builder is a TypeScript library that provides a schema builder and a type registry. Its primary purpose is to facilitate the definition and management of various data types in a structured and type-safe manner. The library offers key features such as schema building, type registration, value node creation, validation, and type compatibility checking.
You can install this library using:
npm i @hananakick/data-builder
data-builder is a TypeScript library that provides a schema builder and a type registry. Its primary purpose is to facilitate the definition and management of various data types in a structured and type-safe manner. The library offers the following key features and benefits:
-
Schema Builder: Create schemas for different data types including:
- Primitive types (string, number, boolean)
- Arrays (homogeneous arrays and tuples)
- Objects with nested properties
- Custom types with validation logic
-
Type Registry: A centralized registry to manage and retrieve type definitions, ensuring type names are unique and easily accessible.
-
Value Node Factory: Create strongly-typed value nodes that encapsulate both the type information and the actual value, with built-in validation.
-
Schema Validation: Comprehensive validation system that checks values against their schemas, providing detailed error messages.
-
Type Compatibility: Tools to check compatibility between different types based on their schemas.
- Type Safety: Ensures data conforms to defined schemas at runtime
- Extensibility: Easily add custom types with specific validation logic
- Reusability: Centralized type registry promotes code reuse
- Clear Error Reporting: Detailed validation errors help in debugging
Use this library when you need to:
- Validate complex data structures in a type-safe way
- Manage and reuse type definitions across your application
- Ensure data conforms to specific schemas before processing
- Create custom validation logic for specific data formats
This library is particularly useful in applications that require strict data validation and type management, such as APIs, data processing pipelines, or configuration management systems.
This section provides clear, concise examples of how to use the data-builder library. Each example demonstrates a common use case and explains its purpose.
The library allows you to define and register custom types with specific validation logic. This is useful for ensuring data conforms to specific formats.
Create a simple custom type with validation logic:
import { SchemaBuilder, defineType } from 'data-builder';
// Define a custom email type with validation
const EmailType = defineType(
'email',
SchemaBuilder.custom('email', {
validator: (value) => typeof value === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
}),
'Represents an email address format'
);Define a type with nested properties:
import { SchemaBuilder, defineType } from 'data-builder';
// Define a user type with nested properties
const UserType = defineType(
'user',
SchemaBuilder.object({
id: SchemaBuilder.number(),
name: SchemaBuilder.string(),
email: SchemaBuilder.custom('email') // Use the custom email type
}, { required: ['id', 'name'] }),
'Represents a user entity'
);Create a more complex type with nested objects and arrays:
import { SchemaBuilder, defineType } from 'data-builder';
// Define an address type
const AddressType = defineType(
'address',
SchemaBuilder.object({
street: SchemaBuilder.string(),
city: SchemaBuilder.string(),
zip: SchemaBuilder.string().pattern(/^\d{5}$/)
}),
'Represents a mailing address'
);
// Define a company type with nested objects and arrays
const CompanyType = defineType(
'company',
SchemaBuilder.object({
name: SchemaBuilder.string(),
address: SchemaBuilder.custom('address'),
employees: SchemaBuilder.array(SchemaBuilder.custom('user'))
}),
'Represents a company entity'
);The library provides a factory to create strongly-typed value nodes that encapsulate both the type information and the actual value, with built-in validation.
import { ValueNodeFactory } from 'data-builder';
// Create a string value node
const nameNode = ValueNodeFactory.createStringNode('John Doe');
console.log(nameNode); // { type: 'string', value: 'John Doe' }
// Create a number value node
const ageNode = ValueNodeFactory.createNumberNode(25);
console.log(ageNode); // { type: 'number', value: 25 }
// Create a custom email value node
const emailNode = ValueNodeFactory.createTypedNode('email', 'john@example.com');
console.log(emailNode); // { type: 'email', value: 'john@example.com' }
// Attempt to create an invalid email node (will throw an error)
try {
ValueNodeFactory.createTypedNode('email', 'invalid-email');
} catch (error) {
console.error(error.message); // "Schema validation failed for type 'email': Custom validation failed for type: email"
}Purpose: This example demonstrates how to create value nodes for different types, including custom types, and shows how validation works.
The library can check compatibility between different types based on their schemas, which is useful for type validation and conversion.
import { TypeCompatibility } from 'data-builder';
// Check if two types are compatible
const areCompatible = TypeCompatibility.areCompatible('string', 'string');
console.log(areCompatible); // true
// Check compatibility with a reason
const compatibilityResult = TypeCompatibility.checkCompatibility('number', 'string');
console.log(compatibilityResult);
// {
// isCompatible: false,
// reason: "Type mismatch: number is not compatible with string"
// }
// Check if a value node is compatible with a target type
const stringNode = ValueNodeFactory.createStringNode('test');
const nodeCompatibility = TypeCompatibility.isNodeCompatible(stringNode, 'string');
console.log(nodeCompatibility); // { isCompatible: true }Purpose: This example shows how to check type compatibility, which is useful for validating data types and ensuring type safety.
The schema builder allows you to define complex data structures, including arrays, objects, and custom types.
import { SchemaBuilder, defineType } from 'data-builder';
// Define a schema for a homogeneous array of strings
const tagsSchema = SchemaBuilder.array([SchemaBuilder.string()], { minItems: 1 });
console.log(tagsSchema);
// {
// kind: 'array',
// items: [{ kind: 'primitive', type: 'string' }],
// minItems: 1
// }
// Define a schema for a tuple (array with fixed elements)
const pairSchema = SchemaBuilder.array([
SchemaBuilder.string(),
SchemaBuilder.number()
]);
console.log(pairSchema);
// {
// kind: 'array',
// items: [
// { kind: 'primitive', type: 'string' },
// { kind: 'primitive', type: 'number' }
// ]
// }
// Define a schema for a complex object with nested properties
const productSchema = SchemaBuilder.object({
id: SchemaBuilder.number(),
name: SchemaBuilder.string(),
price: SchemaBuilder.number(),
tags: tagsSchema,
details: SchemaBuilder.object({
description: SchemaBuilder.string(),
dimensions: pairSchema
})
});
console.log(productSchema);
// {
// kind: 'object',
// properties: {
// id: { kind: 'primitive', type: 'number' },
// name: { kind: 'primitive', type: 'string' },
// price: { kind: 'primitive', type: 'number' },
// tags: { ... },
// details: { ... }
// },
// additionalProperties: false
// }Purpose: This example demonstrates how to use the schema builder to define complex data structures, including arrays, tuples, and nested objects.
This section provides a comprehensive overview of the library's API, documenting all major classes, functions, and methods with type information, parameter descriptions, and usage examples.
- SchemaBuilder
- TypeRegistry
- ValueNodeFactory
- SchemaValidator
- TypeCompatibility
- Utility Functions
- defineType
The SchemaBuilder object provides methods for creating different kinds of schema definitions.
Creates a schema definition for a primitive 'string' type.
SchemaBuilder.string(): PrimitiveSchemaReturns:
PrimitiveSchema: A schema object representing a string.
Example:
const nameSchema = SchemaBuilder.string();
// Result: { kind: 'primitive', type: 'string' }Creates a schema definition for a primitive 'number' type.
SchemaBuilder.number(): PrimitiveSchemaReturns:
PrimitiveSchema: A schema object representing a number.
Example:
const ageSchema = SchemaBuilder.number();
// Result: { kind: 'primitive', type: 'number' }Creates a schema definition for a primitive 'boolean' type.
SchemaBuilder.boolean(): PrimitiveSchemaReturns:
PrimitiveSchema: A schema object representing a boolean.
Example:
const isActiveSchema = SchemaBuilder.boolean();
// Result: { kind: 'primitive', type: 'boolean' }Creates a schema definition for an 'array' type.
Parameters:
items(Schema[]): An array of schema definitions.- For a homogeneous array (e.g., array of strings), pass a single-element array:
[SchemaBuilder.string()]. - For a tuple (e.g.,
[string, number]), pass an array with schemas for each element:[SchemaBuilder.string(), SchemaBuilder.number()].
- For a homogeneous array (e.g., array of strings), pass a single-element array:
options(object, optional):minItems(number, optional): The minimum number of items the array must contain.maxItems(number, optional): The maximum number of items the array can contain.
Returns:
ArraySchema: A schema object representing an array.
Example:
// Homogeneous array of strings
const tagsSchema = SchemaBuilder.array([SchemaBuilder.string()], { minItems: 1 });
// Result: { kind: 'array', items: [{ kind: 'primitive', type: 'string' }], minItems: 1 }
// Tuple of a string and a number
const pairSchema = SchemaBuilder.array([
SchemaBuilder.string(),
SchemaBuilder.number()
]);
// Result: { kind: 'array', items: [{ kind: 'primitive', type: 'string' }, { kind: 'primitive', type: 'number' }] }
// Array of numbers with min and max items (homogeneous)
const scoresSchema = SchemaBuilder.array(
[SchemaBuilder.number()], // Note: items is an array
{ minItems: 1, maxItems: 5 }
);
// Result: { kind: 'array', items: [{ kind: 'primitive', type: 'number' }], minItems: 1, maxItems: 5 }object(properties: Record<string, Schema>, options?: { required?: string[]; additionalProperties?: boolean }): ObjectSchema
Creates a schema definition for an 'object' type.
Parameters:
properties(Record<string, Schema>): An object where keys are property names and values are their schema definitions.options(object, optional):required(string[], optional): An array of property names that are required for this object.additionalProperties(boolean, optional): Whether to allow properties not defined in the schema. Defaults to false.
Returns:
ObjectSchema: A schema object representing an object.
Example:
// Simple object
const pointSchema = SchemaBuilder.object({
x: SchemaBuilder.number(),
y: SchemaBuilder.number()
});
// Result: { kind: 'object', properties: { x: { kind: 'primitive', type: 'number' }, y: { kind: 'primitive', type: 'number' } }, additionalProperties: false }
// Object with required properties and allowing additional properties
const userProfileSchema = SchemaBuilder.object(
{
id: SchemaBuilder.string(),
username: SchemaBuilder.string(),
email: SchemaBuilder.custom('email') // Assumes 'email' custom type is defined
},
{ required: ['id', 'username'], additionalProperties: true }
);
// Result: {
// kind: 'object',
// properties: { id: ..., username: ..., email: ... },
// required: ['id', 'username'],
// additionalProperties: true
// }custom(typeName: string, options?: { validator?: (value: any) => boolean; innerSchema?: Schema }): CustomSchema
Creates a schema definition for a 'custom' type.
Parameters:
typeName(string): The unique name identifying this custom type.options(object, optional):validator((value: any) => boolean, optional): A function to validate the value of this custom type. Should return true if valid, false otherwise.innerSchema(Schema, optional): An underlying schema that this custom type is based on or extends.
Returns:
CustomSchema: A schema object representing a custom type.
Example:
// Custom type with a validator
const emailSchema = SchemaBuilder.custom('email', {
validator: (value) => typeof value === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
});
// Result: { kind: 'custom', typeName: 'email', validator: [Function] }
// Custom type based on an inner schema (e.g., a positive number)
const positiveNumberSchema = SchemaBuilder.custom('positiveNumber', {
innerSchema: SchemaBuilder.number(),
validator: (value) => typeof value === 'number' && value > 0
});
// Result: { kind: 'custom', typeName: 'positiveNumber', innerSchema: { kind: 'primitive', type: 'number' }, validator: [Function] }
// Custom type that just references another registered type
const userIdSchema = SchemaBuilder.custom('UUID');
// Result: { kind: 'custom', typeName: 'UUID' }
// (Assumes a 'UUID' type is registered elsewhere with its own schema and/or validator)
// Custom type with only an inner schema (useful for aliasing or adding semantic meaning)
const productCodeSchema = SchemaBuilder.custom('ProductCode', { innerSchema: SchemaBuilder.string() });
// Result: { kind: 'custom', typeName: 'ProductCode', innerSchema: { kind: 'primitive', type: 'string' } }The TypeRegistry class manages the registration and retrieval of type definitions.
Registers a new type definition.
Parameters:
typeDefinition(TypeDefinition): The type definition object to register.
Throws:
Error: If the type name is already registered.
Example:
import { defineType } from './core/registry';
import { SchemaBuilder } from './core/registry';
const EmailType = defineType(
'email',
SchemaBuilder.custom('email', {
validator: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
}),
'Represents an email address format'
);Retrieves a type definition by its name.
Parameters:
typeName(string): The name of the type to retrieve.
Returns:
TypeDefinition | undefined: The type definition if found, otherwise undefined.
Checks if a type with the given name is registered.
Parameters:
typeName(string): The name of the type to check.
Returns:
boolean: True if the type is registered, false otherwise.
Retrieves all registered type definitions.
Returns:
ReadonlyMap<string, TypeDefinition>: A read-only map of all type definitions.
Retrieves the names of all registered types.
Returns:
string[]: An array of registered type names.
The ValueNodeFactory class provides methods for creating value nodes with type validation.
Creates a typed value node with validation.
Parameters:
typeName(T): The name of the type.value(U): The value to create a node for.
Returns:
ValueNode<T, U>: A value node with the specified type and value.
Throws:
Error: If the type is unknown or validation fails.
Example:
import { ValueNodeFactory } from './factory/index';
const stringNode = ValueNodeFactory.createStringNode("hello");Creates a string value node.
Parameters:
value(string): The string value.
Returns:
ValueNode<'string', string>: A value node with the string value.
Example:
const nameNode = ValueNodeFactory.createStringNode("John Doe");Creates a number value node.
Parameters:
value(number): The number value.
Returns:
ValueNode<'number', number>: A value node with the number value.
Example:
const ageNode = ValueNodeFactory.createNumberNode(25);Creates a boolean value node.
Parameters:
value(boolean): The boolean value.
Returns:
ValueNode<'boolean', boolean>: A value node with the boolean value.
Example:
const isActiveNode = ValueNodeFactory.createBooleanNode(true);tryCreateNode<T extends string, U>(typeName: T, value: U): { success: true; node: ValueNode<T, U> } | { success: false; error: string }
Attempts to create a typed value node with validation, returning a result object.
Parameters:
typeName(T): The name of the type.value(U): The value to create a node for.
Returns:
{ success: true; node: ValueNode<T, U> }: If successful, contains the created value node.{ success: false; error: string }: If failed, contains the error message.
Example:
const result = ValueNodeFactory.tryCreateNode('email', 'test@example.com');
if (result.success) {
console.log(result.node);
} else {
console.error(result.error);
}The SchemaValidator class provides methods for validating values against schemas.
Validates a value against a schema.
Parameters:
value(unknown): The value to validate.schema(Schema): The schema to validate against.
Returns:
ValidationResult: An object containing validation results.
Example:
import { SchemaValidator } from './validation/index';
import { SchemaBuilder } from './core/registry';
const schema = SchemaBuilder.string();
const result = SchemaValidator.validateValue("hello", schema);
console.log(result.isValid); // trueThe TypeCompatibility class provides methods for checking type compatibility.
Checks if two types are compatible.
Parameters:
sourceTypeName(string): The name of the source type.targetTypeName(string): The name of the target type.
Returns:
boolean: True if the types are compatible, false otherwise.
Example:
import { TypeCompatibility } from './core/types';
const areCompatible = TypeCompatibility.areCompatible('string', 'string');
console.log(areCompatible); // trueChecks if two types are compatible and returns a detailed result.
Parameters:
sourceTypeName(string): The name of the source type.targetTypeName(string): The name of the target type.
Returns:
CompatibilityResult: An object containing compatibility results and reasons.
Example:
const result = TypeCompatibility.checkCompatibility('number', 'string');
console.log(result.isCompatible); // false
console.log(result.reason); // "Type mismatch: number is not compatible with string"Checks if a value node is compatible with a target type.
Parameters:
node(ValueNode): The value node to check.targetTypeName(string): The name of the target type.
Returns:
CompatibilityResult: An object containing compatibility results and reasons.
Example:
const stringNode = ValueNodeFactory.createStringNode('test');
const result = TypeCompatibility.isNodeCompatible(stringNode, 'string');
console.log(result.isCompatible); // trueValidates a value against a registered type and returns an array of error messages.
Parameters:
typeName(string): The name of the type to validate against.value(unknown): The value to validate.
Returns:
string[]: An array of error messages, or an empty array if the value is valid.
Example:
import { validateTypeValue } from './core/registry';
const errors = validateTypeValue('email', 'invalid-email');
console.log(errors); // ["Schema validation failed for type 'email': Custom validation failed for type: email"]Inspects a registered type and logs its details to the console.
Parameters:
typeName(string): The name of the type to inspect.
Example:
import { inspectType } from './core/registry';
inspectType('email');
// Output: {
// name: 'email',
// schema: { kind: 'custom', typeName: 'email', validator: [Function] },
// description: 'Represents an email address format'
// }Defines and registers a new type with a schema and optional description.
defineType<T extends string>(
name: T,
schema: Schema,
description?: string
): TypeDefinition<T>Parameters:
name(T): The name of the type. This must be a unique string that identifies the type.schema(Schema): The schema definition for the type. This can be created using theSchemaBuilder.description(string, optional): A description of the type, explaining its purpose or usage.
Returns:
TypeDefinition<T>: The registered type definition, which includes the name, schema, and description.
Example:
import { SchemaBuilder, defineType } from 'data-builder';
// Define a custom email type with validation
const EmailType = defineType(
'email',
SchemaBuilder.custom('email', {
validator: (value) => typeof value === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
}),
'Represents an email address format'
);