@amritk/mjst
v0.11.0
Published
Generate TypeScript parsers and type definitions from JSON Schemas.
Maintainers
Readme
@amritk/mjst
Generate TypeScript parsers and type definitions from JSON Schemas.
Overview
@amritk/mjst is the command-line entry point for mjst. Point it at a JSON Schema and it produces TypeScript parsers, validators, and type definitions in the directory of your choice.
Options can be supplied via CLI flags or a JSON config file. CLI flags always take precedence over config file values.
It also carries a lint subcommand — mjst lint <files> — a format-agnostic JSON/YAML style-guide linter powered by @amritk/lint. See Linting.
Installation
npm install --save-dev @amritk/mjst
# or
pnpm add -D @amritk/mjst
# or
yarn add -D @amritk/mjst
# or
bun add -d @amritk/mjstThe package ships a mjst bin that runs under Node ≥ 20 (or Bun), so you can invoke it via npx mjst, pnpm dlx mjst, yarn dlx mjst, bunx mjst, or as a script in package.json.
Usage
CLI
npx mjst --schema ./schema.json --out-dir ./generated[!NOTE] Every flag accepts both kebab-case and camelCase, so
--out-dirand--outDirare equivalent.
Single file
Pass --out-file instead of --out-dir to concatenate every generated definition into one
self-contained file. This is currently supported together with --types-only:
npx mjst --schema ./schema.json --out-file ./generated/schema.ts --types-onlyRecursive — a whole folder of schemas
Point --schema-dir at a directory of JSON Schemas and mjst generates parsers for every
*.json file it finds, mirroring the directory layout under --out-dir:
npx mjst --schema-dir ./schemas --out-dir ./generatedschemas/ generated/
user.json ─▶ user/
api/ user.ts, index.ts
order.json ─▶ api/order/
order.ts, index.ts
_helpers/ ← shared runtime helpers (embedded mode)Each schema lands in its own subdirectory so generated files never collide, and in embedded
mode the runtime helpers are emitted once into outDir/_helpers/ — every nested parser
imports from that single shared location. --build compiles the whole tree in place. Each
schema's root type is named after the schema (its title, else its filename in PascalCase),
so user.json yields User / parseUser and api/order.json yields Order / parseOrder.
Running the generated output
By default mjst emits relative imports with the literal .ts extension (e.g.
import { parseUser } from './user.ts'), so the generated sources run as-is — no build
step — under:
- Bun (
bun run generated/index.ts) - Node ≥ 22.6 type stripping (
node generated/index.ts; unflagged from 22.18/23, else pass--experimental-strip-types) - tsc, provided your
tsconfig.jsonsets"allowImportingTsExtensions": true
If you'd rather compile the output first, pass --build (mjst switches the imports to the
NodeNext ./user.js form and runs tsc for you), or generate with --import-ext js to emit
the compile-ready form without building. --import-ext ts together with --build is rejected,
since tsc cannot emit from .ts specifiers.
[!NOTE] Breaking change (0.9.0): the root type is no longer always named
Document. It is derived from the schematitle, falling back to the schema filename (spec-plan.json→SpecPlan, givingparseSpecPlan/validateSpecPlanShape). Pass--root-type <Name>to override it for a single--schemarun. Update any imports that referencedDocument/parseDocument.
Config file
npx mjst --config ./mjst.config.json[!NOTE] Validate your config against the bundled JSON Schema:
config.schema.json
Linting
mjst lint lints JSON/YAML documents against a ruleset — JSON Schema validation and custom style rules, with exact line:column findings. It has its own flags (and its own --help), independent of the generation flags above.
# Lint files against an explicit ruleset
npx mjst lint "**/*.{yaml,json}" -r .lint.yaml
# Auto-discover a .lint.* ruleset by walking up from each file
npx mjst lint config/
# Lint piped input
cat service.yaml | npx mjst lint --stdin-filepath service.yamlFindings are printed as a compact, editor-clickable report:
config/service.yaml:2:7 error config-schema /port must be integer
✖ 1 problem(s) (1 error(s), 0 warning(s))With no -r, mjst discovers a .lint.{yaml,yml,json,js,mjs} ruleset by walking up from each linted file. A ruleset maps a JSONPath (given) to a function (then) — schema (JSON Schema), casing, pattern, truthy, and more, plus your own. The @amritk/lint README documents the ruleset format and built-in functions. For machine-readable output (JSON, SARIF, …), call the @amritk/lint library's lintDocument, which returns structured findings.
| Flag | Description |
| --- | --- |
| <documents..> | Files or globs to lint (positional). |
| -r, --ruleset | Path to a ruleset file. Defaults to .lint.* discovery. |
| -F, --fail-severity | Minimum severity that fails the run: error (default), warn, info, hint. |
| -D, --display-only-failures | Only show findings at or above --fail-severity. |
| --stdin-filepath | Path to associate with piped input (labels findings and enables ruleset discovery). |
| --concurrency | Max documents linted in parallel (default 8). |
| --no-resolve | Skip $ref dereferencing; rules see the raw document. |
| --resolve-remote | Fetch http(s) $refs while resolving (off by default; a lint run stays offline). |
| --allowed-hosts | Restrict remote $ref fetches to these hosts (implies --resolve-remote). |
| --allow-private-hosts | Permit remote $refs to private/loopback hosts (SSRF guard, off by default). |
| -q, --quiet | Suppress the findings report (the exit code still reflects findings). |
By default mjst lint dereferences $ref, $dynamicRef/$dynamicAnchor, and $recursiveRef/$recursiveAnchor before running rules, so rules with resolved: true (the ruleset default) see through references. Internal and local cross-file refs resolve from disk — a finding on a cross-file node reports that file's own line:column — while remote refs are only fetched when you opt in with --resolve-remote or --allowed-hosts.
The exit code is 0 when no finding reaches --fail-severity, 1 when one does, and 2 on a usage error. Run mjst lint --help for the full list.
Configuration reference
Config file examples
Minimal — generate parsers and types
{
"schema": "./schema.json",
"outDir": "./generated"
}Types only — skip parser functions
{
"schema": "./schema.json",
"outDir": "./generated",
"typesOnly": true
}Build — emit .js and .d.ts instead of .ts
{
"schema": "./schema.json",
"outDir": "./generated",
"build": true
}Log warnings — warn on unknown input properties
{
"schema": "./schema.json",
"outDir": "./generated",
"logWarnings": true
}Scripts
| Script | Command |
|:---|:---|
| bun run dev | bun run --conditions=development ./src/cli.ts |
| bun run start | bun run ./src/cli.ts |
| bun run build | bun run build:code && bun run build:types |
| bun run generate-readme | bun run generate-readme |
