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

slimgym

v1.9.0

Published

An indentation-based configuration format parser for the 24th and ½ century!

Downloads

921

Readme

slimgym

An indentation-based configuration format with block strings, arrays, comments, type inference, and bidirectional conversion.

Installation

bun add slimgym    # or npm/pnpm/yarn

Works with Bun and Node.js (v18+). For syntax highlighting, install the SlimGym VS Code extension.

Quick Start

import sg from 'slimgym'

// Parse a string
const parsed = sg.parse(`
app
  name "MyApp"
  port 8080
  tags ["web", "api"]
`)

// Fetch from a file (sync or async)
const fromFile = sg.file('./config.sg')
const fromFileAsync = await sg.fileAsync('./config.sg')

// Fetch from a URL
const fromUrl = await sg.fetch('https://example.com/config.sg')

// Convert back to SlimGym format
const str = sg.slimgify({ name: "MyApp", port: 8080 })

Features

  • Indentation-based syntax - Clean, readable structure
  • Type inference - Numbers, booleans, dates, null detected automatically
  • Block strings - Multi-line strings with """
  • Arrays - Inline [a, b] or multi-line format
  • Comments - Lines starting with #
  • Repeated keys - Automatically converted to arrays
  • Forced arrays - []key syntax for single-item arrays
  • File imports - @"path" and @@"path" syntax
  • File fetching - Read files with file(), fileAsync(), and fetch URLs with fetch()
  • Bidirectional - Convert objects back with slimgify()

Syntax Guide

Basic Values

name "John"           # String
age 30                # Number
active true           # Boolean
score null            # Null
date 2025-06-15       # Date (ISO format)

Nested Objects

database
  host "localhost"
  port 5432
  credentials
    username "admin"
    password "secret"

Arrays

# Inline
tags ["frontend", "react", "typescript"]

# Multi-line
dependencies [
  "react"
  "typescript"
]

# Empty
empty []

Block Strings

description """
  Multi-line text that preserves
  formatting and "quotes" without escaping.
"""

Comments

# Full line comment
name "MyApp"  # Inline comment

Repeated Keys (Auto-Arrays)

item
  name "First"
item
  name "Second"
# Result: { item: [{ name: "First" }, { name: "Second" }] }

Forced Arrays

[]items
  id 1234
# Result: { items: [{ id: 1234 }] }

File Imports

# Import entire file as object
config @"./settings.sg"

# Unwrap single-key array (@@)
items @@"./list.sg"

API

parse<T>(input: string, options?): T

Parse a SlimGym string into a JavaScript object.

const config = sg.parse<MyConfig>(`name "App"`)

Options: baseDir, maxDepth (default: 100), maxArraySize (default: 10000), maxImportDepth (default: 10). Set limits to 0 or Infinity to disable.

parseStream<T>(input: AsyncIterable<string | Uint8Array>, options?): Promise<T>

Parse a stream of chunks/lines into a JavaScript object (useful for very large files).

import { createReadStream } from 'node:fs'
import { parseStream } from 'slimgym/parse'

const stream = createReadStream('./config.sg')
const config = await parseStream(stream)

file<T>(filePath, options?) / fileAsync<T>(filePath, options?)

Read and parse a SlimGym file (sync or async).

const config = sg.file('./config.sg')
const config = await sg.fileAsync('./config.sg', { sandboxDir: '/app' })

Additional options: sandboxDir (restricts file access to prevent path traversal)

fetch<T>(url: string, options?): Promise<T>

Fetch and parse from a URL.

const config = await sg.fetch('https://example.com/config.sg', {
  headers: { 'Authorization': 'Bearer token' },
  allowedHosts: ['example.com']  // SSRF prevention
})

// With timeout
const config = await sg.fetch('https://example.com/config.sg', {
  signal: AbortSignal.timeout(5000)
})

Options: headers, signal (AbortSignal for cancellation/timeout), method, baseUrl, allowedHosts

slimgify(obj: any): string

Convert a JavaScript object to SlimGym format.

sg.slimgify({ app: { name: 'MyApp', tags: ['web'] } })
// app
//   name "MyApp"
//   tags ["web"]

$find(query, options?) / $findAll(query, options?)

Search by key path with optional regex patterns. $find returns first match, $findAll returns all as { key, value }[].

config.$find('user.personalInfo.firstName')  // Exact path → "John"
config.$find('Name$')                         // Regex: keys ending with "Name"
config.$find('user.\\w+.firstName')           // Regex: any intermediate key

config.$findAll('Name$')  // All matches → [{ key: '...', value: '...' }, ...]

Pattern notes: Use \\w+ (not .+) to match any key since . is the path separator. Options: depth limits search depth.

$findValue(pattern, options?) / $findAllValues(pattern, options?)

Search by value instead of key. Returns { key, value } or array of matches.

config.$findValue('@example\\.com')   // First value matching pattern
config.$findAllValues('^active$')     // All values matching pattern

$forEach(callback)

Iterate over root object keys or array elements: (value, key, parent) => void

config.$forEach((value, key) => console.log(`${key}: ${value}`))

$clone(query?, options?)

Deep clone the entire object or a portion using $find syntax:

const copy = config.$clone()              // Full deep copy
const tags = config.$clone('user.tags')   // Clone only matching portion

$merge(override)

Deep merge another object into this one. Override values take precedence at any depth. Returns a new wrapped config (chainable).

const base = sg.file('./base.sg')
const overrides = sg.file('./overrides.sg')
const config = base.$merge(overrides)

// Chain multiple overrides
const config = base
  .$merge(sg.file('./env/production.sg'))
  .$merge({ database: { port: 5433 } })

Merge behavior:

  • Plain objects are recursively merged at all depths
  • Arrays, primitives, Dates, and null replace entirely (not merged)
  • Keys only in base are preserved
  • Keys only in override are added

$mergeJSON(json)

Same as $merge, but accepts a JSON string instead of an object. Throws ParseError for invalid JSON or non-object JSON values.

import { readFileSync } from 'node:fs'

// Merge from a local JSON file
const config = base.$mergeJSON(readFileSync('./overrides.json', 'utf8'))

// Or from a config service
const overrides = await fetch('https://config.example.com/app').then(r => r.text())
const config = base.$mergeJSON(overrides)

$freeze()

Recursively freeze the object (immutable). Returns self for chaining.

config.$freeze()  // All mutations now throw in strict mode

Note: Method names use $ prefix because $ isn't valid in SlimGym keys—no collision with your data.

CLI

SlimGym includes a command-line interface for parsing, formatting, validating, and converting files.

# Install globally
npm install -g slimgym

# Or use via npx
npx slimgym <command> <file>

Commands

| Command | Description | |---------|-------------| | slimgym parse <file> | Parse .sg file → output JSON | | slimgym format <file> | Format/prettify a .sg file | | slimgym validate <file> | Validate syntax (exit 0/1) | | slimgym convert <file> | Convert JSON → SlimGym |

Options

| Option | Description | |--------|-------------| | -o, --output <file> | Write to file instead of stdout | | -i, --indent <n> | JSON indent spaces (default: 2) | | -q, --quiet | Suppress output | | -h, --help | Show help | | -v, --version | Show version |

Examples

slimgym parse config.sg                    # Output JSON to stdout
slimgym parse config.sg -o config.json     # Write JSON to file
slimgym format config.sg -o config.sg      # Format in-place
slimgym validate config.sg && echo "OK"    # Check syntax
slimgym convert config.json -o config.sg   # JSON to SlimGym

TypeScript

All methods support generics: sg.parse<Config>(...), sg.file<Config>(...), etc.

Tree-Shaking

import { parse, file } from 'slimgym/parse'
import { slimgify } from 'slimgym/slimgify'

Error Handling

Catch ParseError for file/parse errors. Includes message, lineNumber, columnNumber, and a codeFrame when applicable.

import sg, { ParseError } from 'slimgym'

try {
  sg.parse(`user\n  name "Jane"\n  []__proto__ 1`)
} catch (error) {
  if (error instanceof ParseError) {
    console.error(error.message)
  }
}

Output:

Forbidden key "__proto__" (potential prototype pollution) at line 3, column 5
3 |   []__proto__ 1
  |     ^

Note: file() / fileAsync() and @ imports are server-only and throw ParseError in browser environments.

Security

Built-in protections for untrusted input:

  • Prototype pollution – Keys like __proto__, constructor, prototype are blocked
  • Path traversal – Use sandboxDir to restrict file access
  • SSRF prevention – Use allowedHosts to restrict URL fetching
  • DoS protectionmaxDepth, maxArraySize, maxImportDepth limits (set to 0 or Infinity to disable)

Use Cases

i18n / localization - Structured translations with block strings for long copy and/or markdown.

CMS content - Static pages with metadata + content in one human-editable file.

Multi-environment config - Compose a base config and override per environment using @ imports.

Test fixtures - Keep readable test data and API mock responses; compose fixtures with forced arrays and imports.

Game content - Dialogue, quests, item databases—friendly for writers/designers to edit safely.

AI prompt templates - Store system prompts, few-shot examples, and long multi-line instructions with block strings (no escaping).

Development

bun install && bun run build && bun test

Node.js works too—just use npm install / npm run build / npx vitest run.

License

MIT