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

learn-content-engine

v0.22.0

Published

Framework-agnostic TypeScript engine that parses lesson content from pluggable sources into a canonical lesson object.

Downloads

3,336

Readme

learn-content-engine

npm version CI

Framework-agnostic TypeScript engine that parses and validates lesson content from pluggable sources into a canonical lesson object.

It takes raw content (a single-JSON lesson plus a manifest.yaml) and a set context, and produces canonical lesson / set-entry objects. It contains no network, storage, or UI code - you supply the bytes and keep fetch + persistence. The bundled, strict JSON-Schema makes it a self-contained format reference: you can author and validate lessons without the application the format originated in (Adaptive Learner). Tracks the lesson schema, currently v1.12.

Install

npm install learn-content-engine

ESM, ships TypeScript declarations, Node >= 18.

As a git dependency

For development loops against an unreleased revision, or when consuming a fork, install straight from GitHub, pinned to a commit or tag for reproducibility:

// package.json of the host app
{
  "dependencies": {
    "learn-content-engine": "github:astrapi69/learn-content-engine#<commit-or-tag>"
  }
}

dist/ is not committed, so on install npm runs the package's prepare script (npm run build) to compile dist/ (JS + .d.ts) from source in the checkout. No extra step is needed in the host - a plain npm install builds the engine.

Quick example

import { parseLesson, validateLesson, type LessonSetContext } from "learn-content-engine";

const context: LessonSetContext = {
  language: "fr", target_language: "fr", source_language: "en", domain: "language",
};
const raw = `{ "id": "01", "title": "Greetings", "steps": [
  { "id": "s1", "type": "exercise",
    "exercise": { "id": "e1", "type": "free_text", "prompt": "Say hello.", "accept": ["bonjour"] } }
] }`;

const lesson = parseLesson(raw, context);        // canonical ContentLesson (set context injected)
const result = validateLesson(JSON.parse(raw));  // explicit, opt-in validation
if (!result.valid) console.error(result.errors); // [{ path, message }, …]

Documentation

  • Getting started - install, the 5-minute pipeline example.
  • Concepts - the pipeline, context inheritance, the legacy alias, schema policy.
  • Lesson format reference - every field and exercise type, with tested examples.
  • Schema diagrams - four pictures of the format; the two schema-derived ones are generated and drift-gated.
  • Authoring patterns - expressing common exercise ideas (true/false, conjugation, synonyms, collocations, word order) with the existing types.
  • Validation - the strict schema, the semantic rules, the error model.
  • Extensions - opt-in ext: exercise types, the portability contract, the registry.
  • QTI interop - the optional QTI 2.x import/export adapter, mapping table, fidelity limits.
  • Architecture - the engine boundary, consumer parity, roadmap.
  • API reference - generated TypeDoc for the core and /qti entry points.
  • Contributing - TDD workflow, release gate, adding an exercise type.
  • Security policy - supported versions, private vulnerability reports.
  • Code of conduct - Contributor Covenant 2.1.

Public API

| Export | Kind | Purpose | |---|---|---| | parseLesson | fn | raw source + context → canonical ContentLesson (via an adapter) | | singleJsonLessonAdapter | fn | the built-in single-JSON source adapter | | parseManifest | fn | raw manifest.yaml text → ParsedManifest | | asContentSetEntry | fn | raw parsed set → canonical ContentSetEntry | | resolveLanguagePair | fn | language-pair resolution (legacy alias + en default) | | setBasePath | fn | repo-relative base dir for a set | | asContentSetBook | fn | project a manifest book block → ContentSetBook \| null | | validateLesson | fn | validate a lesson against the bundled schema + semantic rules → ValidationResult | | validateManifest | fn | validate a manifest against the bundled schema (legacy alias normalized) | | collectStableIds | fn | set-wide stable_id view over several lessons: total count + duplicates with locations (the cross-lesson half the schema cannot see) | | buildStableIdInventory, compareStableIdInventories, formatStabilityResult | fn | the pure core of the shipped check-stable-ids gate: published-state vs head, violations V1-V6 (V5/V6 read each tree's declared retired_ids, engine#131) | | computeStableIdCoverage, gateStableIdCoverage, formatCoverageResult | fn | the pure core of the shipped check-stable-id-coverage gate: how many listed sets are fully minted, judged against the repo-local baseline | | isBaseCredible | fn | whether a comparison base is a plausible predecessor: an empty history is a broken run, not a clean one (the floor under the stability gate) | | lessonIdOrderingIssues | fn | set-level ordering check over a set's lesson ids: mixed NN- prefixes, inconsistent widths, lexicographic-vs-numeric divergence (validateManifest runs it over metadata.lessons) | | KNOWN_CONTENT_DOMAINS | const | the canonical domain vocabulary of the known-values-plus-other contract (engine#127); consumers group their subject facet on it instead of keeping a copy | | CEFR_LEVELS | const | the CEFR proficiency bands (A1..C2) a language set declares as its level | | LEVEL_NONE | const | the explicit no-level sentinel ("none") a deliberately level-less non-language set declares (engine#127) | | isKnownContentDomain | fn | whether a domain value is in the canonical vocabulary (case-insensitive; absent counts as the language default) - the W-DOMAIN-UNKNOWN lint's predicate | | isKnownLevel | fn | whether a level is a CEFR band or, for a non-language set, the none sentinel - the W-LEVEL-UNKNOWN lint's predicate | | ContentLesson, ContentSetEntry, ContentSetBook, ContentSetSource, … | types | the canonical internal format | | ContentLessonInlineExample | type | one inline worked example (schema v1.5) on a theory step or exercise | | SetReviewStatus, SetAttribution | types | the set entry's review standing (schema v1.9) and attribution block | | StableIdReport, StableIdDuplicate | types | the collectStableIds return shape | | ValidationResult, ValidationIssue | types | the validate* return shape ({ valid, errors[] }) | | LessonSetContext, LessonSourceAdapter, ParsedManifest, ParsedSet | types | adapter + manifest surface |

The bundled JSON-Schema ships too, so a content repo can mirror against it directly: import schema from "learn-content-engine/schema/lesson.schema.json".

Scope

By design, this package contains only parse / transform / validate / types + the single-JSON source adapter - no fetch, storage, or UI; those stay in the consumer. See architecture.md. The adaptive-learner app consumes this library (pinned in its frontend/package.json) as the reference consumer, so parse/validate/types live here once. As of v0.6.0 this engine is the canonical source of the lesson schema (schema authority moved here, roadmap stage 4); consumers - adaptive-learner and the content repos - mirror it. See Schema authority.

What this is NOT

This is a language-learning-shaped lesson engine: the format is built around cards, drill-style exercise types, and a target/source language pair (see concepts.md). The shape carries more than languages, though - a domain field (language, programming, psychology, ...; known values plus other, see content domains) lets the same format hold knowledge-domain sets (tech courses, driving-test prep, dog training in the dedicated domain repos below); there, target_language is simply the language the content is written in. It is deliberately not:

  • a general assessment standard - the CORE schema covers the exercise types its consumers render and grows additively when a consumer needs a new core type. A consumer needing a bespoke type can add one via the opt-in extension tier (ext: types) without touching the core enum; the core stays the portable authority.
  • a runtime - no rendering, grading, scheduling/SRS, persistence, or networking; consumers own all of that.
  • a content repository - it ships the format, the validator, and the author tooling, not lessons.

Example repositories

The engine is used in the wild - these repos show the full consumer setup (pinned engine version, byte-mirrored schema artifacts, make lint running the same validator locally that CI enforces):

Schema authority

The canonical source of the lesson schema is this engine's schema/lesson.schema.json (+ content-manifest.schema.json, quality-rules.json). It is an authored artifact here; consumers mirror the schema shipped in each pinned engine release:

  • adaptive-learner (the reference consumer) keeps its Pydantic models as an editorial tool for its own runtime types, but its generated schema must be byte-identical to the engine's - its parity gate treats the engine as the reference (the consumer conforms to the engine, not the reverse).
  • Content repos mirror the schema from the pinned engine release via their drift tool (schema/engine-version.txt).

The lesson schema's $id is engine-owned: https://astrapi69.github.io/learn-content-engine/schema/lesson.schema.json.

To evolve the schema, edit the artifact here (the frozen byte baseline in src/schema-baseline.test.ts guards against accidental content drift), run make sync-types to regenerate src/types/lesson-schema.generated.ts from it, mirror any new cross-field rule in src/validate.ts, extend the fixtures + rule catalog, and bump the version; consumers then re-pin. The TypeScript types are generated here (in-engine, scripts/generate-lesson-types.mjs), so they cannot drift from the schema; the drift gate runs in release-check + CI.

Changelog

See CHANGELOG.md - one dated section per release, from the current release back to 0.1.0.

License

MIT © Asterios Raptis