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 🙏

© 2025 – Pkg Stats / Ryan Hefner

slimgym

v1.5.10

Published

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

Downloads

1,902

Readme

slimgym

An indentation-based configuration format that combines clean syntax with modern features like block strings, arrays, comments, type inference, and bidirectional conversion.

Installation

Works with both Bun and Node.js (v18+):

bun add slimgym
# or
npm install slimgym
# or
pnpm add slimgym

Quick Start

import sg from 'slimgym'

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

// Fetch from a file (sync or async)
const config = sg.fetch('./config.sg')
const config = await sg.fetchAsync('./config.sg')

// Fetch from a URL
const config = await sg.fetchUrl('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 fetch(), fetchAsync(), or fetchUrl()
  • 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 - Base directory for @ imports (default: process.cwd())
  • maxDepth - Maximum nesting depth (default: 100, set to 0 to disable)
  • maxArraySize - Maximum array size (default: 10000, set to 0 to disable)
  • maxImportDepth - Maximum @ import chain depth (default: 10, set to 0 to disable)

fetch<T>(filePath: string, options?): T

Read and parse a SlimGym file synchronously.

const config = sg.fetch('./config.sg')
const config = sg.fetch('config.sg', { baseDir: '/app', sandboxDir: '/app' })

Options:

  • baseDir - Base directory for relative paths (default: process.cwd())
  • sandboxDir - Restrict file access to this directory (prevents path traversal)
  • maxDepth, maxArraySize, maxImportDepth - Same as parse()

fetchAsync<T>(filePath: string, options?): Promise<T>

Read and parse a SlimGym file asynchronously.

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

Options: Same as fetch()

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

Fetch and parse a SlimGym file from a URL using Node's native fetch.

const config = await sg.fetchUrl('https://example.com/config.sg')
const config = await sg.fetchUrl('https://cdn.example.com/config.sg', {
  allowedHosts: ['cdn.example.com']
})

Options:

  • baseUrl - Base URL for resolving @ imports within the content
  • allowedHosts - Restrict fetching to these hostnames (prevents SSRF)
  • maxDepth, maxArraySize, maxImportDepth - Same as parse()

slimgify(obj: any): string

Convert a JavaScript object to SlimGym format.

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

Multi-line strings automatically become block strings. Large arrays use multi-line format.

toJSON()

Parsed objects include a toJSON() method that converts Date objects to ISO strings:

const config = sg.parse(`date 2025-06-15T09:00:00Z`)
config.date          // Date object
config.toJSON().date // "2025-06-15T09:00:00.000Z"

clone()

Parsed objects include a clone() method that creates a deep, completely decoupled copy:

const config = sg.parse(`
user
  name "John"
  tags ["admin", "active"]
`)

const copy = config.clone()

// Modifications to the clone don't affect the original
copy.user.name = "Jane"
copy.user.tags.push("new")

config.user.name  // "John" (unchanged)
config.user.tags  // ["admin", "active"] (unchanged)

The clone method is optimized for performance using an iterative algorithm that avoids recursion overhead. Date objects are properly cloned as new Date instances.

TypeScript

All methods support generics for type safety:

interface Config {
  name: string
  port: number
}

const config = sg.parse<Config>(`name "App"\nport 8080`)
const config = sg.fetch<Config>('./config.sg')
const config = await sg.fetchAsync<Config>('./config.sg')
const config = await sg.fetchUrl<Config>('https://example.com/config.sg')

Tree-Shaking

Import only what you need:

import { parse, fetch, fetchAsync, fetchUrl } from 'slimgym/parse'
import { slimgify } from 'slimgym/slimgify'

Exported Types: NodeObject, NodeValue, Primitive, ParseError, ParseOptions, FetchOptions, FetchUrlOptions

Error Handling

import { ParseError } from 'slimgym'

try {
  sg.fetch('./missing.sg')
} catch (error) {
  if (error instanceof ParseError) {
    console.error(error.message)  // "File not found: ..."
    console.error(error.lineNumber) // Line number (if parse error)
  }
}

Security

SlimGym includes built-in protections when handling untrusted input:

Prototype Pollution Protection

Keys like __proto__, constructor, and prototype are automatically blocked:

sg.parse('__proto__ "evil"')  // Throws ParseError

Path Traversal Prevention

Use sandboxDir to restrict file access:

// Only allows access within /app/config
sg.fetch('../../../etc/passwd', {
  baseDir: '/app/config',
  sandboxDir: '/app/config'  // Blocks escape attempts
})

SSRF Prevention

Use allowedHosts to restrict URL fetching:

await sg.fetchUrl('https://internal-api.local/config.sg', {
  allowedHosts: ['cdn.example.com']  // Blocks - not in allowlist
})

DoS Protection

Limits prevent resource exhaustion:

sg.parse(maliciousInput, {
  maxDepth: 50,        // Max nesting depth (default: 100)
  maxArraySize: 1000,  // Max array items (default: 10000)
  maxImportDepth: 5    // Max @import chain (default: 10)
})

Set any limit to 0 or Infinity to disable it.

Use Cases

Configuration files - App settings, environment configs, feature flags

Content definition - CMS content, templates, theming

Data serialization - Human-readable data storage and exchange

Development

This project uses Bun for development:

bun install    # Install dependencies
bun run build  # Build the project
bun test       # Run tests

Using Node.js

The published package is fully compatible with Node.js (v18+). Install with your preferred package manager:

npm install slimgym
# or
yarn add slimgym
# or
pnpm add slimgym

No runtime differences — the package uses standard Node.js APIs (node:fs, node:path) that work identically in both Bun and Node.js. All features, including fetch(), fetchAsync(), fetchUrl(), and parse(), behave the same regardless of runtime.

If you want to contribute or run the test suite locally with Node.js instead of Bun:

# Install dependencies (using npm/pnpm/yarn)
npm install

# Build (requires tsc from devDependencies)
npm run build

# Run tests (requires vitest from devDependencies)
npx vitest run

Note: The lockfile (bun.lockb) is Bun-specific. When using npm/pnpm/yarn, a new lockfile will be generated for your package manager.

License

MIT