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

@longsightgroup/qti3-writer

v0.9.8

Published

Framework-neutral QTI 3 assessment item XML writer for authoring applications.

Readme

@longsightgroup/qti3-writer

Framework-neutral QTI 3 assessment item XML writer for authoring applications.

This package writes QTI-shaped authoring primitives to QTI 3 XML. It does not render UI or sanitize HTML. Trusted XHTML/QTI fragments must be prepared by the calling application before they are passed to the writer.

Install

npm install @longsightgroup/qti3-writer

Use

import { qti3TrustedXmlFragment, writeQti3AssessmentItemResult } from "@longsightgroup/qti3-writer";

const result = writeQti3AssessmentItemResult({
  interactionType: "choice",
  identifier: "item-1",
  title: "Choice item",
  promptHtml: qti3TrustedXmlFragment("Choose one."),
  responseCardinality: "single",
  choices: [
    { identifier: "A", text: "Alpha" },
    { identifier: "B", text: "Beta" },
  ],
  correctResponse: ["B"],
});

if (!result.ok) {
  throw new Error(result.diagnostics.map((diagnostic) => diagnostic.message).join("; "));
}

console.log(result.xml);

The stable application-facing API is writeQti3AssessmentItemResult(item). It returns typed diagnostics and should be used by production authoring systems. Use validateQti3AuthoringItem(item) when a UI or import pipeline needs diagnostics before writing XML.

writeQti3AssessmentItem(item) remains available as a convenience API for scripts and tests. It throws Qti3WriterError when writer invariants fail.

Direct builders do not require the redundant interactionType discriminant:

import { buildQti3TextEntryItem, qti3TrustedXmlFragment } from "@longsightgroup/qti3-writer";

const xml = buildQti3TextEntryItem({
  identifier: "item-2",
  title: "Text entry",
  bodyHtml: qti3TrustedXmlFragment(
    '<p>Answer: <qti-text-entry-interaction response-identifier="RESPONSE"/></p>',
  ),
  responses: [{ responseIdentifier: "RESPONSE", answers: [{ value: "deno" }] }],
});

Packages

The package writer emits item-bank QTI 3 packages as a manifest, file map, or deterministic ZIP:

import { writeQti3PackageZipResult } from "@longsightgroup/qti3-writer";

const result = writeQti3PackageZipResult({
  identifier: "package-1",
  title: "Example package",
  items: [
    {
      kind: "authoringItem",
      path: "items/item-1.xml",
      assets: [
        {
          path: "items/assets/prompt.png",
          data: new Uint8Array([137, 80, 78, 71]),
        },
      ],
      item: {
        interactionType: "choice",
        identifier: "item-1",
        title: "Choice item",
        responseCardinality: "single",
        choices: [
          { identifier: "A", text: "Alpha" },
          { identifier: "B", text: "Beta" },
        ],
        correctResponse: ["B"],
      },
    },
  ],
});

if (result.ok) {
  console.log(result.zip);
}

Use writeQti3PackageManifestResult(input) for only imsmanifest.xml, writeQti3PackageFilesResult(input) for an ordered file list containing imsmanifest.xml, item XML, and item-owned assets, and writeQti3PackageZipResult(input) for deterministic ZIP bytes. Package result APIs validate item XML through @longsightgroup/qti3-core and return typed diagnostics. Convenience APIs without Result throw Qti3WriterError.

Package writing currently targets item-bank packages. Assets are explicit: declare each asset on the item that owns the manifest resource. The writer does not rewrite URLs inside trusted item XML. @longsightgroup/qti3-core infers asset media types from file extensions when packages are parsed.

Trusted Fragments

bodyHtml, promptHtml, and rich choice content use Qti3TrustedXmlFragment. The writer escapes plain text fields and XML attributes, but it assembles trusted fragments as provided. Sanitization is the calling application's responsibility.

Support Matrix

The exported qti3WriterInteractionSupport array is the authoritative support matrix for interactions the writer can currently write and validate:

| Interaction | QTI element | Writer support | | ----------------- | ----------------------------------- | ---------------------------------------------------------------------- | | Choice | qti-choice-interaction | Writes and validates | | Order | qti-order-interaction | Writes and validates ordered cardinality and choice references | | Inline choice | qti-inline-choice-interaction | Replaces empty QTI placeholders and validates slot references | | Hottext | qti-hottext-interaction | Replaces empty QTI placeholders and validates choice references | | Gap match | qti-gap-match-interaction | Writes and validates gap choices, targets, and directed pairs | | Extended text | qti-extended-text-interaction | Writes constructed-response interactions and rubric blocks | | Upload | qti-upload-interaction | Writes file response declarations and application upload metadata | | Media | qti-media-interaction | Writes audio, video, and object media with playback metadata | | Associate | qti-associate-interaction | Writes and validates pair responses and associable choices | | Text entry | qti-text-entry-interaction | Writes declarations and validates trusted body interaction references | | Match | qti-match-interaction | Writes and validates | | Hotspot | qti-hotspot-interaction | Writes and validates accessible object metadata and references | | Graphic order | qti-graphic-order-interaction | Writes and validates object metadata, hotspots, and ordered responses | | Select point | qti-select-point-interaction | Writes and validates point responses, area mappings, and object data | | Position object | qti-position-object-interaction | Writes and validates stage/movable objects, point responses, targets | | Slider | qti-slider-interaction | Writes and validates numeric bounds, responses, mappings, presentation | | Custom | qti-custom-interaction | Writes trusted legacy custom markup and response processing fragments | | Portable custom | qti-portable-custom-interaction | Writes launch metadata, modules, trusted markup, response processing | | Drawing | qti-drawing-interaction | Writes file responses with accessible canvas object metadata | | End attempt | qti-end-attempt-interaction | Writes boolean end-attempt responses and button metadata | | Graphic associate | qti-graphic-associate-interaction | Writes and validates object metadata, hotspots, and pair responses | | Graphic gap match | qti-graphic-gap-match-interaction | Writes and validates hotspot and inline-gap target modes |

The writer test suite round-trips every supported builder through @longsightgroup/qti3-core parsing and validateAssessmentItem() with zero diagnostics.

For order items, an explicit correctOrder must include every choice by default. Partial correct orders are accepted only when minChoices or maxChoices explicitly configures subset ordering.

Inline choice items use trusted bodyHtml with empty QTI-shaped placeholders. The writer replaces each <qti-inline-choice-interaction response-identifier="..."/> placeholder with the generated interaction for the matching slot. This keeps editor markers out of the public API while still letting applications control the surrounding inline prose. Default all_or_nothing scoring writes the slot count as the score when every inline choice is correct, so a two-slot item awards SCORE = 2. Use map_response scoring when the application needs per-slot partial credit or custom normalization.

Hottext items use trusted bodyHtml inside the generated qti-hottext-interaction. Each generated hottext choice is placed by an empty <qti-hottext identifier="..."/> placeholder in that body fragment. When maxChoices is omitted, the writer emits cardinality="multiple"; set maxChoices: 1 for single-select hottext items.

Gap match items use trusted bodyHtml inside the generated qti-gap-match-interaction. Targets are QTI-shaped <qti-gap identifier="..."/> elements in that body fragment and must match the structured targets list. Gap styling, including input-width classes, belongs on those trusted qti-gap elements. Each gap target can have at most one associated choice; repeated target identifiers in correctResponse are rejected. The writer supports text and image gap choices and defaults to map_response scoring for authoring systems that need per-pair partial credit.

Extended text items write a single qti-extended-text-interaction after optional trusted bodyHtml. The writer supports prompt, rubric, expected length/lines, min/max strings, placeholder text, pattern-mask attributes, format="plain", format="preformatted", and format="xhtml". qti3-core validates extended text response declarations as cardinality="single" and base-type="string", so the writer reports diagnostics for other response shapes.

Upload items write a single qti-upload-interaction with a cardinality="single" / base-type="file" response declaration. Application constraints such as maximum file size, allowed file types, and multiple-file UI behavior are emitted as data-max-size, data-file-types, and data-multiple attributes for runtimes that understand them. When correctResponse is provided, the writer emits a correct-response filename and the match_correct response-processing template; otherwise upload items remain manually scored/unscored.

Media items write a single qti-media-interaction with a cardinality="single" / base-type="integer" response declaration for play count. The writer supports audio, video, and object media sources, video captions, transcript companion materials, autostart/loop flags, min/max play bounds, coords, labels, dimensions, and media shared-vocabulary player controls.

Slider items write a single numeric qti-slider-interaction. The writer infers base-type as integer when bounds, step, correct response, and mapping keys are whole numbers; otherwise it uses float. Slider scoring defaults to map_response when mappings are present and match_correct when they are not.

Custom interaction items write deprecated legacy qti-custom-interaction items for applications that still need that QTI shape. The writer validates response declaration shape and XML attribute names, but it treats widget markup and custom response processing as trusted fragments supplied by the application. Portable custom items write qti-portable-custom-interaction launch metadata, optional qti-interaction-modules, and trusted qti-interaction-markup. The writer requires a custom interaction type identifier plus either a module attribute or at least one interaction module. Drawing items write a single qti-drawing-interaction with a cardinality="single" / base-type="file" response declaration and an accessible canvas object. End attempt items write a single qti-end-attempt-interaction with a cardinality="single" / base-type="boolean" response declaration and a required button title.

Graphic interactions render generated long-description markup immediately before the interaction element. Graphic order defaults correctOrder to the declared hotspot order when omitted. For graphic order items, an explicit correctOrder must include every hotspot by default. Partial correct orders are accepted only when minChoices or maxChoices explicitly configures subset ordering. Select point and position object write map_response_point scoring and require at least one area mapping target. When maxChoices is omitted, these point interactions emit cardinality="single"; set maxChoices above 1 for multi-point responses. For graphic gap match, trusted bodyHtml is interaction content so inline qti-gap targets can be parsed and validated by QTI engines.

Graphic gap match supports two target modes. Hotspot targets are generated as qti-associable-hotspot elements on the graphic. Inline targets are declared in targets with targetType: "inlineGap" and must have matching trusted bodyHtml containing qti-gap elements.