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

@ueu/canvas-injector

v0.1.2

Published

Self-documenting Canvas course transformation library. Prototype IS the spec.

Readme

@hale9000/canvas-injector

Self-documenting Canvas course transformation library for Node.js and browsers.

The prototype IS the specification. The TypeScript code documents every transformation rule, API endpoint, and validation criterion.

Features

Markdown ↔ Canvas HTML - Round-trip conversion with template preservation ✅ Canvas API Client - Full REST API support (create/update courses, modules, assignments, pages, rubrics) ✅ Comprehensive Validators - Check points, rubrics, course structure ✅ Context-Agnostic - Works in Node.js, Chrome, Obsidian, VS Code, browsers ✅ Self-Documenting - Types and functions document the spec via their signatures ✅ TypeScript First - Full type safety, tree-shakeable, zero dependencies

Installation

npm install @hale9000/canvas-injector
# or
yarn add @hale9000/canvas-injector
# or
pnpm add @hale9000/canvas-injector

Quick Start

Transform Markdown to HTML

import { parseMarkdown, markdownToHtml } from '@hale9000/canvas-injector/transforms';

const markdown = `
---
title: "My Assignment"
points: 100
rubric:
  - criterion: "Theoretical Grounding"
    points: 50
    description: "Student demonstrates understanding of core concepts"
---

# Assignment Instructions

Write a 5-page essay...
`;

const parsed = parseMarkdown(markdown);
console.log(parsed.data.title);        // "My Assignment"
console.log(parsed.data.points);       // 100
console.log(parsed.data.rubric);       // Array of criteria

const html = markdownToHtml(parsed.body);
console.log(html);                     // "<h1>Assignment Instructions</h1>..."

Reverse-Extract Canvas HTML

import { reverseExtractFromCanvasHtml } from '@hale9000/canvas-injector/transforms';

const canvasHtml = `
<link rel="stylesheet" href="...UTC_Template2023_Mobile.css">
<div class="cbt-banner ...">
  <h1>Assignment Title</h1>
</div>
<div class="cbt-content">
  <p>Assignment instructions...</p>
</div>
`;

const result = reverseExtractFromCanvasHtml(canvasHtml);

if (result.success) {
  console.log(result.data?.markdown);  // YAML frontmatter + markdown body
  console.log(result.data?.analysis);  // Theme, CSS/JS URLs, metadata
}

Validate Course Structure

import { validateCourseStructure } from '@hale9000/canvas-injector/validators';

const assignments = [
  {
    id: 'assign_1',
    name: 'Assignment 1',
    points_possible: 100,
    rubric: { /* ... */ },
    // ...
  },
  // ... more assignments
];

const result = validateCourseStructure(assignments, 800);

console.log(result.isValid);     // true/false
console.log(result.errors);      // Array of errors
console.log(result.warnings);    // Array of warnings

Use Canvas API Client

import { CanvasApiClient } from '@hale9000/canvas-injector/api';

const client = new CanvasApiClient({
  baseUrl: 'https://university.instructure.com',
  token: 'your-api-token',
});

// Get course
const course = await client.getCourse(123);

// Create module
const module = await client.createModule(123, {
  name: 'Module 1',
  position: 1,
});

// Create assignment
const assignment = await client.createAssignment(123, {
  name: 'Assignment 1',
  points_possible: 100,
  description: '<p>Assignment description</p>',
  due_at: '2025-12-31T23:59:59Z',
  submission_types: ['online_text_entry', 'online_upload'],
  grading_type: 'points',
});

// Upload complete course
const result = await client.uploadCourseData(123, courseData);
console.log(result.created);   // { modules: 5, assignments: 13, pages: 2 }

With Progress Tracking

import { uploadCourseWithProgress } from '@hale9000/canvas-injector/api';

await uploadCourseWithProgress(
  client,
  123,
  courseData,
  (progress) => {
    console.log(`${progress.status} (${progress.current}/${progress.total})`);
    // Output: "Creating module: Module 1 (1/20)"
    //         "Creating assignment: Assignment 1 (5/20)"
    //         "Creating page: Syllabus (19/20)"
  },
);

Architecture

Types (./types)

Complete TypeScript definitions for:

  • Frontmatter & Metadata - YAML frontmatter structure, parsed markdown files
  • Rubric - Criterion definition, Canvas rubric objects
  • HTML Templates - Template formats, theme analysis
  • Canvas Objects - Courses, modules, assignments, pages, rubrics
  • Validation - Result types, point validation, rubric validation
  • API Client - Configuration, request/response types, context types

Read types as documentation: src/types/index.ts is the API contract.

Transforms (./transforms)

Transformation functions:

  • Parsing - parseMarkdown(), extractHtmlTemplate(), extractRubric()
  • Conversion - markdownToHtml(), htmlToMarkdown(), applyHtmlTemplate()
  • Analysis - analyzeCanvasHtml(), extractRubricFromHtml()
  • Reverse - reverseExtractFromCanvasHtml() (Canvas HTML → markdown)

Read functions as spec: Each function signature documents exactly what it transforms.

Validators (./validators)

Validation functions:

  • Points - validatePointsTotal(), validateAssignmentRubric()
  • Rubrics - validateRubric(), validateRubricCriterion()
  • Course - validateCourseMetadata(), validateModule(), validateCourseStructure()
  • Reports - generateValidationReport()

Read validators as spec: Each validator documents what it checks and why.

API Client (./api)

Canvas REST API client:

  • Courses - getCourse(), updateCourse()
  • Modules - getModules(), createModule()
  • Assignments - getAssignments(), createAssignment(), updateAssignment()
  • Pages - getPages(), createPage(), updatePage()
  • Rubrics - createRubric(), associateRubricWithAssignment()
  • Batch - uploadCourseData(), uploadCourseWithProgress(), exportCourseData()

Read API client as spec: Each method documents the Canvas REST API endpoint and data format.

Plugin Contexts

Chrome Extension

import { parseMarkdown, reverseExtractFromCanvasHtml } from '@hale9000/canvas-injector/transforms';

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'extractCanvasHtml') {
    const result = reverseExtractFromCanvasHtml(request.html);
    sendResponse({ success: result.success, markdown: result.data?.markdown });
  }
});

Obsidian Plugin

import { CanvasApiClient } from '@hale9000/canvas-injector/api';
import { parseMarkdown } from '@hale9000/canvas-injector/transforms';

export default class CanvasInjectorPlugin extends Plugin {
  async onload() {
    this.addCommand({
      id: 'inject-to-canvas',
      name: 'Inject current note to Canvas',
      callback: async () => {
        const file = this.app.workspace.getActiveFile();
        const content = await this.app.vault.read(file);
        const parsed = parseMarkdown(content);

        const client = new CanvasApiClient({
          baseUrl: this.settings.canvasUrl,
          token: this.settings.canvasToken,
        });

        // Use parsed.data for metadata, parsed.body for content
      },
    });
  }
}

VS Code Extension

import * as vscode from 'vscode';
import { validateCourseStructure } from '@hale9000/canvas-injector/validators';

export function activate(context: vscode.ExtensionContext) {
  vscode.commands.registerCommand('canvas-injector.validate', async () => {
    const editor = vscode.window.activeTextEditor;
    const content = editor?.document.getText();

    // Validate course structure
    const result = validateCourseStructure(assignments, 800);

    // Show in output channel
    const channel = vscode.window.createOutputChannel('Canvas Injector');
    channel.appendLine(generateValidationReport(result));
    channel.show();
  });
}

Node.js CLI

import { CanvasApiClient, uploadCourseWithProgress } from '@hale9000/canvas-injector/api';
import { parseMarkdown } from '@hale9000/canvas-injector/transforms';
import * as fs from 'fs';

async function main() {
  const client = new CanvasApiClient({
    baseUrl: process.env.CANVAS_URL!,
    token: process.env.CANVAS_TOKEN!,
  });

  const courseJson = JSON.parse(fs.readFileSync('.course.json', 'utf-8'));
  const result = await uploadCourseWithProgress(client, courseJson.canvas_id, courseData, (p) => {
    console.log(`${p.status} (${p.current}/${p.total})`);
  });
}

The Prototype IS The Spec

Instead of separate design documents, the library documents the specification via code:

Reading the Spec

  1. API Contract - Read src/types/index.ts

    • Every exported interface is a data structure
    • Field names and types document what data is required
    • Comments explain context (e.g., "NOT: vague / YES: specific")
  2. Transformation Rules - Read src/transforms/index.ts

    • Function signatures document input/output
    • Implementation shows exact transformation logic
    • Comments explain the reasoning (e.g., "Canvas Banner Template detection")
  3. Validation Rules - Read src/validators/index.ts

    • Function names document what is validated
    • Error/warning arrays document what can fail
    • Implementation shows exact validation criteria
  4. API Endpoints - Read src/api/index.ts

    • Method names document Canvas REST API operations
    • Parameters document Canvas API data format
    • Comments reference Canvas API documentation

Why This Approach?

  • Single Source of Truth - Code and spec are the same
  • Always Up-to-Date - No separate docs to maintain
  • Executable Spec - Code is tested and working
  • Clear Contracts - TypeScript enforces the spec at compile time
  • Self-Documenting - Function/type names clearly state intent

Validation Examples

Detect Vague Rubrics

import { validateRubricCriterion } from '@hale9000/canvas-injector/validators';

const criterion = {
  criterion: 'Clarity',
  points: 20,
  description: 'Student writes clearly', // ❌ Vague
};

const result = validateRubricCriterion(criterion);
// warnings: ["Criterion 'Clarity' uses vague language. Recommend NOT/YES examples instead."]

// Fixed:
const fixed = {
  criterion: 'Clarity',
  points: 20,
  description: `NOT: "text is hard to follow" | YES: "text flows logically with clear transitions"`,
};

const result2 = validateRubricCriterion(fixed);
// isValid: true

Points Validation

import { validatePointsTotal } from '@hale9000/canvas-injector/validators';

const assignments = [
  { name: 'Module 1', points_possible: 150 },
  { name: 'Module 2', points_possible: 150 },
  { name: 'Module 3', points_possible: 150 },
  { name: 'Module 4', points_possible: 150 },
  { name: 'Module 5', points_possible: 200 },
];

const result = validatePointsTotal(assignments, 800);
// isValid: true
// total: 800
// byModule: { module_1: 150, module_2: 150, ... }

Error Handling

All functions return result objects with .success, .data, .error, and .warnings:

const result = reverseExtractFromCanvasHtml(htmlContent);

if (!result.success) {
  console.error(`Failed: ${result.error}`);
  console.warn(result.warnings);
} else {
  console.log(result.data?.markdown);
}

Performance

  • No Runtime Dependencies - Only gray-matter and markdown-it for parsing
  • Tree-Shakeable - Import only what you need
  • Async/Await - All API calls are asynchronous
  • Streaming Ready - Can be extended for large file handling

TypeScript Support

Full TypeScript support with:

  • Strict null checking enabled
  • No implicit any
  • Complete type coverage
  • JSDoc comments on all exports

Browser Support

Works in:

  • ✅ Chrome 90+
  • ✅ Firefox 88+
  • ✅ Safari 14+
  • ✅ Edge 90+
  • ✅ Node.js 16+

Uses fetch API (polyfill in older browsers).

License

MIT

Contributing

Read the source code to understand the spec. Submit PRs that:

  1. Don't break existing types
  2. Include new test cases
  3. Update docstrings if adding features

The code IS the documentation.