npm.io
1.0.0 • Published 18h agoCLI

xyaml

Licence
MIT
Version
1.0.0
Deps
1
Size
278 kB
Vulns
0
Weekly
0

CI npm version npm downloads minzipped size license TypeScript

xyaml

A secure, extensible YAML compiler for TypeScript, Bun, Node.js, and modern browsers.

xyaml adds deterministic references, composition, resource imports, typed results, and opt-in JavaScript evaluation to standard YAML. The default mode does not execute code or access the network.

Features

  • Explicit self, parent, root, and context references
  • Deterministic $include merging and typed !import values
  • Local files, HTTP resources, raw text, JSON, and JavaScript modules
  • Safe-by-default parsing with a clearly separated trusted mode
  • Async and sync Node.js APIs, plus a browser-safe core
  • First-class TypeScript declarations, ESM, CommonJS, and a CLI
  • Structured errors, source metadata, cycle detection, caching, and cancellation
  • YAML 1.2 support powered by yaml

Installation

bun add xyaml
npm install xyaml

xyaml requires Bun 1.3+ or Node.js 22+.

Quick start

# config.xyaml
$include:
  - ./defaults.xyaml

app:
  host: localhost
  port: 8000
  url: http://${self.host}:${self.port}

features: !import ./features.json
license: !text ./LICENSE
import { loadFile } from 'xyaml/node';

interface Config {
  app: {
    host: string;
    port: number;
    url: string;
  };
  features: Record<string, boolean>;
  license: string;
}

const config = await loadFile<Config>('./config.xyaml');

The generic type describes the expected result; it is not runtime validation. Validate untrusted data with the schema library used by your application.

References

References are intentionally explicit:

name: xyaml
port: 8000
portCopy: ${self.port}

server:
  host: localhost
  url: http://${self.host}:${parent.port}
  packageName: ${root.name}
  environment: ${context.environment}
  • self is the mapping or sequence containing the current value.
  • parent is the parent of self.
  • root is the compiled root document.
  • context is supplied by the caller.

An exact reference preserves its type. Embedded references accept scalar values and produce a string. Use ${self.value} to emit the literal text ${self.value}. Missing references, ambiguous short names, object interpolation, and cycles are errors.

import { parse } from 'xyaml';

const config = parse(source, {
  context: { environment: 'production' },
});

Composition

$include

$include merges mappings into the current mapping:

$include:
  - ./defaults.xyaml
  - source: ./environments.xyaml
    select: production

port: 9000

Includes are applied from left to right. Later includes override earlier includes, and local keys override every included key. Included values must be mappings.

!import

!import inserts a YAML, XYAML, or JSON resource as a value:

database: !import ./database.xyaml

enabledFeatures: !import
  source: ./features.json
  select: production

Local Node.js resolution checks the exact path, then .xyaml, .yaml, .yml, .json, and matching index.* files.

!text and !resolve
license: !text ./LICENSE
absoluteConfigPath: !resolve ./config.xyaml

!text returns the resource without parsing it. !resolve resolves a specifier without reading it; the Node loader returns a native absolute path for file: URLs.

YAML's standard explicit tags replace the coercion commands from xyaml 0.x:

portAsString: !!str 8000
portAsInteger: !!int "8000"
ratio: !!float "0.75"

Trusted mode

Trusted mode enables arbitrary JavaScript. It is intended only for configuration files controlled by the application owner.

math:
  base: 2
  add: !fn |
    (a, b) => a + b + self.base
  result: !expr self.add(3, 4)

$eval:
  - document.delete("math.add")
const config = await loadFile('./config.xyaml', { trusted: true });
  • !expr evaluates a synchronous JavaScript expression.
  • !fn evaluates a function expression and captures its self, parent, root, and context.
  • $eval executes synchronous statements after the complete document has been resolved.
  • $eval also receives document.get, document.has, document.set, and document.delete.

Trusted mode uses the JavaScript runtime itself. It is not a sandbox and does not protect against filesystem access, process access, infinite loops, or global mutation.

JavaScript modules

The built-in Node loader supports local JavaScript modules in trusted mode:

generated: !module
  source: ./generate.mjs
  args: [production]
export default ({ args, context, source }) => ({
  environment: args[0],
  region: context.region,
  loadedFrom: source.href,
});

Async loading supports .mjs, .cjs, and .js. loadFileSync only supports .cjs. Remote JavaScript modules are not supported by the built-in loader.

Values containing functions cannot be serialized. Remove build-time helper functions with $eval before passing the result to the CLI or stringify.

HTTP resources

HTTP access is disabled by default:

const config = await loadFile('https://example.com/config.xyaml', {
  allowHttp: true,
});

The browser-safe core uses a fetch loader:

import { compile, createFetchLoader } from 'xyaml';

const baseUrl = new URL('/config/app.xyaml', window.location.href);
const loader = createFetchLoader({ baseUrl });
const source = await fetch(baseUrl).then(response => response.text());

const config = await compile(source, {
  allowHttp: true,
  baseUrl,
  loader,
});

Pass an AbortSignal as signal to cancel asynchronous resource loading. A compiler caches each canonical resource for the duration of one compilation.

API

Universal core
import {
  compile,
  compileDocument,
  createCompiler,
  parse,
  parseDocument,
  stringify,
} from 'xyaml';
  • parse<T>(source, options?) synchronously compiles a string. External resources require a loader with synchronous support.
  • compile<T>(source, options?) asynchronously compiles a string with full resource support.
  • parseDocument<T> and compileDocument<T> return an XyamlDocument<T>.
  • createCompiler(options?) creates a reusable compiler configuration. Caches remain isolated to each compilation.
  • stringify(value, options?) serializes a non-cyclic value to YAML and rejects functions and symbols.
Node.js and Bun
import {
  loadFile,
  loadFileDocument,
  loadFileDocumentSync,
  loadFileSync,
} from 'xyaml/node';
  • loadFile<T>(pathOrUrl, options?) is the recommended complete API.
  • loadFileSync<T> supports local YAML, JSON, text, and CommonJS modules.
  • Document variants return metadata and navigation methods.
Document API
const document = await loadFileDocument('./config.xyaml');

document.get('server.port');
document.has(['server', 'host']);
document.set('server.port', 9000);
document.delete('internal');
document.toJS();
document.toJSON();
document.toString();

toJS() and toJSON() return independent snapshots. Parse and resource errors throw XyamlError, which includes a stable code and optional source, path, and cause.

Custom resource loaders

Implement ResourceLoader to integrate virtual filesystems, application caches, authenticated HTTP clients, or browser storage:

import type { ResourceLoader, ResourceSource } from 'xyaml';

class VirtualLoader implements ResourceLoader {
  resolve(specifier: string, importer?: URL): URL {
    return new URL(specifier, importer);
  }

  async load(url: URL): Promise<ResourceSource> {
    return { url, content: await readFromApplicationStorage(url) };
  }
}

loadSync, loadModule, loadModuleSync, and display are optional capabilities.

CLI

xyaml compile config.xyaml
xyaml compile config.xyaml --output config.json
xyaml compile config.xyaml --format yaml --output compiled.yaml
xyaml compile - --base ./config --context context.yaml

Options:

  • -o, --output <file> writes to a file instead of stdout.
  • -f, --format json|yaml selects the output format.
  • --context <file> loads a YAML or JSON mapping as context.
  • --base <path|url> sets the base for stdin imports.
  • --trusted enables executable features.
  • --allow-http enables HTTP(S) resources.

JSON is the default stdout format. A .yaml, .yml, or .xyaml output filename selects YAML when --format is omitted. Diagnostics are written to stderr and failures use a non-zero exit code.

Migration from 0.x

Version 1.0 intentionally replaces the historical string-command grammar:

xyaml 0.x xyaml 1.0
${host} ${self.host}
~include $include
~import !import
~read, ~download !text
~path, ~resolve !resolve
~str, ~num !!str, !!int, !!float
~ex or JavaScript interpolation !expr with trusted: true
Inline fn or arrow functions !fn with trusted: true
eval $eval with trusted: true
JS modules mutating process.argv !module with { args, context, source }
dump stringify

The old releases remain useful as historical references, but 1.0 does not include a compatibility parser.

Development

bun install
bun run check

The complete check runs formatting, linting, strict TypeScript, Bun tests with coverage, the library build, Node ESM/CommonJS tests, a browser-target bundle smoke test, publint, and Are The Types Wrong.

License

MIT

Keywords