- Overview - Back to API reference index
- Compiler Reference - Using the .chalk compiler programmatically
- Custom Parsers - Creating custom parsers and parser priority
- Primitives Reference - Built-in data types and elements in .chalk
- Type Reference - Complete TypeScript type definitions
- Troubleshooting - Common issues and solutions
Library Reference
Complete API documentation for creating and managing .chalk libraries. This guide covers the redesigned, simplified API for defining libraries, elements, attribute types (including enumerations), and document classes.
Table of Contents
- Overview
- Definition Merging
- Creating a Library
- Defining Document Classes
- Defining Elements
- Defining Inline Elements
- Defining Types
- Defining Enums
- Library Configuration
- Document Classes and Scoping
- Content Policies
- Attribute Specifications
- Validating Libraries
- Complete Examples
Overview
A .chalk library defines the structure and behaviour of your .chalk documents. Libraries contain:
- Document Classes: Multiple document types with their own schemas, attributes, and configurations
- Element Definitions: Custom element types with body/detail policies and attributes (can be scoped to specific document classes)
- Attribute Type Definitions: Custom types for validating and parsing attribute values (can be scoped to specific document classes)
- Custom Types: Types with parsing/validation logic using the
.type()method - Enumerations: Types that restrict values to a fixed set of strings using the
.enum()method
- Custom Types: Types with parsing/validation logic using the
Key Features
- Fluent API: All methods return the library instance for chaining
- Multiple Document Classes: Define different document types in a single library
- Scoped Definitions: Elements and attribute types (including enums) can be scoped to specific document classes
- Array-Based Attributes: Clean, consistent attribute definition format
- Type Safety: Strong TypeScript support with type inference
- Primitives Included: Every library includes built-in primitives (paragraph, h1-h6) and attribute types (string, number, boolean, etc.)
Definition Merging
One of the most powerful features of the .chalk library API is definition merging. When you call .element(), .type(), .enum(), or .document() multiple times with the same identifier, the definitions are merged rather than replaced.
How Merging Works
- Only specified properties are overridden - If you don’t specify a property in the second call, it’s preserved from the first
- Attributes are merged by identifier - When merging attributes arrays, attributes with the same
idare replaced; new attributes are added - Primitives can be overridden - You can extend or customise built-in primitives (like
paragraph,h1, etc.) - Multiple chains work - You can call the same definition method three or more times to incrementally build up a definition
Element Merging Examples
// Base definition
lib.element({
id: 'section',
body: 'all',
attributes: [
{ id: 'title', type: 'string' }
]
});
// Add detail policy and new attribute
lib.element({
id: 'section',
detail: 'literal', // Adds detail policy
attributes: [
{ id: 'id', type: 'string' } // Adds new attribute
]
});
// Result: section has body='all', detail='literal', and both title and id attributes
Override Specific Attribute Properties
// Initial definition
lib.element({
id: 'item',
attributes: [
{ id: 'name', type: 'string', required: true },
{ id: 'count', type: 'number', default: 1 }
]
});
// Modify the name attribute
lib.element({
id: 'item',
attributes: [
{ id: 'name', type: 'string', required: false, aliases: ['title'] }
// count attribute is preserved from first definition
]
});
// Result: name is now optional with an alias, count is unchanged
Overriding Primitives
// Add custom attributes to the built-in paragraph element
lib.element({
id: 'paragraph',
attributes: [
{ id: 'align', type: 'string', default: 'left' }
]
});
// Replace the built-in h1 parser
lib.element({
id: 'h1',
parser: (input) => {
// Custom parsing logic
return { ok: true, data: input.toUpperCase() };
},
parserPriority: 25 // Higher priority than default
});
// Result: paragraph has the new align attribute plus all primitive behaviour
// h1 uses the custom parser but keeps its body='literal' and other properties
Type Merging
lib.type({
id: 'email',
documentClasses: ['article']
});
lib.type({
id: 'email',
parse: (input) => {
if (!input.includes('@')) return { error: 'Invalid email' };
return { value: input.toLowerCase() };
}
});
// Result: email type has both the parser and documentClasses
Enum Merging
lib.enum({
id: 'status',
values: ['draft', 'published'],
documentClasses: ['article']
});
lib.enum({
id: 'status',
values: ['draft', 'published', 'archived'] // Override values
});
// Result: status has the new values but keeps documentClasses
Document Class Merging
lib.document({
name: 'article',
body: 'all',
attributes: [
{ id: 'title', type: 'string', required: true }
]
});
lib.document({
name: 'article',
attributes: [
{ id: 'author', type: 'string', required: true }
]
});
// Result: article has body='all' and both title and author attributes
Multiple Chains
lib
.element({
id: 'widget',
body: null,
attributes: [{ id: 'a', type: 'string' }]
})
.element({
id: 'widget',
detail: 'literal',
attributes: [{ id: 'b', type: 'string' }]
})
.element({
id: 'widget',
body: 'all', // Override body
attributes: [{ id: 'c', type: 'string' }]
});
// Result: widget has body='all', detail='literal', and attributes a, b, and c
Best Practices
- Use merging for progressive refinement - Define base behaviour first, then add specifics
- Override primitives sparingly - Only when you need to fundamentally change behaviour
- Group related definitions - Keep merging calls close together in your code
- Document your intentions - Use comments when merging changes non-obvious behaviour
Creating a Library
chalk.library()
Creates a new library instance. Takes no parameters - document classes and other definitions are added via method chaining.
Signature:
chalk.library(): Library
Example:
import chalk from '@jackhgns/dotchalk';
// Create a library
const lib = chalk.library();
Defining Document Classes
lib.document(spec)
Defines a document class in the library. You can define multiple document classes, each with their own schema, attributes, body policy, and formatting configuration.
Signature:
lib.document(spec: {
name: string; // Required: document class name
body?: ContentPolicy; // Optional: what content is allowed in document body
attributes?: AttributeSpec[]; // Optional: array of header attribute definitions
config?: { // Optional: document-specific configuration
formatting?: {
bold?: boolean; // Enable **bold** syntax (default: true)
italic?: boolean; // Enable *italic* syntax (default: true)
code?: boolean; // Enable `code` syntax (default: true)
latex?: boolean; // Enable $math$ syntax (default: true)
links?: boolean; // Enable [text](url) syntax (default: true)
};
};
}): Library
Parameters:
- name (string, required): Unique identifier for the document class
- body (ContentPolicy, optional): Defines allowed content. Default:
'all''all': Any element allowedstring[]: Specific element identifiersstring: Single element identifier
Note: Unlike elements, documents cannot use
nullor'literal'body policies. - attributes (AttributeSpec[], optional): Array of header attribute definitions (see Attribute Specifications)
- config (object, optional): Document-specific formatting configuration
Examples:
// Simple document class
lib.document({
name: 'article',
body: 'all',
attributes: [
{ id: 'title', type: 'string', required: true },
{ id: 'author', type: 'string' }
]
});
// Multiple document classes in one library
lib
.document({
name: 'tutorial',
body: ['section', 'code', 'paragraph'],
attributes: [
{ id: 'title', type: 'string', required: true },
{ id: 'difficulty', type: 'string' }
]
})
.document({
name: 'blogPost',
body: 'all',
attributes: [
{ id: 'title', type: 'string', required: true },
{ id: 'author', type: 'string' },
{ id: 'published', type: 'boolean', default: false }
]
});
// With custom formatting configuration
lib.document({
name: 'mathPaper',
body: 'all',
attributes: [
{ id: 'title', type: 'string', required: true },
{ id: 'abstract', type: 'string' }
],
config: {
formatting: {
latex: true, // Enable LaTeX for math papers
bold: true,
italic: true,
code: true,
links: false // Disable link syntax
}
}
});
Defining Elements
lib.element(spec)
Defines a new element in the library. Elements are the building blocks of documents and can be scoped to specific document classes.
Signature:
lib.element(spec: {
id: string; // Required: element identifier
aliases?: string[]; // Optional: alternative names
body?: ContentPolicy; // Optional: what content allowed in body
detail?: ContentPolicy; // Optional: what content allowed in detail
attributes?: AttributeSpec[]; // Optional: array of attribute definitions
parser?: (input: string) => Result<ElementContent>; // Optional: custom parser
parserPriority?: number; // Optional: parser priority (higher = tried first, default: 0)
documentClasses?: string[]; // Optional: scope to specific document classes
}): Library
Parameters:
- id (string, required): Unique identifier for the element. Must start with letter or underscore, can contain letters, numbers, underscores, hyphens, and periods. Cannot end with a period. Examples:
'section','ui.button','component.card.fancy' - aliases (string[], optional): Alternative names for shorthand usage
- body (ContentPolicy, optional): Defines allowed body content. Default:
'all' - detail (ContentPolicy, optional): Defines allowed detail content. Default:
null - attributes (AttributeSpec[], optional): Array of attribute definitions (see Attribute Specifications)
- parser (function, optional): Custom parsing function for non-standard syntax
- parserPriority (number, optional): Priority for custom parser execution order. Higher values are tried first when multiple parsers could match. Default:
5. Common values: headings=20, lists=15, paragraphs=0 (last resort). - documentClasses (string[], optional): Limits element to specific document classes
Examples:
// Basic element with attributes
lib.element({
id: 'section',
aliases: ['sec'],
body: ['paragraph', 'h1', 'h2'],
attributes: [
{ id: 'title', type: 'string', required: true },
{ id: 'id', type: 'string' }
]
});
// Element with period-based namespacing
lib.element({
id: 'ui.button',
body: 'literal',
attributes: [
{ id: 'variant', type: 'string', default: 'primary' }
]
});
// Deeply nested namespacing
lib.element({
id: 'component.card.fancy',
body: 'all',
detail: 'literal',
attributes: [
{ id: 'color', type: 'string' }
]
});
});
// Element with body and detail
lib.element({
id: 'spoiler',
body: ['paragraph'], // Visible content
detail: ['paragraph'], // Hidden content
attributes: [
{ id: 'revealed', type: 'boolean', default: false }
]
});
// Scoped to specific document classes
lib.element({
id: 'abstract',
body: 'literal',
attributes: [
{ id: 'wordCount', type: 'string' }
],
documentClasses: ['article', 'mathPaper'] // Only in these document classes
});
// Element with custom parser
lib.element({
id: 'link',
aliases: ['lnk'],
body: 'literal',
attributes: [
{ id: 'url', type: 'string', required: true },
{ id: 'target', type: 'string' }
],
parser: (input) => {
// Custom parser for [text](url) syntax
const match = input.match(/^\[(.+?)\]\((.+?)\)$/);
if (!match) {
return { ok: false, error: 'Invalid link syntax. Expected [text](url)' };
}
return { ok: true, data: match[1] }; // Return link text as body
}
});
Defining Inline Elements
lib.inlineElement(spec)
Defines a new inline element in the library. Inline elements work within text content (paragraphs, headings, etc.) to provide formatting and semantic markup. They can use full syntax (!id{body}(attributes)) or optional shorthand notation.
Signature:
lib.inlineElement(spec: {
id: string; // Required: inline element identifier
aliases?: string[]; // Optional: alternative names
body?: InlinePolicy; // Optional: what inline content allowed in body
attributes?: AttributeSpec[]; // Optional: array of attribute definitions
shorthand?: string; // Optional: shorthand delimiter (e.g., '===')
documentClasses?: string[]; // Optional: scope to specific document classes
}): Library
Parameters:
- id (string, required): Unique identifier for the inline element. Must start with letter or underscore, can contain letters, numbers, underscores, hyphens, and periods. Cannot end with a period. Examples:
'bold','icon.star','ui.component.badge' - aliases (string[], optional): Alternative names for the inline element
- body (InlinePolicy, optional): Controls what inline elements can appear inside this element’s body
inline('all')- Allow all inline elements (default)inline(['bold', 'italic'])- Only specific inline elementsinline(null)- No inline elements (literal text only)
- attributes (AttributeSpec[], optional): Array of attribute definitions
- shorthand (string, optional): Custom shorthand delimiter for this inline element (e.g.,
'==='for===text===) - documentClasses (string[], optional): Limits inline element to specific document classes
Examples:
import chalk from '@jackhgns/dotchalk';
// Basic inline element with attributes
lib.inlineElement({
id: 'highlight',
body: chalk.inline('all'), // Allow nested inline elements
attributes: [
{ id: 'color', type: 'string', default: 'yellow' }
]
});
// Usage: !highlight{important text}(color: pink)
// With custom shorthand
lib.inlineElement({
id: 'highlight',
body: chalk.inline('all'),
shorthand: '===' // Custom delimiter
});
// Usage: ===highlighted text===
// No nesting allowed - literal text only
lib.inlineElement({
id: 'abbr',
body: chalk.inline(null), // No inline formatting
attributes: [
{ id: 'title', type: 'string', required: true }
]
});
// Usage: !abbr{HTML}(title: HyperText Markup Language)
// Restricted nesting
lib.inlineElement({
id: 'tooltip',
body: chalk.inline(['bold', 'italic']), // Only bold and italic inside
attributes: [
{ id: 'text', type: 'string', required: true }
]
});
// Scoped to specific document classes
lib.inlineElement({
id: 'citation',
body: chalk.inline(null),
attributes: [
{ id: 'source', type: 'string', required: true }
],
documentClasses: ['article', 'research']
});
Notes:
- Inline elements must be on a single line (cannot contain newlines)
- Primitive inline elements (
bold,italic,code,latex) are automatically available - Shorthand syntax cannot include attributes (use full syntax for attributes)
- The
inline()helper function is imported from@jackhgns/dotchalk
Defining Types
lib.type(spec)
Defines a custom attribute type with validation and parsing logic. Types can be scoped to specific document classes.
Attribute types are used to validate and transform attribute values. Both .type() and .enum() define attribute types—the difference is that .enum() provides a convenient shorthand for types that restrict values to a fixed set of strings, whilst .type() allows custom parsing logic for more complex validation.
Signature:
lib.type(spec: {
id: string; // Required: type identifier
parse?: (input: string) => any | { value: any; error?: string }; // Optional: parser
documentClasses?: string[]; // Optional: scope to specific document classes
}): Library
Parameters:
- id (string, required): Unique identifier for the type
- parse (function, optional): Parsing/validation function. Can return:
- Direct value (any type) for success
{ value: any }for success with explicit value{ value: any, error: string }for success with warning{ error: string }for validation failure
- documentClasses (string[], optional): Limits type to specific document classes
Examples:
// Global type - available in all document classes
lib.type({
id: 'email',
parse: (input) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(input)) {
return { error: 'Invalid email format' };
}
return { value: input.toLowerCase() };
}
});
// Scoped type - only in specific document classes
lib.type({
id: 'percentage',
parse: (input) => {
const num = parseFloat(input);
if (num < 0 || num > 100) {
return { error: 'Must be between 0 and 100' };
}
return { value: num };
},
documentClasses: ['mathPaper', 'article']
});
// Simple type without validation
lib.type({
id: 'url' // Accepts any string
});
Defining Enums
lib.enum(spec)
Defines an enumeration attribute type with a fixed set of allowed string values. Enums are a specialised category of attribute types that restrict values to a predefined list, providing compile-time validation. Like custom types, enums can be scoped to specific document classes and are referenced by their identifier in attribute definitions.
Enums are functionally equivalent to types with validation logic, but provide a cleaner syntax for the common case of restricting to a fixed set of values.
Signature:
lib.enum(spec: {
id: string; // Required: enum identifier
values: string[]; // Required: array of allowed values
documentClasses?: string[]; // Optional: scope to specific document classes
}): Library
Parameters:
- id (string, required): Unique identifier for the enum
- values (string[], required): Array of allowed string values
- documentClasses (string[], optional): Limits enum to specific document classes
Examples:
// Global enum - available in all document classes
lib.enum({
id: 'status',
values: ['draft', 'published', 'archived']
});
// Scoped enum - only in specific document classes
lib.enum({
id: 'difficulty',
values: ['beginner', 'intermediate', 'advanced'],
documentClasses: ['tutorial']
});
// Multiple enums
lib
.enum({
id: 'priority',
values: ['low', 'medium', 'high', 'critical']
})
.enum({
id: 'category',
values: ['tech', 'design', 'business']
});
Library Configuration
lib.config(options)
Updates the global configuration for the library. Settings specified here serve as defaults that can be overridden at the document class level.
Signature:
lib.config(options: {
formatting?: {
bold?: boolean; // Enable **bold** syntax
italic?: boolean; // Enable *italic* syntax
code?: boolean; // Enable `code` syntax
latex?: boolean; // Enable $math$ syntax
links?: boolean; // Enable [text](url) syntax
};
}): Library
Parameters:
- formatting.bold (boolean, optional): Enable
**bold**syntax. Default: true - formatting.italic (boolean, optional): Enable
*italic*syntax. Default: true - formatting.code (boolean, optional): Enable
`code`syntax. Default: true - formatting.latex (boolean, optional): Enable
$math$syntax. Default: true - formatting.links (boolean, optional): Enable
[text](url)syntax. Default: true
Examples:
// Disable LaTeX and links globally
library.config({
formatting: {
latex: false,
links: false
}
});
// Enable only basic formatting
library.config({
formatting: {
bold: true,
italic: true,
code: false,
latex: false,
links: false
}
});
Document Classes and Scoping
Elements and attribute types (both custom types and enums) can be scoped to specific document classes using the documentClasses property. This allows you to create specialised definitions that only apply to certain document types.
Global Definitions
When documentClasses is omitted, the definition is available in all document classes:
lib.element({
id: 'paragraph',
body: 'literal',
attributes: []
});
// Available in all document classes
Scoped Definitions
When documentClasses is specified, the definition is only available in those document classes:
lib.element({
id: 'theorem',
body: ['paragraph'],
attributes: [
{ id: 'number', type: 'string' }
],
documentClasses: ['mathPaper'] // Only in mathPaper documents
});
lib.type({
id: 'difficulty',
parse: (input) => {
const valid = ['beginner', 'intermediate', 'advanced'];
if (!valid.includes(input)) {
return { error: 'Invalid difficulty level' };
}
return { value: input };
},
documentClasses: ['tutorial', 'course'] // Only in these document classes
});
Example: Multi-Document Library
const lib = chalk.library()
// Define document classes
.document({
name: 'tutorial',
body: 'all',
attributes: [
{ id: 'title', type: 'string', required: true },
{ id: 'difficulty', type: 'string' }
]
})
.document({
name: 'mathPaper',
body: 'all',
attributes: [
{ id: 'title', type: 'string', required: true },
{ id: 'abstract', type: 'string' }
],
config: { formatting: { latex: true } }
})
// Scoped enum (tutorial only)
.enum({
id: 'difficulty',
values: ['beginner', 'intermediate', 'advanced'],
documentClasses: ['tutorial']
})
// Scoped element (tutorial only)
.element({
id: 'step',
body: ['paragraph', 'code'],
attributes: [
{ id: 'number', type: 'string' }
],
documentClasses: ['tutorial']
})
// Scoped element (mathPaper only)
.element({
id: 'theorem',
body: ['paragraph'],
attributes: [
{ id: 'name', type: 'string' }
],
documentClasses: ['mathPaper']
})
// Global element (all document classes)
.element({
id: 'note',
body: 'literal',
attributes: []
});
// Compile as tutorial (has step, note, but NOT theorem)
const tutorialResult = chalk.compile(source, lib, 'tutorial');
// Compile as mathPaper (has theorem, note, but NOT step)
const paperResult = chalk.compile(source, lib, 'mathPaper');
Attribute Specifications
Attributes are defined using an array of attribute specification objects. This provides a clean, consistent format for all attribute definitions.
The type property references an attribute type identifier—this can be a built-in primitive type (like 'string', 'number', 'boolean'), a custom type defined with .type(), or an enumeration defined with .enum(). Both .type() and .enum() create attribute types that can be referenced by their id in attribute specifications.
AttributeSpec Format
interface AttributeSpec {
id: string; // Required: attribute identifier/name
type?: string; // Optional: attribute type (default: 'string')
required?: boolean; // Optional: whether attribute must be provided (default: false)
default?: any; // Optional: default value if not provided
aliases?: string[]; // Optional: alternative names for this attribute
description?: string; // Optional: human-readable description
}
Examples
// Basic attributes
lib.element({
id: 'section',
attributes: [
{ id: 'title', type: 'string', required: true },
{ id: 'id', type: 'string' }
]
});
// With defaults
lib.element({
id: 'button',
attributes: [
{ id: 'label', type: 'string', required: true },
{ id: 'type', type: 'string', default: 'primary' },
{ id: 'disabled', type: 'boolean', default: false }
]
});
// With aliases
lib.element({
id: 'code',
attributes: [
{ id: 'language', type: 'programmingLang' },
{ id: 'programming language', type: 'programmingLang' }, // Multi-word identifier
{ id: 'interactive', type: 'boolean', default: false },
{ id: 'is interactive', type: 'boolean' } // Alias with spaces
]
});
// Document header attributes
lib.document({
name: 'article',
attributes: [
{ id: 'title', type: 'string', required: true },
{ id: 'author', type: 'string', required: true },
{ id: 'published', type: 'boolean', default: false },
{ id: 'published status', type: 'string' } // Multi-word identifier
]
});
Multi-Word Identifiers
Attribute identifiers can contain spaces for improved readability:
lib.element({
id: 'article',
attributes: [
{ id: 'published status', type: 'string' },
{ id: 'is featured', type: 'boolean', default: false },
{ id: 'content type', type: 'string', default: 'article' }
]
});
Custom Parsers
For detailed information about creating custom parsers and controlling parser priority, see the Custom Parsers reference page.
Custom parsers allow you to define non-standard syntax for elements. You can control the order in which parsers are tried using the parserPriority property (default: 5, headings: 20, paragraphs: 0).
Quick Example:
lib.element({
id: 'link',
body: 'literal',
attributes: [{ id: 'url', type: 'string' }],
parserPriority: 15, // Medium priority
parser: (input) => {
const match = input.match(/^\[(.+?)\]\((.+?)\)$/);
if (!match) return { ok: false, error: 'Expected [text](url)' };
return { ok: true, data: /* ... */ };
}
});
See: Custom Parsers Reference for complete documentation on parser priority, best practices, and examples.
Content Policies
Content policies define what content is allowed in document bodies, element bodies, and element details.
Policy Types
| Policy | Description | Empty Content Result | Example |
|---|---|---|---|
null |
No content allowed | null |
detail: null |
"literal" |
Raw text only, no parsing | "" (empty string) |
body: "literal" |
"all" |
All element types allowed | null |
body: "all" |
string[] |
Specific element types | null |
body: ["paragraph", "h1", "h2"] |
Note: When content is empty (e.g.,
{}or[]),literalpolicy returns an empty string""to preserve the explicit empty value, while other policies returnnullsince there are no elements to contain.
Examples
Literal Text Only:
lib.element({
id: 'code',
body: 'literal', // Body contains raw code, no parsing
detail: null, // No detail
attributes: [
{ id: 'language', type: 'string' }
]
});
Specific Elements:
lib.element({
id: 'section',
body: ['paragraph', 'h1', 'h2', 'h3'], // Only these elements allowed
attributes: []
});
All Elements:
lib.element({
id: 'container',
body: 'all', // Any element type allowed
attributes: []
});
Body and Detail:
lib.element({
id: 'spoiler',
body: ['paragraph'], // Body: visible content
detail: ['paragraph'], // Detail: hidden content
attributes: [
{ id: 'revealed', type: 'boolean', default: false }
]
});
Validating Libraries
Note: While the validateLibrary function can be called explicitly, it is automatically invoked by both compile and compileFile before any compilation occurs. Explicit validation is useful for testing library definitions or providing early feedback during library construction.
Validates a library definition to ensure it has at least one document class defined and that all definitions are internally consistent.
This function performs comprehensive validation checks:
- Document internal class names match their keys
- Element identifiers and aliases are unique (case-insensitive)
- Element internal identifiers match their keys
- Type and enum identifiers are unique and non-empty
- Element body/detail policies reference valid elements
- Document body policies reference valid elements
- Attribute types reference defined types or enums
- Attribute identifiers and aliases are unique within their scope (case-insensitive)
- Enums have at least one value with no duplicates
- No naming conflicts between types and enums
- Enums have at least one value
- No duplicate enum values
Signature:
chalk.validateLibrary(library: Library): Result<void, string>
Parameters:
library(Library) - The library to validate
Returns: Result object with either success or validation error
Example:
import chalk from '@jackhgns/dotchalk';
const lib = chalk.library()
.document({
name: 'article',
body: 'all',
attributes: [
{ id: 'title', type: 'string', required: true },
{ id: 'author', type: 'string' }
]
})
.element({
id: 'section',
body: ['paragraph', 'h1', 'h2'],
attributes: []
});
const result = chalk.validateLibrary(lib);
if (result.ok) {
console.log('Library is valid!');
} else {
console.error('Library validation failed:', result.error);
}
Validation Errors:
The validator catches common mistakes:
// Error: No document class defined
const lib1 = chalk.library()
.element({ id: 'note', body: 'literal', attributes: [] });
chalk.validateLibrary(lib1);
// Returns: { ok: false, error: 'Library must define at least one document class using .document()' }
// Error: Undefined type reference
const lib2 = chalk.library()
.document({
name: 'article',
body: 'all',
attributes: [
{ id: 'priority', type: 'priority-level' } // 'priority-level' not defined
]
});
chalk.validateLibrary(lib2);
// Returns: { ok: false, error: "Document 'article' attribute 'priority' references undefined type 'priority-level'" }
// Error: Undefined element in body policy
const lib3 = chalk.library()
.document({
name: 'tutorial',
body: ['step', 'note'], // 'step' not defined
attributes: []
});
chalk.validateLibrary(lib3);
// Returns: { ok: false, error: "Document 'tutorial' body policy references undefined element 'step'" }
Usage in Testing:
Library validation is particularly useful in testing scenarios:
describe('Library Definition', () => {
test('should define a valid library', () => {
const library = chalk.library()
.document({ name: 'article', body: 'all', attributes: [] })
.element({ id: 'note', body: 'literal', attributes: [] });
const result = chalk.validateLibrary(library);
expect(result.ok).toBe(true);
});
test('should catch missing type definitions', () => {
const library = chalk.library()
.document({
name: 'report',
body: 'all',
attributes: [{ id: 'status', type: 'report-status' }]
});
const result = chalk.validateLibrary(library);
expect(result.ok).toBe(false);
expect(result.error).toContain('undefined type');
});
});
Complete Examples
Blog Library
import chalk from '@jackhgns/dotchalk';
// Create library
const blogLib = chalk.library()
.document({
name: 'blog-post',
body: 'all',
attributes: [
{ id: 'title', type: 'string', required: true },
{ id: 'author', type: 'string', required: true },
{ id: 'date', type: 'string', required: true },
{ id: 'status', type: 'status', default: 'draft' },
{ id: 'category', type: 'category' },
{ id: 'featured', type: 'boolean', default: false }
],
config: {
formatting: {
bold: true,
italic: true,
code: true,
latex: false,
links: true
}
}
});
// Define enums
blogLib
.enum({
id: 'status',
values: ['draft', 'published', 'archived']
})
.enum({
id: 'category',
values: ['tech', 'design', 'business']
});
// Define elements
blogLib
.element({
id: 'section',
aliases: ['sec'],
body: ['paragraph', 'h2', 'h3'],
detail: ['paragraph'],
attributes: [
{ id: 'title', type: 'string', required: true },
{ id: 'id', type: 'string' }
]
})
.element({
id: 'callout',
body: 'literal',
attributes: [
{ id: 'type', type: 'string', default: 'info' },
{ id: 'title', type: 'string' }
]
})
.element({
id: 'image',
aliases: ['img'],
body: 'literal',
attributes: [
{ id: 'src', type: 'string', required: true },
{ id: 'alt', type: 'string', required: true },
{ id: 'caption', type: 'string' }
]
});
// Use the library
const result = chalk.compile(`
*(
title: My Blog Post
author: John Doe
date: 2025-12-21
status: published
)
section{
This is an **introduction** with *inline* formatting and \`code\`.
}[
This is a side column detail.
](
title: Introduction
)
callout{
Don't forget to save your work!
}(
type: warning
title: Important
)
img{
A beautiful landscape
}(
src: photo.jpg
alt: A photo
)
`, blogLib, 'blog-post');
Technical Documentation Library
import chalk from '@jackhgns/dotchalk';
// Create library
const docsLib = chalk.library()
.document({
name: 'documentation',
body: 'all',
attributes: [
{ id: 'title', type: 'string', required: true },
{ id: 'version', type: 'string', required: true },
{ id: 'api version', type: 'string' } // Multi-word identifier
]
});
// Custom enum for programming languages
docsLib.enum({
id: 'lang',
values: [
'javascript', 'typescript', 'python', 'java',
'csharp', 'go', 'rust'
]
});
// Code block element
docsLib.element({
id: 'code',
body: 'literal',
attributes: [
{ id: 'language', type: 'lang', default: 'javascript' },
{ id: 'filename', type: 'string' },
{ id: 'highlight lines', type: 'string' }
]
});
// API reference element
docsLib.element({
id: 'api',
body: ['paragraph'],
detail: 'literal',
attributes: [
{ id: 'method', type: 'string', required: true },
{ id: 'endpoint', type: 'string', required: true },
{ id: 'auth required', type: 'boolean', default: false }
]
});
Interactive Content Library with Custom Parser
import chalk from '@jackhgns/dotchalk';
const interactiveLib = chalk.library()
.document({
name: 'interactive',
body: 'all',
attributes: [
{ id: 'title', type: 'string', required: true },
{ id: 'interactive', type: 'boolean', default: true }
]
});
// Link element with custom parser for markdown syntax
interactiveLib.element({
id: 'link',
aliases: ['lnk'],
body: 'literal',
attributes: [
{ id: 'url', type: 'string', required: true },
{ id: 'target', type: 'string' }
],
parserPriority: 50, // Medium priority - try before paragraphs
parser: (input) => {
// Custom parser for [text](url) syntax
const match = input.match(/^\[(.+?)\]\((.+?)\)$/);
if (!match) {
return { ok: false, error: 'Invalid link syntax. Expected [text](url)' };
}
// Return just the link text as the body content
return {
ok: true,
data: match[1]
};
}
});
// Quiz element with body and detail
interactiveLib.element({
id: 'quiz',
body: ['paragraph'], // Body: question
detail: 'literal', // Detail: answer
attributes: [
{ id: 'type', type: 'string', default: 'multiple-choice' },
{ id: 'points', type: 'string', default: '1' }
]
});
// Poll element
interactiveLib.element({
id: 'poll',
body: ['paragraph'],
attributes: [
{ id: 'question', type: 'string', required: true },
{ id: 'options', type: 'string', required: true },
{ id: 'multi select', type: 'boolean', default: false }
]
});
Multi-Document Library with Scoping
import chalk from '@jackhgns/dotchalk';
const academicLib = chalk.library()
// Define multiple document classes
.document({
name: 'tutorial',
body: 'all',
attributes: [
{ id: 'title', type: 'string', required: true },
{ id: 'difficulty', type: 'difficulty' },
{ id: 'estimated time', type: 'string' }
]
})
.document({
name: 'mathPaper',
body: 'all',
attributes: [
{ id: 'title', type: 'string', required: true },
{ id: 'authors', type: 'string', required: true },
{ id: 'abstract', type: 'string' }
],
config: {
formatting: { latex: true }
}
})
.document({
name: 'article',
body: 'all',
attributes: [
{ id: 'title', type: 'string', required: true },
{ id: 'author', type: 'string' }
]
})
// Scoped enum (tutorial only)
.enum({
id: 'difficulty',
values: ['beginner', 'intermediate', 'advanced'],
documentClasses: ['tutorial']
})
// Scoped elements
.element({
id: 'step',
body: ['paragraph', 'code'],
attributes: [
{ id: 'number', type: 'string' }
],
documentClasses: ['tutorial'] // Only in tutorials
})
.element({
id: 'theorem',
body: ['paragraph'],
attributes: [
{ id: 'name', type: 'string', required: true },
{ id: 'number', type: 'string' }
],
documentClasses: ['mathPaper'] // Only in math papers
})
.element({
id: 'proof',
body: ['paragraph'],
attributes: [],
documentClasses: ['mathPaper'] // Only in math papers
})
// Global elements (all document classes)
.element({
id: 'note',
body: 'literal',
attributes: [
{ id: 'type', type: 'string', default: 'info' }
]
})
.element({
id: 'section',
aliases: ['sec'],
body: ['paragraph', 'h2', 'h3'],
attributes: [
{ id: 'title', type: 'string', required: true }
]
});
// Compile with different document classes
const tutorial = chalk.compile(tutorialSource, academicLib, 'tutorial');
const paper = chalk.compile(paperSource, academicLib, 'mathPaper');
const article = chalk.compile(articleSource, academicLib, 'article');
Best Practices
1. Use Meaningful Identifiers
// Good
lib.element({
id: 'user-profile',
attributes: [
{ id: 'display-name', type: 'string' }
]
});
// Avoid
lib.element({
id: 'e1',
attributes: [
{ id: 'a', type: 'string' }
]
});
2. Use Enums for Fixed Sets
// Good - constrains values
lib.enum({
id: 'priority',
values: ['low', 'medium', 'high']
});
lib.element({
id: 'task',
attributes: [
{ id: 'priority', type: 'priority', default: 'medium' }
]
});
// Avoid - allows any string
lib.element({
id: 'task',
attributes: [
{ id: 'priority', type: 'string' }
]
});
3. Use Aliases for Common Shortcuts
lib.element({
id: 'section',
aliases: ['sec', 's'], // Allow 'sec{...}' and 's{...}'
body: 'all',
attributes: []
});
4. Multi-Word Identifiers
Attribute identifiers can contain spaces for readability:
lib.element({
id: 'article',
attributes: [
{ id: 'published status', type: 'string' },
{ id: 'is featured', type: 'boolean', default: false },
{ id: 'content type', type: 'string', default: 'article' }
]
});
5. Method Chaining
Take advantage of method chaining for cleaner code:
const lib = chalk.library()
.document({ name: 'article', body: 'all', attributes: [...] })
.element({ id: 'section', body: ['paragraph'], attributes: [...] })
.element({ id: 'callout', body: 'literal', attributes: [...] })
.enum({ id: 'status', values: ['draft', 'published'] })
.type({ id: 'email', parse: emailParser });
6. Scope Wisely
Use document class scoping to create specialised elements:
// Global - use for common elements
lib.element({
id: 'paragraph',
body: 'literal',
attributes: []
});
// Scoped - use for specialised elements
lib.element({
id: 'theorem',
body: ['paragraph'],
attributes: [{ id: 'name', type: 'string' }],
documentClasses: ['mathPaper']
});
Type Definitions Reference
For complete TypeScript type definitions of all interfaces and types used in the library API, see the Type Reference page.
Key Types:
- Library - Library builder interface
- ElementSpec - Element specification object
- DocumentSpec - Document specification object
- TypeSpec - Custom type specification
- EnumSpec - Enum specification
- AttributeSpec - Attribute specification
- ContentPolicy - Content policy type
- Result - Result type for operations that can fail