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

hapleroo-quizzard-core

v0.1.1

Published

Framework-agnostic quiz engine

Readme

hapleroo-quizzard-core

Framework-agnostic quiz engine for Hapleroo Quizzard — validation, state machine, scoring, and typed events. Use it standalone or with the React adapter.

npm version


Installation

pnpm add hapleroo-quizzard-core
npm install hapleroo-quizzard-core

For the React UI, install hapleroo-quizzard instead (it includes this package as a dependency).


Quick start

import { createQuizEngine } from "hapleroo-quizzard-core";
import type { QuizDefinition } from "hapleroo-quizzard-core";

const quiz: QuizDefinition = {
  id: "my-quiz",
  title: "JavaScript Basics",
  questions: [
    {
      id: "q1",
      type: "single-choice",
      question: "Which keyword declares a block-scoped variable?",
      options: [
        { id: "a", label: "var" },
        { id: "b", label: "let" },
        { id: "c", label: "function" },
      ],
      correctAnswer: "b",
    },
  ],
};

const engine = createQuizEngine(quiz, {
  allowBackNavigation: true,
  shuffleOptions: false,
});

engine.subscribeToState((state) => {
  console.log("Status:", state.status, "Index:", state.currentIndex);
});

engine.subscribe((event) => {
  console.log(event.type, event.payload);
});

engine.start();
engine.answer("q1", "b");
engine.next();
const result = engine.submit();

console.log(result.score, "/", result.maxScore, `(${result.percentage}%)`);

Quiz engine API

createQuizEngine(definition, config?, options?)

Creates a quiz engine instance. The definition is validated on creation.

Methods

| Method | Description | | --- | --- | | start() | Begin the quiz (applies shuffling if configured) | | answer(questionId, value) | Record an answer for a question | | next() | Navigate to the next question | | previous() | Navigate to the previous question | | goTo(index) | Jump to a question by index | | submit() | Submit the quiz and return the scored QuizResult | | restart() | Reset the quiz to idle state | | getState() | Get current QuizState | | getResult() | Get result after completion, or null | | getCurrentQuestion() | Get the active question object | | canGoNext() | Whether next navigation is allowed | | canGoPrevious() | Whether back navigation is allowed | | canSubmit() | Whether submit is allowed | | isAnswered(questionId) | Whether a question has an answer | | subscribe(listener) | Subscribe to QuizEvent emissions | | subscribeToState(listener) | Subscribe to state changes |


Quiz definition

interface QuizDefinition {
  id: string;
  title: string;
  description?: string;
  questions: Question[];
}

Question types

Single-choice — one correct option:

{
  id: "q1",
  type: "single-choice",
  question: "What is 2 + 2?",
  options: [
    { id: "a", label: "3" },
    { id: "b", label: "4" },
    { id: "c", label: "5" },
  ],
  correctAnswer: "b",
}

Multiple-choice — exact set match required:

{
  id: "q2",
  type: "multiple-choice",
  question: "Select all vowels:",
  options: [
    { id: "a", label: "a" },
    { id: "b", label: "b" },
    { id: "c", label: "e" },
    { id: "d", label: "z" },
  ],
  correctAnswers: ["a", "c"],
}

Configuration

| Option | Type | Default | Description | | --- | --- | --- | --- | | shuffleQuestions | boolean | false | Randomize question order on start | | shuffleOptions | boolean | false | Randomize option order per question | | allowBackNavigation | boolean | true | Allow navigating to previous questions | | requireAnswerToProceed | boolean | true | Block next until current question is answered | | showPercentage | boolean | true | Include percentage in results | | pointsPerQuestion | number | 1 | Points per correct question |

import { DEFAULT_QUIZ_CONFIG, resolveQuizConfig } from "hapleroo-quizzard-core";

const config = resolveQuizConfig({ shuffleQuestions: true });

Validation

Validate quiz definitions and config before runtime:

import { validateQuizDefinition, validateQuizConfig } from "hapleroo-quizzard-core";

const quizResult = validateQuizDefinition(maybeQuiz);

if (!quizResult.success) {
  for (const issue of quizResult.issues) {
    console.error(issue.path, issue.message);
  }
} else {
  const quiz = quizResult.data;
}

const configResult = validateQuizConfig({ pointsPerQuestion: 2 });

Throw on invalid input:

import { assertValidQuizDefinition } from "hapleroo-quizzard-core";

assertValidQuizDefinition(maybeQuiz); // throws QuizValidationError

Scoring

import {
  evaluateQuestion,
  evaluateAllQuestions,
  calculateScore,
  buildQuizResult,
} from "hapleroo-quizzard-core";

const answers = { q1: "b", q2: ["a", "c"] };
const questionResults = evaluateAllQuestions(quiz, answers, 1);
const summary = calculateScore(questionResults);
const result = buildQuizResult({
  quizId: quiz.id,
  questionResults,
  startedAt: Date.now() - 60000,
  completedAt: Date.now(),
});

QuizResult shape

interface QuizResult {
  quizId: string;
  score: number;
  maxScore: number;
  percentage: number;
  questionResults: QuestionResult[];
  startedAt: number;
  completedAt: number;
  durationMs: number;
}

Events

Subscribe via engine.subscribe():

| Event | Description | | --- | --- | | quiz:started | Quiz session began | | quiz:restarted | Quiz was reset | | question:viewed | User navigated to a question | | answer:selected | User selected or changed an answer | | question:answered | Question has a complete answer | | navigation:next | Moved to next question | | navigation:previous | Moved to previous question | | quiz:submitted | User submitted the quiz | | quiz:completed | Quiz finished with result in payload |


Main exports

// Engine
export { createQuizEngine } from "hapleroo-quizzard-core";

// Validation
export {
  validateQuizDefinition,
  validateQuizConfig,
  assertValidQuizDefinition,
  assertValidQuizConfig,
  QuizValidationError,
} from "hapleroo-quizzard-core";

// Evaluation & scoring
export {
  evaluateQuestion,
  calculateScore,
  evaluateAllQuestions,
  buildQuizResult,
} from "hapleroo-quizzard-core";

// Types
export type {
  QuizDefinition,
  QuizConfig,
  QuizState,
  QuizResult,
  QuizEvent,
  Question,
  AnswerValue,
} from "hapleroo-quizzard-core";

// Type guards
export {
  isSingleChoiceQuestion,
  isMultipleChoiceQuestion,
} from "hapleroo-quizzard-core";

React UI

For a drop-in <Quiz /> component and useQuizEngine hook, see hapleroo-quizzard.