dms-js-parser
v0.5.2
Published
Pure-JavaScript parser for DMS, a data syntax with strong typing, ordered maps, and heredocs.
Readme
dms-js
JavaScript parser for DMS, a data syntax with strong typing, ordered maps, multi-line heredocs, and front-matter metadata.
Two packages live in this repo, both with the same Node API and value shape:
| package | implementation | when to use |
| -------- | ------------------------------------ | ---------------------------------------------- |
| dms-js-parser | pure JS, zero runtime deps | browsers, default Node usage |
| dms-c | N-API addon wrapping the dms-c parser | hot paths where you want a C-backed parser |
See Performance below for the honest answer to "is the C addon faster?" (short version: not always, on Node).
What DMS looks like
A medium-size tier-0 document, exercising every feature you'd touch in a
real config — front matter, comments (line + trailing), nested tables,
list-of-tables with the + marker, flow forms, distinct types, and a
heredoc with a trim modifier:
+++
title: "DMS feature tour"
version: "1.0.0"
updated: 2026-04-24T09:30:00-04:00
+++
# Hash and // line comments both work.
// Bare keys allow full Unicode; quoted keys take any string.
database:
host: "db.internal"
port: 5432 # bumped after the LB change
pool: { size: 10, idle_timeout_s: 30 } # flow table
servers:
+ name: "web1"
disks:
+ mount: "/"
size_gb: 100
+ mount: "/var"
size_gb: 500
+ name: "web2"
regions: ["us-east-1", "eu-west-1", "ap-south-1"]
sql: """SQL _trim("\n", ">")
SELECT id, email
FROM users
WHERE active = true
SQLTier 1 layers structured decorators on top of the value tree. Sigils bind
to families published by a dialect; here is dms+html carrying an HTML
fragment as a DMS document:
+++
_dms_tier: 1
_dms_imports:
+ dialect: "html"
version: "1.0.0"
+++
+ |html(lang: "en")
+ |head
+ |title "DMS feature tour"
+ |meta(charset: "UTF-8")
+ |body(class: "main")
+ |h1 "Welcome to DMS"
+ |p(class: "lede")
+ "Click "
+ |a(href: "/spec.html") "here"
+ " to read the spec."Full feature tour, format comparison, and dialect index on the DMS website.
Install
npm install dms-js-parser # pure JS (works in Node and browsers)
npm install dms-c # native (C) addon — Node onlyUsage
// pure JS port:
const dms = require('dms-js-parser');
// native (C) port — same API, same value shape:
const dms_c = require('dms-c');
const src = require('fs').readFileSync('config.dms', 'utf8');
const value = dms.decode(src); // or dms_c.decode(src)
const doc = dms.decodeDocument(src); // → { body, comments, meta, originalForms }SPEC v0.14 renamed the entry points:
parse→decode,parseDocument→decodeDocument,toDMS→encode,toDMSLite→encodeLite. The old names still work as deprecated aliases in this release; they will be removed in the release after 0.14.x.
Tables are plain objects (insertion-ordered for string keys per ES2015),
lists are arrays. Datetimes are wrapped types: the pure module returns
LocalDate / LocalTime / LocalDateTime / OffsetDateTime class
instances; the C addon returns plain { __dmsType, value } objects with
the same shape. Encoders that detect via __dmsType + value work
unchanged across both ports. See dms.js for the full type surface.
Working with comments and heredocs
DMS preserves comments through parse → mutate → re-emit (SPEC
§Comments). The Document carries them on a side-channel keyed by
breadcrumb path; the same shape lets you attach a comment to a value
after parsing and have it round-trip through encode:
const dms = require('dms-js-parser');
const doc = dms.decodeDocument('db:\n port: 8080\n');
// Mutate a value in place.
doc.body.db.port = 5432;
// Attach a leading line comment to db.port.
doc.comments.push({
comment: { content: '# bumped after LB change', kind: 'line' },
position: 'leading',
path: ['db', 'port'],
});
console.log(dms.encode(doc));Forcing a heredoc on emit
Strings parse and re-emit in their source form. To switch a basic-quoted
string to a heredoc (or to construct one from scratch), push an
originalForms entry whose lit.form.kind === 'heredoc':
doc.body.db.greeting = 'Hello, friend.\nWelcome aboard.\n';
doc.originalForms.push({
path: ['db', 'greeting'],
lit: {
kind: 'string',
form: {
kind: 'heredoc',
flavor: 'basic-triple', // or 'literal-triple' for '''
label: null, // null = unlabeled (terminator is """ / ''')
modifiers: [], // { name, args } records — _trim(...), _fold_paragraphs(), …
},
},
});Round-trip rules (SPEC §Round-trip semantics): comments stick to
still-present nodes; deleting a node drops its comments; newly
inserted nodes start with no comments. The first originalForms entry
per path wins, so override a parser-recorded form by replacing rather
than appending if the key is already present.
Browser
dms.js uses no Node-specific APIs and works in modern browsers as-is.
Drop it in with a <script> tag and the API attaches to window.DMS:
<script src="dms.js"></script>
<script>
const doc = DMS.decodeDocument(sourceText);
</script>The same file still works as a CommonJS module (require('dms-js-parser')).
Performance
50,000-key flat document (~700 KB), best-of-5, startup-subtracted, Node 23 on Windows 11:
| tier | DMS port | time | peer | time | DMS / peer |
|-------------|-----------|----------|-------------|---------|------------|
| pure JS | dms-js-parser | 60.7 ms | js-yaml | 94.0 ms | 0.65× — DMS faster |
| native (C) | dms-c | 59.5 ms | JSON.parse (V8 native) | 21.3 ms | 2.79× |
Node is the surprising case. V8's JIT optimizes the pure-JS parser hard, and the N-API marshalling cost of building 50k JS strings + setting properties on a single object eats the C port's parse-time advantage. On this fixture the two tiers tie. The C port wins on documents with heavy datetime / number content and short keys; the pure port wins on dict-heavy maps. Pick whichever ergonomics you prefer — the perf difference is small.
The pure-vs-pure column is the honest one for Node. There's no
widely-used pure-JS JSON parser to bench against (V8's JSON.parse is
C++), so JSON only appears in the FFI tier; correspondingly there's no
widely-used libyaml binding for Node, so YAML only appears in the pure
tier.
Reproduce with:
npm install # pure deps
(cd dms-c && npm install) # builds the addon
(cd bench-formats && npm install) # js-yaml + @iarna/toml
python bench-formats/bench_decoders.py --tier bothTest
npm testRuns test_roundtrip.js (encode emitter) and test_comments.js
(comment-AST attachment).
Conformance
The fixture corpus lives in dms-tests (4500+ pairs). Clone it once as a sibling:
git clone https://gitlab.com/flo-labs/pub/dms-tests.git ../dms-testsThen run the sweep:
python3 ../dms-tests/run_conformance.py "node encoder.js"dms-tests can also drive every implementation in one shot — see its
README for the cross-language workflow.
Publish
npm publishYou'll need to run npm login once.
License
MIT OR Apache-2.0
