# Changelog

All notable changes to **xml-xsd-engine** are documented here.

The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

---

## [Unreleased]

---

## [1.7.1] — Patch (packaging fix)

### Fixed

- **`dist/esm/utils/sha256.js` missing from published package** — The `files` array in `package.json` listed only four individual `utils/` files by name (`ParseBudget.js`, `StringReader.js`, `benchmark.js`, `xmlUtils.js`) and omitted `sha256.js`, `XmlDiff.js`, `XmlCanonical.js`, and `SchemaInference.js`. Consumers using the ESM entry-point received `Cannot find module '.../utils/sha256.js'` at runtime. Fixed by replacing the four per-file entries with `dist/cjs/utils/`, `dist/esm/utils/`, and `dist/types/utils/` glob-directory entries so all current and future utils files are included. ([reported by @peloquina](https://github.com/hephesthesis/xml-xsd-engine/issues))

---

### v1.7 (on branch `v1_7_0`, targeting v1.7.0)

### Added

**G27 — Full DFA construction per compositor (SchemaCompilerLite)**
- `CompiledSchema.compiledDfas: ReadonlyMap<string, DfaModel>` — DFA pre-compiled once per complex type during `compileSchema()`; zero rebuild cost per validation call
- `DfaModel.particleIndex: Map<string, NormalisedParticle>` — O(1) element-name lookup; eliminates O(n) `Array.find` in `_runAll` and `_runChoice`

**G38 — Incremental / subtree revalidation**
- `revalidateSubtree(elem, schema, opts?)` — validates only the given `XmlElement` subtree; intended for editor "lint on edit" use; `psvi: true` option is fully propagated and produces populated PSVI map (T-05 fix)

**G28b — Streaming-aware xs:keyref**
- `StreamingKeyrefTracker` (public class) — accumulates xs:key/unique/keyref tuples in SAX pass; validates referential integrity after parse; `_constraintsByElement` index for O(1) per-element dispatch
- `StreamingKeyrefTrackerOptions.maxTuples` (default 50 000) — S-03 DoS guard; emits `KEYREF_TUPLE_LIMIT` warning when exceeded
- `StreamingValidationOptions.maxKeyTuples` — propagated to internal `StreamingKeyrefTracker`
- Integrated automatically into `validateStreaming()` / `validateStream()`

**Streaming issue async generator (P2)**
- `validateStreamingGenerator(xml, compiled, opts?)` — `AsyncGenerator<StreamingIssue, StreamingValidationResult>` yields issues as produced

**SchemaCache improvements**
- `SchemaCache.get(key): SchemaModel | undefined` — direct cache retrieval
- `SchemaCache.has(key): boolean` — presence check
- `SchemaCacheOptions.statFast?: boolean` — `fs.stat` mtime+size fast path for `getOrLoad`; skips full file read on cache hit

**Shared IValidator interface (A-01 partial)**
- `IValidator` exported from `StreamingValidator.ts` — `StreamingValidator implements IValidator`

### Fixed

- **`PsviMap` now `Map`-based** — Changed from `WeakMap` to `Map<XmlElement, PsviAnnotation>` giving `.size`, `.forEach()`, `.entries()`. Replace any `new WeakMap()` typed as `PsviMap` with `new Map()`.
- **`StreamingValidator._validateSimpleType`** — User-defined `SimpleTypeDefinition` now validated via `tv.validateDef()`; was silently skipped before
- **`StreamingValidator._validateChildOrder`** — Uses pre-compiled `compiledDfas`; no longer calls `buildDfa()` on every `endElement`
- **`StreamingValidator` `xsi:type` (T-02/S-01)** — Full fix: namespace-prefix resolved via `nsDeclarations` from SAX event; simple-type fallback (no spurious warning); `_isDerivedFrom()` type-safety check rejects unrelated types with `VALID_XSI_TYPE_UNSAFE` error
- **`StreamingKeyrefTracker` selector parsing (T-01)** — `_extractSelectorNames()` handles `.//a/b`, `child::item`, `item|other`, `item[@type='foo']`; union selectors register under all branch names
- **`DfaEngine._buildAllDfa` xs:all enforcement (T-03)** — `enforcedNorm` clamps per-particle `maxOccurs` to `min(maxOccurs, 1)` per XSD 1.0 spec; duplicate elements in `xs:all` now correctly reported as `VALID_TOO_MANY`
- **`ValidationEngine._resolveElementDecl` (T-06)** — Added fallback to `{targetNamespace}localName` when `elementFormDefault="qualified"` and SAX provides no namespace URI; prevents false "unknown element" errors on qualified-form schemas
- **`StreamingValidator._resolveDecl` (T-06)** — Same fallback as ValidationEngine for qualified-form schemas
- **`validateFragment` dynamic `require()`** — Replaced with top-level static `import { parseXml as _parseXml }` (tree-shaking safe; no CJS module-cache lookup)
- **`XsdParser.occ()` NaN guard** — Both `parseInt` results now guarded with `isNaN`; malformed `minOccurs`/`maxOccurs` no longer propagate `NaN` into DFA
- **`TypeValidator._checkPrimitive`** — Added missing types: `xs:normalizedString`, `xs:token`, `xs:Name`, `xs:ID`/`xs:IDREF`/`xs:ENTITY`, `xs:NMTOKENS`/`xs:IDREFS`/`xs:ENTITIES`, `xs:anySimpleType`, `xs:anyAtomicType`; fixed `xs:decimal` to accept `.5` leading-dot form per XSD spec; `xs:date` now validates calendar (rejects `2024-02-30`)
- **`TypeValidator._wsCache` (T-04)** — Moved from module-level to per-instance `private readonly _wsCache`; constructing a new `TypeValidator` with a reloaded `SchemaModel` now gets a fresh cache, preventing stale whitespace normalisation modes in watch mode
- **`SchemaCompilerLite._mergeExtensionChain` (P-04)** — Added `_mergeCache: Map<string, ComplexTypeDefinition>` per instance; shared base types in diamond-inheritance hierarchies are computed once and reused in O(1) for subsequent derived types
- **`flattenGroupParticles` (T-09)** — New exported utility function in `SchemaCompilerLite`; inlines xs:group ref particles (occurrence 1..1 → direct inline; 0..1 → inner elements made optional; other → keep wrapper); applied in `_compileDfas` for named types and in `StreamingValidator._validateChildOrder` for anonymous/inline types; eliminates false `__group_sequence` unexpected/missing errors
- **`AssertionEvaluator._getValue` + `_evalExpr` (T-08)** — `_getValue` now handles `child/text()` (strips suffix, delegates to new `_getChildText()`), multi-segment paths via `split('/')` chain, and `string-length(child)` on element text content; `_evalExpr` gains `child/text()` existence and multi-level child path existence checks
- **`CLI --streaming` (A-07)** — `CliArgs.streaming: boolean` added; `--streaming` switch wired in `parseArgs()`; `validateOne()` branches to SAX-based `validateStreaming(xmlSrc, compiled)` when flag is set; `_streamingToValidationResult()` adapts `StreamingValidationResult` → `ValidationResult` for the shared output pipeline; help text updated with `--streaming` description
- **`XmlDiff` fingerprint cache (P-01)** — `_fingerprintCache` moved from module-level `WeakMap` to per-call `WeakMap` created inside `diffXml()` and threaded through `_diffElements()` as a parameter; eliminates cross-call fingerprint pollution risk
- **`validateOne` well-formed error handling** — XML parse errors in `--well-formed` mode now returned as `ValidationResult` with a structured error entry instead of calling `die()` / `process.exit(1)`; allows callers to handle errors programmatically

### Tests

- **2460** total tests (up from 2276 in v1.7.0)
- New: `src/tests/unit/v1.7-post-release-fixes.test.ts` (26 tests — A-07, P-01, P-04, T-04, T-08, T-09)
- New: `src/tests/unit/v1.7-issue-fixes.test.ts`, `v1.7-regression.test.ts`, `coverage-boost-v2/v3/v4.test.ts`, `coverage-final.test.ts`, `coverage-gap-fill.test.ts`, `format-cli.test.ts`, `index-barrel.test.ts`, `np-issue-fixes.test.ts`
- Updated: `src/tests/unit/cli.test.ts` — adapted well-formed error test to new structured-error return behaviour
- All 63 test suites pass; build clean (CJS + ESM + types)

---

## [1.7.0] — 2026-04-20

### Fixed (Critical)

- **`ValidationResult._errorCount` never incremented** — `result.valid` always returned `true`
  even when errors were present. Fixed by incrementing `_errorCount` in `addError()` and
  `_warningCount` in `addWarning()`. `merge()` now propagates both counters.
- **`SchemaCache._setWithMeta` premature eviction** — different content under the same logical
  key was collapsed to size=1. Removed the pre-eviction loop; LRU eviction now only triggers
  when `maxSize` is reached.
- **`XmlCanonical._computeInclusiveNs` wrong diff algorithm** — inclusive C14N was rendering
  only newly-added namespace bindings instead of all in-scope bindings, violating W3C Canonical
  XML 1.0 §2.4. Fixed with delta-based save/restore.
- **`XmlDiff._diffElements` false fingerprint equality** — fingerprint now recursively encodes
  child subtrees so structurally-different-but-similar trees are correctly distinguished.
  Added same-reference fast-path (`e1 === e2`).
- **`XsdParser._parseSubSource` weak cache key** — replaced first-64-chars heuristic with
  `sha256Short()` for collision-resistant sub-schema deduplication.
- **`AssertionEvaluator` non-deterministic ordering** — `xs:assert` expressions are now sorted
  by `test` string before evaluation for stable error messages across runs.
- **`ValidationPipeline` not forwarding `psvi` option** — `psvi` flag is now forwarded to
  `ValidationEngine`.

### Added

#### G29 — PSVI: Post-Schema-Validation Infoset (`src/validator/Psvi.ts`)

- **`PsviAnnotation`** — per-element type annotation produced during validation:
  - `xsdType: string` — resolved XSD type name (e.g. `xs:integer`, `{ns}MyType`)
  - `normalizedValue: string | null` — whitespace-normalised text; `null` for complex types
  - `validity: 'valid' | 'invalid' | 'notKnown'`
  - `memberType?: string` — xs:union member type when applicable
- **`PsviMap`** — `WeakMap<XmlElement, PsviAnnotation>`; does not prevent GC of elements.
- **`ValidationOptions.psvi?: boolean`** — opt-in PSVI collection (default `false`).
- **`ValidationResult.psvi?: PsviMap`** — populated after `validate(doc, schema, { psvi: true })`.
- **`makePsviAnnotation()`** — factory with 256-entry object pool for null-value annotations (−60% heap allocation).
- **`extractPsvi(psvi, root, options?)`** — converts WeakMap PSVI to a serialisable `Map<path, PsviAnnotation>` by walking the document tree. Supports `paths` filter option.

```ts
import { validate, extractPsvi } from 'xml-xsd-engine';
const result = validate(doc, schema, { psvi: true });
const annotated = extractPsvi(result.psvi!, doc);
for (const [path, ann] of annotated) {
  console.log(path, ann.xsdType, ann.validity, ann.normalizedValue);
}
```

#### G30 — Canonical XML 1.0 (`src/utils/XmlCanonical.ts`)

- **`canonicalize(node, options?)`** — W3C Canonical XML 1.0 serializer.
  - Modes: `'inclusive'` (default) and `'exclusive'` (with optional `inclusiveNamespaces`)
  - Option `withComments: boolean` (default `false`) — preserve or strip comments
  - Works on `XmlDocument` or any `XmlElement` subtree
- **`clearCanonicalCache()`** — invalidate the WeakMap subtree memoization cache after DOM mutation.
- **`CanonicalizeOptions`** / **`CanonicalizeMode`** — exported option types.
- Serialization rules: no XML declaration, empty elements expanded, attributes sorted by namespace URI then local name, namespace declarations sorted by prefix, CDATA expanded to character data, characters encoded per spec.
- **Performance:** delta-based namespace save/restore O(decls) per element; sorted attr comparator hoisted to module-level; WeakMap subtree memoization.

#### G21 — `xs:redefine` Support

- `xs:redefine` now loads the referenced schema (via same loader as `xs:include`) and merges it into the model, then applies each child component as an override.
- Required for UBL, ebXML, and other enterprise schema sets.
- Transparent to callers — no API changes.

#### G39 — Mixed Content Model Fix

- `mixed="true"` — interleaved text nodes between child elements are now allowed without producing false-positive validation errors.
- `contentType: 'elementOnly'` — text content produces a **warning** (not error).
- Serializer no longer adds indentation inside mixed-content elements.

#### SchemaMerger (`src/xsd/SchemaMerger.ts`)

- **`SchemaMerger`** — standalone class centralizing `xs:import`/`xs:include`/`xs:redefine` merge logic with SHA-256 content-hash caching.
  - `handleImport(loc, ns, target, subParser)` — load + merge `xs:import`
  - `handleRedefine(loc, ns, target, subParser, applyOverrides)` — load + merge `xs:redefine`
  - `markImported(key)` / `hasImported(key)` — circular-import guard
  - `cache` — shared `Map<string, SchemaModel>` (can be passed across parser instances)
- Exported from `xml-xsd-engine` for advanced consumers.

#### Streaming Code Generation

- **`generateTypeScriptStream(schema, options?)`** — `AsyncGenerator<string>` that yields
  top-level declarations in ≈4 KB chunks; avoids peak memory on large schemas.
- **`generateJsonSchemaStream(schema, options?)`** — `AsyncGenerator<string>` yielding
  JSON output in chunks.

#### XPath Cache Control

- **`configureXPathCache({ coldMax, hotMax })`** — tune two-tier LRU sizes at runtime.
  - Cold tier (default 512): newly compiled expressions
  - Hot tier (default 128): frequently evaluated expressions (auto-promoted)
- **`xpathCacheStats()`** — returns `{ coldSize, hotSize, hits, misses }`.
- **`xpathCacheSize()`** — total compiled-expression count.

### Improved (P0–P2 Performance)

| Fix | Impact |
|-----|--------|
| Two-tier XPath LRU (cold 512 + hot 128) | +24% first-expression, +4× hot-tier |
| `ValidationEngine` `_mergeCache` + `_substCache` memoization | +5% validate throughput |
| `XmlDiff` WeakMap fingerprint cache + reference fast-path | +40% diff throughput |
| `XmlCanonical` delta namespace save/restore; hoisted comparator | +40% C14N throughput |
| `PSVI` 256-entry object pool | −60% heap allocation |
| `IdentityConstraintEngine` pre-built element-name Set | O(1) skip for constraint-free elements |
| `SchemaCache` `maxVersionsPerKey` (default 2) | prevents OOM in long-running servers |
| `NamespaceEngine` linked scope chain; flat-cache for empty scopes | O(decls) per push |
| `SchemaMerger` SHA-256 content-hash cache | prevents redundant XSD re-parsing |
| CLI watch debounce (`XML_VALIDATE_DEBOUNCE_MS`) | eliminates duplicate re-validates |

### New Exports

| Symbol | Description |
|--------|-------------|
| `extractPsvi` | Convert WeakMap PSVI to serialisable `Map<path, PsviAnnotation>` |
| `makePsviAnnotation` | PSVI annotation factory (pool-backed) |
| `PsviAnnotation` | PSVI annotation interface |
| `PsviMap` | `WeakMap<XmlElement, PsviAnnotation>` type alias |
| `ExtractPsviOptions` | Options for `extractPsvi` |
| `SchemaMerger` | Standalone xs:import/include/redefine merge class |
| `configureXPathCache` | Tune two-tier XPath LRU sizes at runtime |
| `xpathCacheStats` | Inspect XPath cache hit/miss counts |
| `xpathCacheSize` | Current compiled-expression count |
| `canonicalize` | W3C Canonical XML 1.0 serializer |
| `clearCanonicalCache` | Invalidate C14N subtree memo cache |
| `CanonicalizeOptions` | Options for `canonicalize` |
| `CanonicalizeMode` | `'inclusive' \| 'exclusive'` |
| `generateTypeScriptStream` | Streaming TypeScript codegen |
| `generateJsonSchemaStream` | Streaming JSON Schema codegen |

### Tests

- **2276 total tests** (up from 1333 in v1.6.0) — 59 test suites, 0 failures
- 220 new tests in three coverage-boost files targeting previously uncovered paths:
  - `SchemaMerger` (0% → fully covered): handleImport, handleRedefine, markImported, cache reuse
  - `Psvi.ts` (33% → >80%): pool, extractPsvi path filtering, multi-child indexing
  - `ValidationPipeline` branches: profile, lax, PSVI forwarding, precompileSchema caching
  - `SchemaCompilerLite` branches: unknown base, depth > 20 guard, restriction passthrough
  - `XmlCanonical` branches: inclusive/exclusive, withComments, attribute ordering, CDATA, subtree
  - `SchemaCache` branches: TTL expiry, invalidateByContent same/changed content, LRU eviction
  - `XsdToJsonSchema`/`XsdToTypeScript` branches: useDefinitions, exportAll, date/choice/union
  - `SaxParser` branches: all event types via parseSax(), events() iterator
  - `IdentityConstraintEngine`: xs:key, xs:unique, duplicate-key detection
- **Coverage:** 87.11% stmts · 76.28% branch · 77.42% funcs · 89.33% lines (up from 85.51/74.66/76.27/87.71)

---

## [1.6.0] — 2026-04-15

### Added

#### G2 — Source-Mapped Error Locations (`src/validator/ValidationEngine.ts`)

- Every `ValidationIssue` now carries `line` and `col` fields pointing to the
  offending element's opening tag in the source document.
- Covers: abstract element, xsi:nil mismatch, wrong type, missing/bad/unknown attributes,
  unexpected/unknown elements, xs:assert failures.
- No API changes — `ValidationIssue.line` and `.col` were already defined; now consistently populated.

#### G16 — XML Diff Utility (`src/utils/XmlDiff.ts`)

- **`diffXml(doc1, doc2, options?)`** — structural diff between two `XmlDocument`s. Returns `XmlChange[]`.
- **`XmlChange`** — `{ type, path, from?, to? }` where `type` is `'added'`, `'removed'`, `'modified'`, or `'type-change'`.
- **`XmlDiffOptions`** — `ignoreWhitespace` (true), `ignoreComments` (true), `ignorePI` (true).
- Paths: XPath-like `/root/book[2]/title` with 1-based sibling indices.

#### G17 — Schema Inference (`src/utils/SchemaInference.ts`)

- **`inferSchema(docs, options?)`** — observe `XmlDocument`s and produce a draft `SchemaModel` + XSD string.
- **`InferredSchema`** — `{ model: SchemaModel, toXsdString(): string }`.
- Type inference cascade: `xs:boolean` → `xs:integer` → `xs:decimal` → `xs:dateTime` → `xs:date` → `xs:string`.
- Occurrence inference from min/max observed repetitions across all sample documents.
- **`InferSchemaOptions`** — `targetNamespace`, `inferSimpleTypes` (true), `inferRequired` (true).

#### G19 — Encoding Declaration Support (`src/parser/XmlParser.ts`)

- **`ParseOptions.encoding`** — when set, stored as `XmlDocument.encoding`, overriding the XML declaration value.
  Useful when the caller has pre-decoded input via `TextDecoder` but wants the encoding name preserved.

#### G45 — Fragment / Subtree Validation (`src/validator/ValidationEngine.ts`)

- **`validateFragment(xmlFragment, schema, options?)`** — parse + validate a raw XML string fragment.
- **`validateSubtree(elem, schema, options?)`** — validate an `XmlElement` subtree directly.
- **`ValidationOptions.rootType`** — new optional field: validate as if declared with the named type.

### Tests

- **51 new tests** in `src/tests/unit/v1.6-features.test.ts`
- **Total: 1333 tests, 46 suites, 0 failures**

### Fixed

- `SaxInstrumentation`: handlers now correctly unwrap `SaxEvent` wrapper to access inner payload.
- `IdentityConstraintEngine`: `.//foo` selector now correctly traverses all descendants.
- `SaxInstrumentation`: namespace declarations correctly tracked via `ev.nsDeclarations`.

---

## [1.5.0] — 2026-04-15

### Added

#### G1-prep — SAX Instrumentation (`src/parser/SaxInstrumentation.ts`)

- **`SaxValidationHint`** — structured event carrying `kind`, `qualifiedName`, `localName`,
  `namespaceURI`, `depth`, `namespaceStack`, `attributes`, `line`, `col`, `path`.
- **`SaxEventConsumer`** — streaming consumer interface: `onHint(hint)`, `onEnd(hadError)`, `onError(err)`.
- **`instrumentSax(parser, consumer)`** / **`createInstrumentedSax(xml, consumer)`** — attach instrumentation.
- **`collectSaxHints(xml)`** — collect all hints into an array (for testing and analysis).
- XPath-like path tracking: `/root/book[2]/title`.

#### G42 — Schema Preflight (`src/validator/SchemaPreflight.ts`)

- **`validateSchema(xsdSource)`** — parse XSD and run all preflight checks. Returns `SchemaIssue[]`.
- **`checkSchema(model)`** — run preflight on an already-compiled `SchemaModel`.
- **`SchemaIssue`** — `{ severity: SchemaSeverity, code: XmlErrorCode, message, component? }`.
- Checks: undefined type refs, circular extension chains, final-type derivation, conflicting declarations, broken xs:keyref links, undefined ref particles.

#### G28b — Identity Constraint Engine (`src/validator/IdentityConstraintEngine.ts`)

- **`IdentityConstraintEngine`** — DOM-based evaluation of `xs:key` and `xs:unique`.
  - `evaluate(doc, result)` — walk document and evaluate all registered constraints.
  - XPath selector support: `.`, `./child`, `.//descendant`, `@attr`.
  - Composite multi-field keys.
  - `xs:keyref` full evaluation added (deferred from initial release).

#### G24 — `xs:assert` Evaluator (`src/validator/AssertionEvaluator.ts`)

- **`AssertionEvaluator`** — evaluates XPath 1.0 test expressions against `XmlElement` nodes.
- Supports: `@attr`, `@a = 'v'`, `@min <= @max`, `not(@x)`, `string-length(@name) > 0`, `contains()`, `starts-with()`, logical AND/OR.
- **`VALID_ASSERTION_FAILED`** error code added.
- ValidationEngine runs assertions after content-model and attribute validation.

#### G40 — Schema Cache Content-Hash Keying (`src/cache/SchemaCache.ts`, `src/utils/sha256.ts`)

- **`sha256Hex(input)`** — pure-TypeScript SHA-256; works in Node.js, browsers, Deno, Bun.
- **`sha256Short(input, len?)`** — first `len` hex chars (default 16).
- Content-hash keying: `getOrParse(key, xsdSource)` keys entries as `key@sha256short(source)`.
- **`invalidateByContent(filePath, newSource)`** — remove stale entries by hash comparison.
- **`getContentHash(key)`** / **`getDependencies(key)`** — cache inspection helpers.

#### G11 — DTD Internal Subset Entity Definitions

- `<!ENTITY name "value">` in internal DTD subset parsed and used during expansion.
- **`ParseOptions.expandDtdEntities`** (default `true`) — opt-out flag.
- External `SYSTEM`/`PUBLIC` entities silently blocked (XXE prevention).

#### G10 — `xml:id` / `xml:base` Processing

- **`XmlElement.xmlId`** — populated from `xml:id` attribute during parsing.
- **`XmlDocument.xmlIds`** — `Map<string, XmlElement>` for O(1) ID lookup.
- Duplicate `xml:id` values throw `PARSE_DUPLICATE_ATTRIBUTE`.
- **`XmlElement.xmlBase`** — resolved effective base URI.
- **`resolveXmlBase(base, ref)`** — RFC 3986 relative URI resolution. Exported from main entry point.

#### G12 — `xs:list` / `xs:union` Hardening

- `xs:list`: empty string correctly treated as zero-item list; length facets applied to item count; anonymous `itemTypeDef` support.
- `xs:union`: anonymous `memberTypeDefs` support; union validates named then anonymous types.

#### G6 — XPath 2.0 Functions (`src/parser/XPath2Functions.ts`)

22 XPath 2.0 functions exported as pure TypeScript utilities:

**String:** `upperCase`, `lowerCase`, `matchesXPath`, `replaceXPath`, `tokenize`, `normalizeUnicode`, `encodeForUri`, `iriToUri`, `substringBefore`, `substringAfter`

**Sequence/aggregate:** `distinctValues`, `isEmpty`, `exists`, `stringJoin`, `sum`, `avg`, `minValue`, `maxValue`, `insertBefore`, `remove`, `subsequence`, `reverseSeq`, `indexOf`

#### G9 — Browser Entry Point (`src/browser.ts`)

- `xml-xsd-engine/browser` — zero Node.js API dependencies. All pure-JS APIs exported; `fs`/`path` functions replaced with descriptive stubs.

#### G25 — Deno + Bun Entry Points

- `xml-xsd-engine/deno` — re-exports browser + `denoReadXmlFile`, `denoReadXsdFile`, `denoSchemaLoader`.
- `xml-xsd-engine/bun` — re-exports browser + `bunReadXmlFile`, `bunReadXsdFile`, `bunSchemaLoader`.
- `"sideEffects": false` added to `package.json`.
- `deno.json` import-map file added.

#### New `XmlErrorCode` values

| Code | Category | Meaning |
|------|----------|---------|
| `SCHEMA_UNDEFINED_REF` | `schema` | Type, element, or attribute reference not defined |
| `SCHEMA_CONFLICTING_DECL` | `schema` | Same element name declared with different types |
| `SCHEMA_KEYREF_UNRESOLVED` | `schema` | `xs:keyref refer=` points to non-existent key/unique |
| `VALID_ASSERTION_FAILED` | `type` | xs:assert test expression evaluated to false |
| `VALID_DUPLICATE_XML_ID` | `identity` | Duplicate xml:id value in document |

#### New exports

```ts
export { SaxEventConsumer, SaxValidationHint, NamespaceFrame,
         instrumentSax, createInstrumentedSax, collectSaxHints } from 'xml-xsd-engine';
export { validateSchema, checkSchema, SchemaIssue, SchemaSeverity } from 'xml-xsd-engine';
export { IdentityConstraintEngine } from 'xml-xsd-engine';
export { AssertionEvaluator, AssertionDefinition } from 'xml-xsd-engine';
export { sha256Hex, sha256Short } from 'xml-xsd-engine';
export { resolveXmlBase } from 'xml-xsd-engine';
```

#### New API changes

- `StartElementEvent.nsDeclarations: Map<string, string>` — new required field.
- `ElementDeclaration.ref?: string` — new optional field.
- `SimpleTypeDefinition.itemTypeDef?: SimpleTypeDefinition` — xs:list anonymous item type.
- `SimpleTypeDefinition.memberTypeDefs?: SimpleTypeDefinition[]` — xs:union anonymous members.
- `ComplexTypeDefinition.assertions: AssertionDefinition[]` — xs:assert list.

### Tests

- **12 new** `sha256.test.ts` · **10 new** `schema-cache-hash.test.ts` · **8 new** `dtd-entities.test.ts`
- **15 new** `xml-id-base.test.ts` · **12 new** `list-union-edge-cases.test.ts` · **22 new** `xs-assert.test.ts`
- **32 new** `platform-entry-points.test.ts` · **115 new** `xpath2-functions.test.ts`
- **45 new** `v1.5-features.test.ts` — SAX instrumentation, identity constraints, schema preflight
- **Total: 1282 tests, 45 suites, 0 failures**

---

## [1.4.0] — 2026-04-14

### Added

#### G35 — Namespace Engine (`src/namespace/NamespaceEngine.ts`)

- **`NamespaceEngine`** — dedicated namespace resolution engine.
  - Scoped `pushScope(declarations[]) / popScope()` stack.
  - `resolveQName(qname)` → `ResolvedName` with `namespaceURI`, `localName`, `prefix`.
  - `tryResolveQName()` — null-safe variant.
  - Enforces XML Namespaces 1.0 constraints (`xml` / `xmlns` prefix rules).
  - `getOrAllocatePrefix(uri, preferred)` for serializer namespace control.
  - Exported constants: `NS_XML`, `NS_XMLNS`, `NS_XSD`, `NS_XSI`.

#### G34 — Validation Pipeline (`src/pipeline/ValidationPipeline.ts`)

- **`ValidationPipeline`** — formal 7-stage pipeline: `parse → namespace → schema-compile → structure-validate → type-validate → identity-check → post-process`.
- `run(xmlString)` / `runDocument(doc)` / `precompileSchema()`.
- **`runPipeline(xmlSource, schema, options?)`** — convenience function.
- `PipelineResult` — `{ validation, stages[], document, compiled, success, totalMs }`.
- Each `StageResult` — `{ stage, success, durationMs, issues[] }`.

#### G27-lite — Schema Compiler (`src/pipeline/SchemaCompilerLite.ts`)

- **`compileSchema(model)`** — compiles `SchemaModel` into `CompiledSchema`.
  - Resolves all named type refs, normalizes occurrences, flattens extensions, detects circular types.
  - Builds namespace-qualified + bare-name element lookup maps.
- **`CompiledSchema`** — `{ elements, complexTypes, simpleTypes, identityConstraints, abstractElements, substitutionGroups, warnings, compiledAt }`.

#### G22 — Compiled / Cached XPath (`src/parser/XPathEngine.ts`)

- **`compileXPath(expression)`** → `CompiledXPath` with `.evaluate()`, `.first()`, `.count()`, `.string()`.
- Internally cached (LRU, 512 entries). `xpath()` uses cache automatically.
- **`clearXPathCache()`** / **`xpathCacheSize()`** — cache management.

#### G37 — Structured Error Classification

- **`ValidationErrorCategory`** — `well-formedness`, `namespace`, `schema`, `structure`, `type`, `identity`, `security`, `io`, `pipeline`.
- **`ERROR_CATEGORY`** map — every `XmlErrorCode` → category. Stable across minor/patch.
- `XmlError.category` / `XmlError.stage`.
- New codes: `VALID_ABSTRACT_ELEMENT`, `VALID_IDENTITY_CONSTRAINT`, `VALID_UNKNOWN_ELEMENT`, `VALID_UNKNOWN_ATTRIBUTE`, `PIPE_STAGE_FAILED`, `PIPE_SCHEMA_COMPILE`.
- **Error code stability guarantee**: codes stable across minor/patch; removal/rename = major bump.

#### G41 — Strict / Lax Validation Mode

- `ValidationOptions.mode: 'strict' | 'lax'` (default `'strict'`).
  - `strict`: unknown elements/attributes are hard errors.
  - `lax`: unknown elements/attributes produce warnings.

#### G43 — Error Recovery Mode

- `ValidationOptions.recover: boolean` (default `false`) — continue past structural errors.

#### G44 — Profiling Hooks

- `ValidationOptions.profile: boolean` + `ValidationOptions.onProfile: (event: ProfileEvent) => void`.
- `ProfileEvent` — `{ stage, path, durationMs }`.
- `ValidationEngine.lastProfileEvents` — read after `validate()` call.

#### G18 — `xml-format` CLI Command

- New `xml-format` binary.
- Options: `--indent <n>`, `--no-declaration`, `--no-self-close`, `--sort-attributes`, `--output <file>`, `--check`, `--quiet`, `--stdin`.
- `--check` mode: exits 0 if already formatted, 1 if changes needed.

#### G20/G23 — Serializer Improvements

- `SerializerOptions.sortAttributes` — sort attributes alphabetically.
- `SerializerOptions.preferredPrefixes` — map URI → preferred prefix for namespace rewriting.
- `xml:space="preserve"` now propagates correctly to all descendants.

#### ValidationResult enhancements

- `ValidationIssue` adds: `category`, `code`, `stage`, `offset`, `endLine`, `endCol`.
- `ValidationResult.byCategory(category)` / `.summary` / `.addInfo()` / `.infos`.
- `Severity` expanded: `'error' | 'warning' | 'info'`.

### Changed

- `ValidationOptions` — extended with `mode`, `recover`, `profile`, `onProfile`.
- `ValidationResult.summary` shape: `{ errorCount, warningCount, infoCount, byCategory }`.

### Fixed

- `XPathEngine`: `normalize-space()` and `string-length()` predicates added.
- `XmlSerializer`: `xml:space="preserve"` correctly inherited by all descendants.

---

## [1.3.0] — 2026-03-29

### Added

#### Code Generation (`src/codegen/`)

- **`generateTypeScript(schema, options?)`** — TypeScript `interface`/`type` declarations from `SchemaModel`.
  - XSD types → TS primitives; enumerations → literal union types; `xs:list` → `T[]`; `xs:union` → TS union.
  - Options: `typePrefix`, `typeSuffix`, `useTypeAlias`, `jsdoc`, `exportAll`, `indent`, `typeMap`.
- **`generateJsonSchema(schema, options?)`** — JSON Schema Draft 7 document.
  - `xs:choice` → `oneOf`; `xs:union` → `anyOf`; `xs:list` → array with `items`.
  - All facets mapped: `pattern`, `minLength`/`maxLength`, `minimum`/`maximum`, `enum`.
  - Options: `draft`, `id`, `title`, `useDefinitions`, `allowAdditional`, `typeMap`.
- **`XsdToTypeScript`** / **`XsdToJsonSchema`** classes for low-level control.

#### `SchemaModel.toJSON()`

- Serializes compiled schema to a plain JSON-serialisable object. Includes elements, types, groups, substitutionGroups, targetNamespace.

#### Async Schema Loader

- **`parseXsdAsync(source, loader?)`** — async variant accepting `AsyncSchemaLoader` (`Promise<string | null>`).
- Enables `xs:import`/`xs:include` via `fetch()` or any async I/O.

#### CLI Enhancements

- **`--watch` flag** — re-validates on file save. 150ms debounce. Watches XML and XSD.
- **`--code-frame` flag** — shows source context with `^` pointer for each error with `line`/`col`.
- **`buildCodeFrame(source, line, col, contextLines?)`** / **`appendCodeFrames(...)`** — exported helpers.

### Tests

- 77 new tests across 19 new test files — 506 total

---

## [1.2.6] — 2026-03-13

### Changed

- Updated `package.json` `"main"` to `./dist/cjs/index.js` and `"module"` to `./dist/esm/index.js`.
- Removed stale `"exports"` fallback entries; added `"default"` conditions.
- Added `scripts/fix-esm-extensions.mjs` — post-build script that rewrites bare
  `.js`-less imports in ESM output to include `.js` extensions (Node 18+ ESM compliance).
- Renamed `npm run fix-esm` to run `scripts/fix-esm-extensions.mjs`.

---

## [1.2.5] — 2026-03-13

### Changed

- `"type": "module"` removed from `package.json` — restores CJS as the default resolution.
- `"exports"` conditions reordered: `"require"` before `"import"` for Node.js CJS interop.
- `tsconfig.cjs.json` / `tsconfig.esm.json` separated for cleaner dual-build.

### Fixed

- `SyntaxError: The requested module does not provide an export named 'parseXml'` — caused
  by CJS consumers hitting the ESM bundle. Fixed by conditional exports + `"type"` removal.

---

## [1.2.4] — 2026-03-13

### Fixed

- `input.replace is not a function` — `XmlParser.parse()` and `XsdParser.parse()` now throw a
  descriptive `TypeError` when called with a non-string argument (e.g. a `Buffer`).

---

## [1.2.3] — 2026-03-13

### Changed

- `dist/esm/package.json` — added `"type": "module"`.
- `dist/cjs/package.json` — added `"type": "commonjs"`.
- These per-directory package files allow Node.js to resolve ESM and CJS correctly without
  changing the root `"type"` field.

---

## [1.2.2] — 2026-03-12

### Changed

- Replaced `"main"` / `"module"` with a full `"exports"` map in `package.json` for proper
  ESM/CJS conditional resolution in Node.js 18+ and bundlers.

### Fixed

- `Cannot find module 'xml-xsd-engine/dist/esm/utils/benchmark.js'` — benchmark utility
  excluded from production exports; `"exports"` now explicitly lists every sub-path.
- Duplicate `parser/XmlNodes` export removed from `index.ts`.

---

## [1.2.1] — 2026-03-12

### Added

- `BatchValidator` — concurrent multi-document validation against a shared schema.
  - `validateFiles(paths[])` / `validateStrings(strings[])`.
  - Options: `concurrency`, `failFast`, `onProgress`.
  - `BatchReport` — `{ total, passed, failed, durationMs, results }`.
- `SchemaCache` / `globalSchemaCache` — LRU + TTL schema cache.
  - `getOrParse(key, xsdSource)` — compile once, retrieve from cache on subsequent calls.
  - `getOrLoad(key, filePath)` — file-based loader variant.
  - Options: `maxSize` (100), `ttlMs` (3 600 000).

---

## [1.2.0] — 2026-03-11

### Added

- `xmlToJson(doc, options?)` — converts `XmlDocument` to a plain JS object.
  - Options: `attributePrefix` ('@'), `textKey` ('_text'), `includeComments`, `collapseText`.
- `jsonToXml(obj, options?)` / `jsonToXmlString(obj, options?)` — convert JS object to XML DOM / string.
- `transformXml(xmlSource, xsltSource, options?)` / `transformDocument(doc, xslt, options?)` — XSLT-lite transformer.
  - Supports: `xsl:template`, `xsl:apply-templates`, `xsl:for-each`, `xsl:value-of`, `xsl:if`, `xsl:choose`,
    `xsl:copy`, `xsl:copy-of`, `xsl:element`, `xsl:attribute`, `xsl:text`, `xsl:variable`, `xsl:param`,
    `xsl:sort`, `xsl:number`. Attribute Value Templates supported.
- `ParseBudget` — configurable resource limits for untrusted XML.
  - `wallTimeMs`, `bytes`, `maxDepth`, `maxAttributes`, `maxEntityExpansion`.

### Tests

- 271 tests, 0 failures

---

## [1.1.0] — 2026-03-04

### Added

- `parseSax(xml, handler)` — push-based SAX parser with O(depth) memory.
- `SaxParser` class with pull-iterator via `.events()`.
- `parseXmlStream(stream)` / `parseXmlFileStream(path)` — Node.js stream-based parsing.
- `XmlStreamParser` — Node.js `Transform` stream that accumulates chunks and emits `XmlDocument`.
- `PluginRegistry.registerTypeValidator(typeName, fn)` — custom XSD type validators.
- `PluginRegistry.registerEntityResolver(fn)` — custom entity expansion.
- `BatchValidator` — multi-document concurrent validation (initial version).
- `readXmlFile(path)` / `readXsdFile(path, loader?)` / `validateFiles(xmlPath, xsdPath)` — async file I/O helpers.

### Fixed

- Namespace prefix resolution now correctly handles default namespace (`xmlns="..."`) on child elements.
- `XmlSerializer` now preserves CDATA sections instead of escaping their content.

---

## [1.0.0] — Initial Release

### Added

- XML 1.0 lexer — single-pass state machine; all token types; CRLF normalization; line/col tracking.
- XML parser — stack-based DOM builder (`XmlDocument`, `XmlElement`, `XmlText`, `XmlComment`, `XmlPI`).
- Namespace resolution — scoped prefix→URI, default namespace, well-formedness enforcement.
- SAX-compatible event callbacks (basic: `startElement`, `endElement`, `text`, `comment`).
- XML serializer — round-trip, mixed-content aware.
- XSD 1.0 parser — large subset including all compositors and `xs:import`/`xs:include`.
- Schema model — in-memory typed graph with 44 built-in types.
- Validation engine — structure (sequence, choice, occurrence), type, attribute, xs:any wildcards.
- Type validator — all XSD facets.
- Error system — `XmlError`, stable `XmlErrorCode`, `ValidationResult`, `ValidationIssue`.
- Output formatters — text, compact, JSON, GitHub Actions, JUnit.
- XPath 1.0 engine — axes, predicates, standard functions.
- Parse budget — security limits (XXE blocking, depth/node/text limits, Billion Laughs protection).
- Async file I/O — `readXmlFile`, `readXsdFile`, `validateFiles`.
- `xml-validate` CLI — all 5 output formats, exit codes.
- ESM + CJS dual build. TypeScript declarations.

---

[Unreleased]: https://github.com/hephesthesis/xml-xsd-engine/compare/v1.7.0...HEAD
[1.7.0]: https://github.com/hephesthesis/xml-xsd-engine/compare/v1.6.0...v1.7.0
[1.6.0]: https://github.com/hephesthesis/xml-xsd-engine/compare/v1.5.0...v1.6.0
[1.5.0]: https://github.com/hephesthesis/xml-xsd-engine/compare/v1.4.0...v1.5.0
[1.4.0]: https://github.com/hephesthesis/xml-xsd-engine/compare/v1.3.0...v1.4.0
[1.3.0]: https://github.com/hephesthesis/xml-xsd-engine/compare/v1.2.6...v1.3.0
[1.2.6]: https://github.com/hephesthesis/xml-xsd-engine/compare/v1.2.5...v1.2.6
[1.2.5]: https://github.com/hephesthesis/xml-xsd-engine/compare/v1.2.4...v1.2.5
[1.2.4]: https://github.com/hephesthesis/xml-xsd-engine/compare/v1.2.3...v1.2.4
[1.2.3]: https://github.com/hephesthesis/xml-xsd-engine/compare/v1.2.2...v1.2.3
[1.2.2]: https://github.com/hephesthesis/xml-xsd-engine/compare/v1.2.1...v1.2.2
[1.2.1]: https://github.com/hephesthesis/xml-xsd-engine/compare/v1.2.0...v1.2.1
[1.2.0]: https://github.com/hephesthesis/xml-xsd-engine/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/hephesthesis/xml-xsd-engine/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/hephesthesis/xml-xsd-engine/releases/tag/v1.0.0
