An XSD-driven schema system for dynamic validation, XML generation and CSV-to-schema mapping.
Parse XSD files into queryable registries, then use those registries to validate data, map CSV rows to nested schema objects and generate valid XML.
schema-forge ships raw TypeScript with no build step. It requires a runtime or bundler that consumes .ts directly: Bun, Vite or SvelteKit. It will not work under plain Node or tsc-compiled projects.
bun add @jasonwarrenuk/schema-forgeEverything is exported from the package root. Deep imports into src/ are not a supported interface.
import{buildSchemaRegistry}from"@jasonwarrenuk/schema-forge";constxsdContent=awaitBun.file("schema.xsd").text();constregistry=buildSchemaRegistry(xsdContent);registry.elementsByPath.get("Message.Learner.ULN");// → SchemaElement { name: 'ULN', baseType: 'string', constraints: { ... }}buildSchemaRegistry is synchronous. It throws if the XSD has no root element, or more than one.
import{validateValue}from"@jasonwarrenuk/schema-forge";constelement=registry.elementsByPath.get("Message.Learner.ULN");constissues=validateValue("1234567890",element,{rowIndex: 0,sourceField: "ULN"});// → [] when valid, otherwise SchemaValidationIssue[]import{parseCSVContent,validateRows}from"@jasonwarrenuk/schema-forge";const{ headers, rows }=parseCSVContent(csvString);// Note: validateRows takes a MappingConfig, not a bare arrayconstresult=validateRows(rows,headers,registry,{ mappings });import{mapCsvToSchema}from"@jasonwarrenuk/schema-forge";constmappings=[{csvColumn: "Student ID",xsdPath: "Message.Learner.LearnRefNumber"},{csvColumn: "Postcode",xsdPath: "Message.Learner.Postcode",transform: "postcode"},];constnested=mapCsvToSchema(csvRow,mappings,registry);// → { Message: { Learner: { LearnRefNumber: "ABC123", Postcode: "E1 6AN" }}}Column matching is case-insensitive. mapCsvToSchema takes the mappings array directly, unlike validateRows.
import{generateFromSchema}from"@jasonwarrenuk/schema-forge";const{ xml, warnings }=generateFromSchema(data,registry);Generation always produces output. Missing required elements and type mismatches are reported as warnings, not thrown.
| Feature | Details |
|---|---|
| Base types | string, int, integer, long, decimal, date, dateTime, boolean |
| Constraints | pattern, length, minLength, maxLength, minInclusive, maxInclusive, minExclusive, maxExclusive, totalDigits, fractionDigits, enumeration |
| Complex types | xs:sequence with nested elements |
| Cardinality | minOccurs, maxOccurs (including unbounded) |
| Named types | Simple type reuse and inheritance |
| Namespaces | targetNamespace extraction and preservation |
The parser expects a specific XSD shape and throws when it is not met:
- Elements must use the literal
xs:prefix throughout - Exactly one top-level element
- A
targetNamespacemust be present
The following are not supported. buildSchemaRegistry throws when it meets one, naming the construct and the element path, rather than building a registry with the content silently missing:
xs:choice,xs:all,xs:group,xs:anyxs:complexContent/xs:extension,xs:simpleContentxs:attribute,xs:attributeGroupxs:element refxs:include,xs:import(single-document schemas only)- Named
xs:complexTypereferences (inlinexs:complexTypeonly)
Only the first xs:pattern on a restriction is honoured. A pattern that cannot be compiled as a JavaScript regex produces a warning-severity issue rather than being skipped, since XSD's regex grammar is not a subset of JavaScript's.
21 named transforms, plus parameterised constant(value) and normalizeAddress(n).
Type conversions:stringToInt, stringToIntOptional, stringToIntStrict, stringToFloat, stringToFloatStrict, stringToBoolean, boolToInt
String:trim, uppercase, lowercase, uppercaseTrim, uppercaseNoSpaces, postcode, removeSpaces, digitsOnly, normalizeAddress
Date/time:passthroughDate, passthroughDateTime
Conditional:nullIfEmpty
Prefer the Strict variants for numeric conversion. stringToInt and stringToFloat are parseInt(v, 10) || 0, so a genuine "0" and unparseable input both yield 0; the strict variants return undefined instead.
isoDate and isoDateTime are deprecated aliases for the passthrough transforms. Neither ever parsed or reformatted anything, and the names implied otherwise.
Two transforms carry assumptions from their original use: normalizeAddress defaults to truncating at 50 characters (pass normalizeAddress(n) for your own schema's limit), and digitsOnly strips a leading +, which loses an international dialling prefix.
schema-forge was extracted from foundersandcoders/iris, an ILR toolkit, where the engine originally lived. This repository is now the canonical home; iris consumes it as a dependency.
- fast-xml-parser — XSD/XML parsing
- papaparse — CSV parsing
MIT