npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@o3co/ts.hocon

v1.1.0

Published

Full Lightbend HOCON spec-compliant parser and config library for TypeScript

Readme

ts.hocon — HOCON Parser for TypeScript

npm CI codecov License

A full Lightbend HOCON spec-compliant TypeScript library.

Implemented by Claude (Anthropic) — designed and built end-to-end with Claude Code. Reviewed by GitHub Copilot and OpenAI Codex.

日本語


Quick Start

1. Install

npm install @o3co/ts.hocon

Requires Node.js 18+.

2. Use

import { parse } from '@o3co/ts.hocon'

const cfg = parse(`
  server {
    host = "localhost"
    port = 8080
  }
`)

cfg.getString('server.host')   // "localhost"
cfg.getNumber('server.port')   // 8080
cfg.has('server.host')         // true

Why HOCON?

| | .env | JSON | YAML | HOCON | |---|---|---|---|---| | Comments | No | No | Yes | Yes | | Nesting | No | Yes | Yes | Yes | | References / Substitution | No | No | No | Yes (${var}) | | File inclusion | No | No | No | Yes (include) | | Object merging | No | No | Anchors (fragile) | Yes (deep merge) | | Optional values | No | No | No | Yes (${?var}) | | Trailing commas | N/A | No | N/A | Yes | | Unquoted strings | Yes | No | Yes | Yes |

HOCON gives you the readability of YAML, the structure of JSON, and features that neither has — substitutions, includes, and deep merge. If your config is more than a few flat key-value pairs, HOCON is worth considering.

Features

  • Full HOCON parsing: objects, arrays, scalars, substitutions (${path}, ${?path})
  • Self-referential substitutions (path = ${path}:/extra)
  • Deep-merge for duplicate keys (last definition wins)
  • += append operator
  • include "file.conf" and include file("file.conf") directives
  • Triple-quoted strings ("""...""")
  • Duration and byte size parsing (getDuration(), getBytes())
  • Sync and async API (parse / parseAsync / parseFile / parseFileAsync)
  • ESM + CJS dual package
  • Optional Zod integration for schema validation
  • Browser compatible (parse/parseAsync — no Node.js required)

API

For full API documentation, see o3co.github.io/ts.hocon (generated with TypeDoc, updated on each minor/major release).

Parse functions

import { parse, parseAsync, parseFile, parseFileAsync } from '@o3co/ts.hocon'
import type { ParseOptions } from '@o3co/ts.hocon'

parse(input: string, opts?: ParseOptions): Config
parseAsync(input: string, opts?: ParseOptions): Promise<Config>
parseFile(path: string, opts?: ParseOptions): Config
parseFileAsync(path: string, opts?: ParseOptions): Promise<Config>

ParseOptions: | Option | Type | Description | |--------|------|-------------| | baseDir | string | Base directory for include resolution | | env | Record<string, string> | Environment variables for substitution (default: process.env) | | readFileSync | (path: string) => string | Custom file reader (sync) | | readFile | (path: string) => Promise<string> | Custom file reader (async) |

Config methods

| Method | Returns | Throws if | |--------|---------|-----------| | get(path) | unknown \| undefined | — | | getString(path) | string | missing, wrong type | | getNumber(path) | number | missing, wrong type | | getBoolean(path) | boolean | missing, wrong type | | getConfig(path) | Config | missing, not an object | | getList(path) | unknown[] | missing, not an array | | getDuration(path, unit?) | number | missing, not a string, or invalid duration format | | getBytes(path, unit?) | number | missing, not a string, or invalid byte size format | | has(path) | boolean | — | | keys() | string[] | — | | withFallback(fallback) | Config | — | | toObject() | unknown | — |

Zod integration

import { validate, getValidated } from '@o3co/ts.hocon/zod'
import { z } from 'zod'

const Schema = z.object({
  server: z.object({
    host: z.string(),
    port: z.number().int(),
  }),
})

// Validate entire config
const app = validate(cfg, Schema)

// Validate a single path
const port = getValidated(cfg, 'server.port', z.number().int())

Install Zod as a peer dependency:

npm install zod

Error types

import { ParseError, ResolveError, ConfigError } from '@o3co/ts.hocon'

// ParseError   — lexing/parsing failure: .line, .col, .file?
// ResolveError — substitution/include failure: .path, .line, .col, .file?
// ConfigError  — wrong type or missing path: .path

HOCON Examples

# Comments with # or //
database {
  host = "db.example.com"
  port = 5432
  url  = "jdbc:"${database.host}":"${database.port}
}

# Duplicate keys deep-merge (last wins for scalars)
server { host = localhost }
server { port = 8080 }      // result: { host: "localhost", port: 8080 }

# Self-referential append
path = "/usr/bin"
path = ${path}":/usr/local/bin"

# += shorthand
items = [1]
items += 2
items += 3   // [1, 2, 3]

# Include
include "defaults.conf"
include file("overrides.conf")

# Triple-quoted multiline strings
description = """
  This is a
  multiline string.
"""

Duration and Byte Sizes

const c = parse(`
  timeout   = "30s"
  cache-ttl = "5m"
  max-size  = "512MiB"
`)

c.getDuration('timeout')        // 30000 (ms)
c.getDuration('timeout', 's')   // 30
c.getDuration('cache-ttl', 'm') // 5

c.getBytes('max-size')          // 536870912 (bytes)
c.getBytes('max-size', 'MiB')  // 512

Supported duration units: ns, us, ms, s, m, h, d (and long forms like seconds, minutes). Supported byte units: B, KB/KiB, MB/MiB, GB/GiB, TB/TiB (and long forms like megabytes, mebibytes).

Spec Compliance

Tested against the Lightbend official test suite: 13/13 test groups pass.

Not supported in v0.1.0:

  • include url(...)
  • include classpath(...)

Supported since v0.2.0 (P1):

  • .properties file parsing

Performance

ts.hocon Parsing Cost

Measured with Vitest bench (tinybench). Run pnpm bench to reproduce.

| Scenario | ops/sec | Time per op | |---|---|---| | Small config (10 keys) | ~200,000 | ~5 µs | | Medium config (100 keys) | ~23,000 | ~43 µs | | Large config (1,000 keys) | ~2,100 | ~476 µs | | 10 substitutions | ~74,000 | ~14 µs | | 50 substitutions | ~14,000 | ~71 µs | | 100 substitutions | ~6,900 | ~145 µs | | Depth 5 nesting | ~210,000 | ~5 µs | | Depth 10 nesting | ~147,000 | ~7 µs | | Depth 20 nesting | ~80,000 | ~13 µs |

Comparison with JSON.parse

JSON.parse is V8's native C++ implementation — the fastest possible baseline. This comparison shows the overhead of HOCON's rich feature set.

| Config Size | ts.hocon | JSON.parse | Ratio | |---|---|---|---| | Small (10 keys) | ~198K ops/s | ~1,967K ops/s | ~10x | | Medium (100 keys) | ~23K ops/s | ~280K ops/s | ~12x | | Large (1,000 keys) | ~2.2K ops/s | ~12K ops/s | ~5.4x |

For typical application configs (loaded once at startup), the parsing cost is negligible — even a 1,000-key config parses in under 0.5 ms.

Feature Comparison with node-config

ts.hocon provides significantly richer configuration capabilities compared to node-config (JSON):

| Feature | ts.hocon | node-config (JSON) | |---|---|---| | Comments | // # | No | | Multi-line strings | """...""" | No | | Substitution (${path}) | Yes | No | | Optional substitution (${?path}) | Yes | No | | Environment variable reference | Yes (via substitution) | Partial (custom-environment-variables file) | | Include | Yes | No | | Deep merge | Yes (arrays too) | Partial (arrays replaced) | | Append operator (+=) | Yes | No | | Environment-based config | Configurable via HOCON | Yes (filename convention) | | Schema validation | Zod integration | No | | Programmatic API | parse(string) | File-based initialization, then get() | | Typed getters | getString, getNumber, etc. | get() (any) |

Browser Compatibility

parse() and parseAsync() work in browsers. parseFile() and parseFileAsync() require Node.js (or a custom readFileSync/readFile option).

// Browser usage with custom file loader
const cfg = await parseAsync(hoconString, {
  readFile: async (path) => {
    const res = await fetch(`/config/${path}`)
    return res.text()
  },
})

Best Practices

Config Structure

  • Split by domain: Separate configuration into logical units (database.conf, server.conf, logging.conf)
  • Use include for composition: Compose a full config from domain-specific files
  • Avoid logic in config: HOCON is for declarative data, not conditionals or computation

Environment Variables

  • Minimize ${ENV} usage: Prefer ${?ENV} (optional) with sensible defaults defined in the config itself
  • Never require env vars for local development: Defaults should work out of the box
  • Document required env vars: List them in your project's README or a .env.example

Dev / Prod Separation

config/
├── application.conf    # shared defaults
├── dev.conf            # include "application.conf" + dev overrides
└── prod.conf           # include "application.conf" + prod overrides

Validation

  • Always validate config at application startup, not at point-of-use
  • Use schema validation (Zod for TypeScript, struct unmarshaling for Go, Serde for Rust) to catch errors early
import { parseWithSchema } from '@o3co/ts.hocon/zod'
import { z } from 'zod'

const schema = z.object({
  server: z.object({ host: z.string(), port: z.number() }),
  debug: z.boolean(),
})
const config = parseWithSchema(hoconInput, schema) // fails fast on startup

Related Projects

| Project | Language | Registry | Description | |---------|----------|----------|-------------| | go.hocon | Go | pkg.go.dev | HOCON parser for Go | | rs.hocon | Rust | crates.io | HOCON parser for Rust | | hocon2 | Go | pkg.go.dev | HOCON → JSON/YAML/TOML/Properties CLI |

All implementations are full Lightbend HOCON spec compliant.

Known Limitations

  • include url(...) is not supported. Fetching remote configuration is outside the scope of this parser. Use your application's HTTP client to fetch the content, then pass it to parse().
  • include classpath(...) is not supported. This is a JVM-specific include form with no equivalent outside Java runtimes.
  • No watch/reload — the library parses config at load time. For live-reloading, re-call parse() or parseFile() on change.
  • No streaming parser — the entire input is loaded into memory. For very large configs, validate input size before parsing (see Security Considerations).
  • .properties include — supports basic key=value / key:value syntax. Does not support multiline values (backslash continuation), Unicode escapes, or key escaping from the full Java .properties specification.

Security Considerations

When parsing untrusted HOCON input, be aware of:

  • Path traversal in includes: include "../../../etc/passwd" will resolve relative to baseDir. Use a custom readFileSync/readFile that validates paths if parsing untrusted input.
  • Input size: The parser has no built-in input size limit. For untrusted input, validate size before calling parse().
  • Include depth: Limited to 50 levels to prevent stack overflow from deep include chains.

License

Apache License 2.0 — see LICENSE.

Copyright 2026 1o1 Co. Ltd.