learn-content-engine - v0.23.0
    Preparing search index...

    learn-content-engine - v0.23.0

    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.13.

    npm install learn-content-engine
    

    ESM, ships TypeScript declarations, Node >= 18.

    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.

    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 }, …]
    • 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.
    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
    ContentLessonCard, ContentLessonExercise, ContentLessonStep, ContentLessonResource, ContentLessonClozeBlank, ContentLessonCardTokenRole, ContentLessonCardTokenRoleName types the rest of the ContentLesson field family (cards, exercises, steps, cloze blanks, token roles)
    ContentCardMediaType, ContentExerciseDirection, SetStatus, SetVisibility types enum-like fields on the canonical types above
    SetReviewStatus, SetAttribution types the set entry's review standing (schema v1.9) and attribution block
    StableIdReport, StableIdDuplicate types the collectStableIds return shape
    StabilityResult, StabilityViolation, StableIdElement, StableIdInventory types the buildStableIdInventory/compareStableIdInventories return + input shapes
    StableIdCoverage, CoverageVerdict, CoverageSet, CoverageFailure types the computeStableIdCoverage return shape
    ValidationResult, ValidationIssue, ValidationSeverity types the validate* return shape ({ valid, errors[] }) and its issue-severity enum
    ExerciseExtension, ExtensionRegistry types the ext: extension registry contract (schema 1.7, see Extensions)
    LessonSetContext, LessonSourceAdapter, ParsedManifest, ParsedSet, ParsedSetAsset, ParsedSetBook types adapter + manifest surface
    Card, CardTokenRole, ClozeBlank, Direction, Exercise, ExerciseType, InlineExample, Lesson, LessonResource, LessonStep, MediaType, Pair, PictureImage, StepType, TokenRole types the underlying generated schema element types (one per schema/lesson.schema.json $defs entry ContentLesson* wraps)

    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".

    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.

    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.

    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):

    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.

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

    MIT © Asterios Raptis