slimgym
v1.5.10
Published
An indentation-based configuration format parser for the 24th and ½ century!
Downloads
1,902
Maintainers
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 slimgymQuick 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 -
[]keysyntax for single-item arrays - File imports -
@"path"and@@"path"syntax - File fetching - Read files with
fetch(),fetchAsync(), orfetchUrl() - 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 commentRepeated 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 asparse()
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 contentallowedHosts- Restrict fetching to these hostnames (prevents SSRF)maxDepth,maxArraySize,maxImportDepth- Same asparse()
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 ParseErrorPath 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 testsUsing 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 slimgymNo 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 runNote: The lockfile (
bun.lockb) is Bun-specific. When using npm/pnpm/yarn, a new lockfile will be generated for your package manager.
License
MIT
