yaml
js/format/yaml.ts
fino:format/yaml - YAML 1.2 core schema parser and serializer.
YAML is a human-oriented data serialization format often used for configuration, manifests, and multi-document files. This module implements the YAML 1.2 core schema with a security-first surface: it resolves core scalar types, expands anchors and aliases within configured limits, and never constructs arbitrary application objects from tags.
YAML 1.2.2 conformance matrix:
| Area | Status |
|---|---|
| Block mappings/sequences | Supported for indentation-driven collections. |
| Flow collections | Supported for [] sequences and {} mappings. |
| Scalar styles | Plain, single-quoted, double-quoted, literal ` |
| Core scalar resolution | YAML 1.2 core null, booleans, integers, floats, and strings are resolved; YAML 1.1 words such as yes, on, and Off stay strings. |
| Anchors and aliases | Supported within one document under an expansion budget; undefined, recursive, and cross-document aliases are rejected. |
| Explicit core tags | !!str, !!int, !!float, !!bool, !!null, !!seq, !!map, !!binary, and !!timestamp are supported. |
| Merge keys | <<: *anchor and <<: [*a, *b] are absorbed into the enclosing mapping. |
| Complex keys | ? key is supported; mappings with non-string keys return Map<unknown, YamlValue>. |
| Comments and markers | Comments, ---, ..., and parseAll() multi-document streams are supported; comments and markers are not re-emitted. |
| Stringify normalization | Output preserves the value graph but normalizes comments, source anchor names, document markers, and merge syntax. |
| Security limits | Alias expansion is bounded by an estimated-size budget, and arbitrary object construction is never performed. |
| Intentional limits | %YAML/%TAG directives, custom tags, local tags, and application object construction are rejected. |
This parser targets Fino's core-schema configuration use cases, not complete YAML processor parity.
YAML directives (%YAML, %TAG) are outside the release baseline and are
rejected.
Permanently excluded (security baseline - never executes code): - Arbitrary type construction (!!ruby/object, etc.) - Custom user-defined tags - Local tags (!foo) - use !! core tags only
Note on merge keys: merge pairs (<<) are absorbed at parse time into the
enclosing mapping. stringify does not re-emit them. The round-trip invariant
deepEqual(parse(stringify(parse(x))), parse(x)) holds; text-exact
round-trip does not for documents with merge keys, comments, document
markers, or source anchor names.
import { parse, stringify, parseAll } from 'fino:format/yaml';
const cfg = parse('server:\n port: 8080\nhosts:\n - a\n - b\n');
const text = stringify({ x: 1, y: [2, 3] });
const docs = parseAll('---\na: 1\n---\nb: 2\n');
Useful references: - YAML 1.2.2 specification: https://yaml.org/spec/1.2.2/ - YAML core schema: https://yaml.org/spec/1.2.2/#103-core-schema
Classes
class YamlParseError extends ParseError {
Error thrown when YAML input is malformed or violates configured limits.
The error extends ParseError and carries YAML format metadata plus line,
column, and offset information where available. Duplicate keys, unsupported
tags, undefined aliases, alias expansion limits, and syntax errors are
reported through this type.
import { YamlParseError, parse } from 'fino:format/yaml';
try {
parse('a: 1\na: 2\n');
} catch (error) {
if (error instanceof YamlParseError) console.error(error.render());
}
Properties
name
Error name, always 'YamlParseError'.
Useful for distinguishing YAML failures from other ParseError subclasses
in logs or serialized error reports where instanceof is unavailable.
Types
type YamlValue = null | boolean | number | string | Uint8Array | Date | YamlValue[] | YamlMapping | Map<unknown, YamlValue>
Value types produced by the YAML core schema parser and accepted by stringify.
Core tags resolve to JavaScript primitives, Uint8Array for !!binary,
Date for !!timestamp, arrays for sequences, plain objects for mappings
with string keys, and Map for mappings with complex keys.
import { parse, stringify, type YamlValue } from 'fino:format/yaml';
const value: YamlValue = parse('enabled: true\ncount: 3\n');
stringify(value);
type YamlMapping = {
[k: string]: YamlValue;
}
Plain-object YAML mapping with string keys.
Mappings with non-string keys are returned as Map<unknown, YamlValue>
instead, because JavaScript object keys cannot preserve arbitrary YAML key
values.
import { parse, type YamlMapping } from 'fino:format/yaml';
const mapping = parse('server:\n port: 8080\n') as YamlMapping;
(mapping.server as YamlMapping).port;
Interfaces
interface YamlParseOptions {
Options controlling YAML parsing limits and duplicate-key behavior.
Defaults reject duplicate keys and cap total alias expansion at 1,000,000 estimated characters.
import { parse, type YamlParseOptions } from 'fino:format/yaml';
const options: YamlParseOptions = { maxAliasExpansion: 10_000 };
parse('a: 1\n', options);
Properties
allowDuplicateKeys?: boolean
Permit later duplicate keys to replace earlier values. Defaults to false.
When disabled, duplicate keys throw YamlParseError. For complex keys,
duplicate detection uses a JSON string form and is best-effort.
import { parse } from 'fino:format/yaml';
parse('a: 1\na: 2\n', { allowDuplicateKeys: true });
maxAliasExpansion?: number
Maximum estimated expanded character count from aliases.
Defaults to 1_000_000. Lower values can reject hostile or accidental
alias amplification earlier for untrusted inputs.
import { parse } from 'fino:format/yaml';
parse('a: &a hello\nb: *a\n', { maxAliasExpansion: 100 });
maxAliasDepth?: number
Reserved cap on nested alias expansion depth. Defaults to 100.
The current parser resolves an alias against a node that is already fully
constructed (an anchor is only bound once its value is complete, which is
also why self-referential aliases fail as undefined), so depth cannot grow
during resolution and this option is not separately enforced. Alias
amplification attacks are instead caught by the maxAliasExpansion
budget. The option is accepted so configurations remain valid if a future
parser needs an explicit depth check.
interface YamlStringifyOptions {
Options controlling YAML serialization style.
Stringification emits a readable block style for objects and arrays and does not preserve source comments, anchor names, or merge keys from parsed input.
import { stringify, type YamlStringifyOptions } from 'fino:format/yaml';
const options: YamlStringifyOptions = { indent: 4 };
stringify({ server: { port: 8080 } }, options);
Properties
indent?: number
Spaces per nesting level. Defaults to 2.
Values are used directly by the formatter; choose a positive integer for conventional YAML output.
import { stringify } from 'fino:format/yaml';
stringify({ a: { b: 1 } }, { indent: 4 });
lineWidth?: number
Reserved preferred scalar wrapping width.
The current formatter never wraps scalars: long strings stay on one line (quoted when required), and short all-scalar sequences are inlined using a fixed internal width. This option is accepted but has no effect on output today; it exists so configurations remain valid if wrapping is added.
Functions
function parse(input: string | Uint8Array, options: YamlParseOptions = {}): YamlValue
Parse one YAML document, returning the first document from a stream.
String input is parsed directly; byte input is decoded as UTF-8. If the input
contains no document content, the function returns null. For multi-document
streams, use parseAll() to keep every document.
Throws YamlParseError when the input is malformed, uses an unsupported
feature (directives, custom or local tags), repeats a key without
allowDuplicateKeys, or exceeds the alias expansion budget.
import { parse, type YamlMapping } from 'fino:format/yaml';
const config = parse(`
server:
host: 0.0.0.0
port: 8080
features:
- metrics
- tracing
`) as YamlMapping;
const server = config.server as YamlMapping;
server.port; // 8080 (number, resolved by the core schema)
config.features; // ['metrics', 'tracing']
function parseAll(input: string | Uint8Array, options: YamlParseOptions = {}): YamlValue[]
Parse all YAML documents from a multi-document stream.
Document markers (--- and ...) are consumed between documents. Anchors
are scoped per document and cleared before parsing the next document, so an
alias in one document cannot reference an anchor from a previous one. Empty
input returns an empty array.
Throws YamlParseError under the same conditions as parse(); a syntax
error anywhere in the stream fails the whole call.
import { parseAll } from 'fino:format/yaml';
const docs = parseAll('---\na: 1\n---\nb: 2\n');
docs.length; // 2
function stringify(value: YamlValue, options: YamlStringifyOptions = {}): string
Serialize a YAML-compatible value.
Serialization emits YAML core-schema values from JavaScript primitives,
arrays, plain mappings, Map, Uint8Array (as !!binary), and Date (as
!!timestamp). It does not re-emit comments, merge keys, document markers,
or anchor names from parsed input; however, an object referenced more than
once in the value graph is emitted once with a generated anchor (&a1) and
aliased (*a1) at later occurrences, so shared identity survives a
round-trip. Strings that would otherwise resolve as another scalar type, or
that start with an indicator character or contain newlines, are
single-quoted.
import { stringify } from 'fino:format/yaml';
stringify({
server: { host: '0.0.0.0', port: 8080 },
hosts: ['a', 'b'],
});
// server:
// host: 0.0.0.0
// port: 8080
// hosts: [a, b]