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

@gymbile/wpl-ai

v1.13.0

Published

WPL-AI compiler: parse and compile WPL-AI DSL into WPL JSON

Downloads

2,218

Readme

@gymbile/wpl-ai

npm version CI License

Compiler for WPL-AI — a human/LLM-friendly DSL that compiles to canonical WPL (Wellness Plan Language) JSON.

WPL-AI is the textual authoring format. WPL JSON is the runtime format. This package bridges the two.

WPL-AI source ──[tokenize]──▶ Tokens ──[parse]──▶ AST ──[compile]──▶ WPL JSON
                                                                       │
                                                       ┌───────────────┴─────────────┐
                                                       ▼                             ▼
                                              [@gymbile/wpl-validator]      [your runtime]
                                              schema + semantic checks      (training app, ...)

Where it fits in the WPL ecosystem

| Repo / Package | Role | |---|---| | gymbile/wpl | Canonical JSON Schema, spec, conformance suite (source of truth) | | @gymbile/wpl-ai (this repo) | DSL → WPL JSON compiler (textual authoring) | | @gymbile/wpl-validator | Reference TypeScript validator (schema + semantic invariants) | | wpl.dev | Marketing site + interactive playground (consumes both) |

@gymbile/wpl-ai depends on @gymbile/wpl-validator to validate every successful compile output against the canonical schema and semantic rules.


Installation

npm install @gymbile/wpl-ai

Requires Node ≥18. Ships dual ESM + CJS builds with TypeScript declarations.

// ESM
import { compileWplAi } from "@gymbile/wpl-ai";

// CommonJS
const { compileWplAi } = require("@gymbile/wpl-ai");

Quick start

import { compileWplAi } from "@gymbile/wpl-ai";

const source = `
PLAN "Simple Upper Body"
TYPE workout
VISIBILITY public

GOALS
  GOAL primary strength:
    name "Build Upper Body Strength"

PHASES
  PHASE "Single Session" (1 weeks):
    WEEK 1:
      DAY Monday training 35m "Upper Body Focus":
        warmup:
          arm_circles 2m
        main straight_sets:
          push_up 3x8..12 rpe 7 rest 60 seconds
        cooldown:
          chest_stretch 30s x2 sides both
`;

const result = compileWplAi(source);

if (result.ok) {
  console.log("Plan:", result.json);
  console.log("Schema/semantic valid:", result.validation.valid);
  if (!result.validation.valid) {
    for (const err of result.validation.errors) {
      console.warn(`[${err.code}] ${err.path}: ${err.message}`);
    }
  }
} else {
  console.error(result.formatted); // Pretty-printed compile errors
}

Public API

compileWplAi(source: string): CompileResult

The main entry point. Runs the full pipeline (lex → parse → compile → semantic checks → schema/semantic validation) and returns a discriminated-union result.

type CompileResult =
  | {
      ok: true;
      json: Record<string, unknown>;        // Compiled WPL JSON
      ast: Document;                        // Parser AST (useful for tooling)
      warnings: SemanticWarning[];          // DSL-level vocabulary/consistency warnings
      validation: ValidationResult;         // Schema + semantic findings (re-exported from wpl-validator)
      pointerMap: PointerSourceMap;         // JSONPointer → SourceRange (see below)
    }
  | {
      ok: false;
      errors: WplError[];                   // Lex/parse/compile errors with source positions
      formatted: string;                    // Human-readable error report (plain text, not ANSI-colored)
      summary: string;                      // One-line summary, e.g. "3 errors: 2 lex_unexpected_char, 1 parse_unexpected_token"
    };

formatted is plain text — no ANSI escape codes — so it is safe to render in browser editors as-is. If you want colorized terminal output, wrap the per-error pieces yourself. summary is a single line counting errors by type, suitable for status bars or log lines.

Success vs. failure semantics:

  • ok: false means the source was malformed — couldn't lex, parse, or compile to JSON. No JSON output exists.
  • ok: true means the compile produced JSON. The JSON is then validated against the canonical schema; result.validation.valid indicates whether validation passed. A successful compile can still produce schema-invalid JSON (e.g., the user wrote a workout plan with no phases — that compiles but fails semantic validation).
  • warnings are DSL-level advisory findings (unknown vocabulary, plan-type/activity-type inconsistencies). They don't make a plan invalid.

Source position tracking

Every JSON node the compiler emits is recorded in pointerMap keyed by RFC 6901 JSON Pointer, mapping to a SourceRange of character offsets in the original DSL source.

type SourceRange = { from: number; to: number };
type PointerSourceMap = Map<string, SourceRange>;

const range = result.pointerMap.get("/plan/phases/0/weeks/0/days/0");
// → { from: 142, to: 318 }   (or undefined if synthetic / not from source)

This is what lets the wpl.dev playground highlight validator errors at the right characters in the editor: validator emits path: "/plan/phases/0/duration", the playground looks it up in the map, gets a range, and renders an inline squiggle.

Synthetic JSON nodes (e.g. $schema literal, default values) don't have a corresponding source range — those pointers are absent from the map. Consumers must tolerate undefined lookups.

Error handling helpers

import { formatErrors, formatError, errorSummary } from "@gymbile/wpl-ai";
  • formatErrors(errors, source?) — pretty-prints all errors with caret pointers + source context. Used in result.formatted.
  • formatError(error, source?) — formats a single error.
  • errorSummary(errors) — one-line count-by-type summary.

WplError is a union of three discriminated types (LexerError, ParseError, CompileError); each carries a Location with line/column/offset.

Lower-level access

For tooling that wants to operate on intermediate stages (e.g., custom transformers, IDE integrations):

import { tokenize, parse, compile } from "@gymbile/wpl-ai";

const lex = tokenize(source);                 // → { ok, tokens? } | { ok: false, errors }
const ast = parse(lex.tokens);                // → { ok, document? } | { ok: false, errors }
const out = compile(ast.document);            // → { ok, json, pointerMap } | { ok: false, errors }

The high-level compileWplAi is just a façade over these stages.

Validation re-exports

import {
  validate,                  // re-export from @gymbile/wpl-validator
  type ValidationResult,
  type ValidationError,
} from "@gymbile/wpl-ai";

// Validate already-compiled WPL JSON without re-compiling
const result = validate(plan, { catalog: { exercises: new Set([...]) } });

See @gymbile/wpl-validator for full validator API (rules, catalog support, error codes).

Vocabulary utilities

The DSL accepts well-known vocabularies for goals, exercises, equipment, etc. These are exposed as both arrays (for enumeration) and Sets (for fast membership checks):

import {
  GOAL_CATEGORIES,           // ['weight_loss', 'muscle_gain', 'endurance', ...]
  EXERCISE_CATEGORIES,
  CARDIO_MODALITIES,
  NUTRITION_CATEGORIES,
  MEDITATION_CATEGORIES,
  RECOVERY_CATEGORIES,
  HABIT_CATEGORIES,
  MUSCLE_GROUPS,
  EQUIPMENT,
  FITNESS_LEVELS,
  MEASUREMENT_METRICS,
  WEIGHT_UNITS,
  DISTANCE_UNITS,
  STREAK_TYPES,
} from "@gymbile/wpl-ai";

import { ALL_EXERCISES, isKnownExercise } from "@gymbile/wpl-ai";
import { suggest, bestMatch, validateExercise } from "@gymbile/wpl-ai";
import { validateVocabulary } from "@gymbile/wpl-ai";

isKnownExercise("push_up");                   // → true
suggest("pushp", { limit: 3 });               // → ['push_up', 'push_press', ...]
bestMatch("dummbell_curl");                   // → 'dumbbell_curl' (fuzzy)

validateVocabulary is what powers the DSL-level SemanticWarnings — it flags references to unknown vocabulary terms with suggestions for the closest match.


DSL by example

The DSL is whitespace-significant and case-sensitive on keywords (uppercase) but tolerant on values.

Minimal plan

PLAN "Minimal"
TYPE workout
VISIBILITY private

PHASES
  PHASE "P1" (1 weeks):
    WEEK 1:
      DAY Monday rest "Rest day":

Workout with sets/reps + RPE

DAY Monday training 45m "Upper Body":
  main straight_sets:
    push_up 3x8..12 rpe 7 rest 60 seconds
    dumbbell_row 3x10 weight 10 kg rest 60 seconds
    overhead_press 3x8..10 weight 8 kg rest 90 seconds
  • 3x8..12 — 3 sets of 8–12 reps (range)
  • rpe 7 — Rate of Perceived Exertion 7/10
  • weight 10 kg — load
  • rest 60 seconds — inter-set rest

Cardio

main:
  cardio running continuous 20 minutes intensity heart_rate_zone 3

Cardio supports continuous, intervals (with patterns), and timed prescriptions. Intensity types: heart_rate_zone, rpe, pace.

HIIT circuit with personalization

PERSONALIZATION
  RULES
    WHEN injury contains knee:
      replace jump_squat -> goblet_squat
    WHEN injury contains shoulder:
      exclude overhead_press

PHASES
  PHASE "HIIT Session" (1 weeks):
    WEEK 1:
      DAY Wednesday training 25m "Full Body HIIT":
        main circuit:
          rounds 4
          rest_between_rounds 90 seconds
          kettlebell_swing 3x12 rest 20 seconds
          jump_squat 3x10 rest 20 seconds
          burpee 3x8 rest 20 seconds

Holistic plans (workout + nutrition + meditation + recovery + habits)

PLAN "Holistic Wellness Week"
TYPE hybrid

PHASES
  PHASE "Week 1" (1 weeks):
    WEEK 1:
      DAY Monday training 60m "Strength + Mindfulness":
        main:
          push_up 3x10 rest 60 seconds
        meditation mindfulness 10 minutes:
          name "Post-Workout Calm"
        nutrition post_workout:
          name "Recovery Shake"
          timing after_workout 30 minutes
          macros protein 25..30 g, carbs 40 g
        recovery stretching 15 minutes:
          chest_stretch 30s x2 sides both
        habit water_intake:
          target 8 glasses
          frequency daily

Four full reference fixtures live in __tests__/fixtures.ts:

  • simple-upper-body — beginner workout
  • hiit-circuit-personalization — intermediate HIIT with personalization rules
  • holistic-wellness-week — full hybrid plan exercising every activity type
  • nutrition-with-timing — nutrition plan exercising meal-timing emission

These same fixtures are used as integration tests asserting that the compiled JSON validates cleanly against the canonical schema.


Pipeline architecture

┌──────────────────────────────────────────────────────────────────────────┐
│  src/lexer.ts          — DSL → Tokens                                    │
│                          Tracks line, column, offset on every token      │
│                                                                           │
│  src/parser.ts         — Tokens → AST (Document)                         │
│                          AST nodes carry SourceRange (range field)       │
│                          ~3500 lines covering full grammar               │
│                                                                           │
│  src/grammar.ts        — Single-source-of-truth keyword/enum tables     │
│                          (parser, lexer, vocabularies all reference this)│
│                                                                           │
│  src/compiler.ts       — AST → WPL JSON                                  │
│                          Threads a CompileContext through emission sites │
│                          for pointer-to-source-range tracking            │
│                                                                           │
│  src/compile-context.ts — `withSegment(seg, ast, fn)` records the       │
│                           current JSON pointer + AST source range as the │
│                           compiler descends, then pops on exit.          │
│                                                                           │
│  src/validator.ts      — DSL-level semantic warnings                     │
│                          (plan-type vs. activity-type consistency, etc.) │
│                                                                           │
│  src/vocabularies.ts   — Canonical vocabulary lists (goal categories,   │
│                          muscle groups, equipment, etc.)                 │
│  src/vocabulary-matcher.ts — Fuzzy-match unknown terms to nearest known │
│  src/exercise-matcher.ts   — Same for exercises specifically            │
│  src/exercises.ts          — ALL_EXERCISES catalog                      │
│                                                                           │
│  src/index.ts          — Public façade                                   │
└──────────────────────────────────────────────────────────────────────────┘

All AST node types live in src/types.ts (~70 exported interfaces/types). The error hierarchy is in src/errors.ts.


Two layers of validation

There are two distinct validation steps. Both run on every compile:

1. Compile-time semantic warnings (this package)

validateSemantics() walks the AST and emits SemanticWarning[] for:

  • Unknown vocabulary terms (with closest-match suggestions)
  • Activity types that don't fit the plan type (e.g., a workout-typed plan with only nutrition activities)
  • Unrecognised exercise refs
  • Other DSL-level coherence checks

These are advisory — they never make a compile fail. Surfaced in the playground as yellow squiggles.

2. Schema + semantic validation (@gymbile/wpl-validator)

After compile produces JSON, the validator runs two passes against the canonical schema:

  • Pass 1 — JSON Schema (Draft 2020-12) shape check via ajv
  • Pass 2 — semantic invariants (DUPLICATE_ID, UNRESOLVED_REF, INVALID_PRESCRIPTION, etc.)

Findings are returned as result.validation: ValidationResult. Severity matters: error-severity findings make result.validation.valid === false; warning-severity findings (e.g., PHASE_DURATION_MISMATCH) don't.

See validator error codes for the full list.


Development

git clone https://github.com/gymbile/wpl-ai.git
cd wpl-ai
npm install
npm test          # 942+ tests across lexer, parser, compiler, validator, integration
npm run typecheck # tsc --noEmit
npm run build     # tsup → dist/ (ESM + CJS + .d.ts + .d.cts)

Test layout:

| File | Coverage | |---|---| | __tests__/lexer.test.ts | 292 tests — every token type, edge cases, error positions | | __tests__/parser.test.ts | 140 tests — every grammar production | | __tests__/grammar.test.ts | Grammar table consistency | | __tests__/compiler.test.ts | 172 tests — compiler emission shapes | | __tests__/compile-context.test.ts | Pointer-tracking contract | | __tests__/validator.test.ts | DSL-level semantic warnings | | __tests__/vocabularies.test.ts | Vocabulary table integrity | | __tests__/exercise-matcher.test.ts | Fuzzy matching, suggestions | | __tests__/integration.test.ts | 107 end-to-end DSL → JSON tests | | __tests__/compileWplAi-validation.test.ts | Compiled output validates against @gymbile/wpl-validator | | __tests__/pointer-map.test.ts | Source ranges resolve to expected DSL substrings |


Versioning & releases

| Version | Highlights | |---|---| | 1.1.1 | Fix NutritionTiming compiler emission (relative/absolute shape per schema); lex before_workout/after_workout keywords | | 1.1.0 | Depend on @gymbile/wpl-validator; expose pointerMap on CompileResult; compiler emits schema-valid Activity shapes (prescription, name, target nesting); drops in-package schema-validator.ts | | 1.0.0 | Initial extraction from wpl.dev — full lexer/parser/compiler/DSL-semantic validator |

See CHANGELOG.md for full release history.

Releases are tagged on the GitHub repo and published to npm on tag push. Pin to a specific version (@gymbile/[email protected]) for reproducibility.


Conformance

The compile-conformance corpus lives in the sibling gymbile/wpl repo at conformance/compile/fixtures/. The runner at __tests__/conformance.test.ts discovers it automatically via a relative path (../../wpl/conformance/compile/fixtures/) — check out wpl/ next to wpl-ai/ on disk and it works with no configuration. Set WPL_CORPUS_DIR to override the path (useful for CI that checks out repos at arbitrary locations). Run with npm test -- conformance. When the corpus is absent, the suite skips cleanly and exits 0.


Stability

  • Public API surface: compileWplAi and the types it returns are stable. Re-exports of validator types follow @gymbile/wpl-validator's semver.
  • Lower-level exports (tokenize, parse, compile, AST types in Document): semi-stable. Used by the wpl.dev playground; we won't break them lightly, but they're not as battle-tested as the high-level façade.
  • Vocabulary tables can grow additively in minor releases. New categories don't break consumers, but ID strings won't be renamed within a major.
  • DSL grammar is additive only within a major. New keywords/syntax may be added in minors; existing valid inputs continue to compile.
  • Output JSON shape follows the canonical WPL schema. Schema changes are coordinated through gymbile/wpl releases.

License

Apache-2.0. Patent grant included.

"WPL" and "Wellness Plan Language" are trademarks of Gymbile. The compiler is open under the license above; implementations may declare conformance ("WPL-compatible") but may not be named "WPL" or imply endorsement by Gymbile. Forks must rename. See the schema repo's trademark policy for details.


Related