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

@memizy/plugin-sdk

v0.2.0

Published

Official TypeScript SDK for building Memizy plugins.

Downloads

37

Readme

🛠️ Memizy Plugin API & SDK

Build interactive study modules for the OQSE ecosystem.

Version License


💡 What is this?

Memizy host applications use a sandboxed iframe architecture to render study sets. This SDK provides a TypeScript library around the window.postMessage API, allowing custom plugins to communicate with the host. The SDK also runs the Leitner spaced-repetition algorithm internally, keeping the host's storage in sync after every interaction via the SYNC_PROGRESS message.

📦 Installation

npm install @memizy/plugin-sdk

Or use the CDN build directly in a static HTML plugin:

<script type="module">
  import { MemizyPlugin } from 'https://cdn.jsdelivr.net/npm/@memizy/[email protected]/dist/memizy-sdk.js';
</script>

🏗️ Architecture

State-Sync + CRUD + Asset Bridge

| Role | Responsibility | |---|---| | Host (Memizy Player) | Owns persistent storage. Fetches study sets, rewards Fuel, persists OQSEP progress to its own database. | | Plugin (SDK) | Owns session-level state. Renders items, runs the Leitner reducer internally, pushes progress deltas via SYNC_PROGRESS. Can read/write assets through the host bridge. |

🧭 Standalone Mode

The SDK automatically detects when the plugin is running outside a Memizy host (window.self === window.top) and enters Standalone Mode. The developer's onInit callback is called identically in all cases — no extra code is needed.

Priority order:

| # | Condition | Behaviour | |---|-----------|-----------| | 1 | Running inside the Memizy iframe | Waits for INIT_SESSION postMessage from host | | 2 | useMockData() was called | Fires onInit after standaloneTimeout ms if no host message arrives | | 3 | ?set=<url> query parameter present | Fetches the OQSE JSON from that URL and fires onInit automatically | | 4 | None of the above | Shows a floating ⚙ gear icon and opens a settings dialog |

A floating ⚙ gear button (bottom-right corner, closed Shadow DOM) is shown in standalone mode. Clicking it opens a dialog with two tabs:

  • Study Set — load via URL, paste OQSE JSON, or upload a .oqse.json file (drag & drop supported)
  • Progress — load OQSEP progress via pasted JSON or .oqsep file upload

Set showStandaloneControls: false to suppress the built-in UI entirely.

Using ?set= during development:

http://localhost:5173/?set=https://example.com/my-set/data.oqse.json

All relative MediaObject.value paths in meta.assets and item.assets are automatically resolved to absolute URLs so plugins always receive ready-to-use asset URLs.

📊 State-Sync & Spaced Repetition

The SDK runs a Leitner spaced-repetition reducer internally on every answer() call. Each answer:

  1. Stops the item timer (or uses an explicit timeSpent).
  2. Computes a new ProgressRecord — advances the bucket (0→4) on correct, resets to 1 on incorrect — and sets nextReviewAt.
  3. Immediately sends SYNC_PROGRESS to the host to keep its storage in sync.

skip() records isSkipped: true in lastAnswer without touching the bucket or stats, and also sends SYNC_PROGRESS.

✏️ CRUD — Study Set Mutation

Plugins can modify the study set at any time during a session:

plugin.saveItems(items)       // Create or update items (merged by id)
plugin.deleteItems(itemIds)   // Delete items by UUID
plugin.updateMeta(partialMeta) // Update title, tags, assets, etc.

🗂️ Asset Bridge

Because plugins run in a cross-origin <iframe>, they cannot access host storage directly. The SDK proxies asset I/O through the host:

// Upload a file to host storage and get back a MediaObject
const media = await plugin.uploadAsset(file, 'hero-image');
plugin.saveItems([{ ...item, assets: { image: media } }]);

// Read a raw file from host storage
const file = await plugin.getRawAsset('skull-model');
const url = URL.createObjectURL(file);

🚀 Quick Start

import { MemizyPlugin } from '@memizy/plugin-sdk';
import type { OQSEItem, InitSessionPayload } from '@memizy/plugin-sdk';

const plugin = new MemizyPlugin({
  id: 'https://my-domain.com/flashcard-plugin',  // must match OQSE manifest id
  version: '1.0.0',
  debug: true,
});

// Optional: provide mock data for development outside the host
plugin.useMockData([
  { id: 'q1', type: 'flashcard', question: 'What is 2+2?', answer: '4' },
  { id: 'q2', type: 'flashcard', question: 'Capital of France?', answer: 'Paris' },
]);

let items: OQSEItem[] = [];
let cursor = 0;

plugin.onInit(({ items: sessionItems, progress }: InitSessionPayload) => {
  items = sessionItems;
  showItem(items[cursor]!);
});

function showItem(item: OQSEItem) {
  plugin.startItemTimer(item.id);
  document.getElementById('question')!.textContent = String(item['question']);
}

document.getElementById('btn-correct')!.addEventListener('click', () => {
  const item = items[cursor]!;
  plugin.answer(item.id, true, { confidence: 4 });  // runs Leitner, sends SYNC_PROGRESS
  if (++cursor < items.length) showItem(items[cursor]!);
  else plugin.exit({ score: 100 });
});

document.getElementById('btn-wrong')!.addEventListener('click', () => {
  const item = items[cursor]!;
  plugin.answer(item.id, false, { confidence: 1 });
  if (++cursor < items.length) showItem(items[cursor]!);
  else plugin.exit({ score: 0 });
});

📚 API Summary

Constructor options

| Option | Type | Default | Description | |---|---|---|---| | id | string | — | Unique plugin identifier (URL or URN-UUID). Must match the OQSE manifest. | | version | string | — | SemVer version of the plugin. | | standaloneTimeout | number | 2000 | ms to wait for INIT_SESSION before entering standalone/mock mode. | | debug | boolean | false | Log lifecycle events to the browser console. | | showStandaloneControls | boolean | true | Show the floating ⚙ gear UI in standalone mode. |

Key methods

| Method | Description | |---|---| | onInit(handler) | Called when INIT_SESSION is received (or standalone fires). | | onConfigUpdate(handler) | Called when CONFIG_UPDATE is received (theme/locale change). | | answer(id, isCorrect, opts?) | Record answer → run Leitner → send SYNC_PROGRESS. | | skip(id) | Record skip (isSkipped=true) → send SYNC_PROGRESS. | | syncProgress(records) | Bulk-merge progress records and push to host. | | getProgress() | Snapshot of current internal progress state. | | saveItems(items) | Send MUTATE_ITEMS to host. | | deleteItems(ids) | Send DELETE_ITEMS to host. | | updateMeta(meta) | Send MUTATE_META to host. | | uploadAsset(file, key?) | Upload file to host storage → Promise<MediaObject>. | | getRawAsset(key) | Read raw file from host storage → Promise<File\|Blob>. | | exit(opts?) | Send EXIT_REQUEST (replaces old complete()). | | startItemTimer(id) | Start per-item stopwatch. | | stopItemTimer(id) | Stop timer, return elapsed ms. | | clearItemTimer(id) | Stop timer silently. | | useMockData(items, opts?) | Register mock data for standalone dev mode. | | triggerMock() | Fire onInit with mock data immediately. | | isStandalone() | Returns true when running outside a host frame. | | destroy() | Clean up listeners, timers, and standalone UI. |

📚 Documentation

Full protocol specification: plugin-sdk-api-v1.md