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

@puzzmo/sdk

v1.0.62

Published

Puzzmo runtime SDK for game developers

Readme

Puzzmo Game Infra

Over the years we have a pretty refined route for making web games: prototype in HTML with no concern for code quality, share with people you like, then migrate it to a "real" codebase.

LLMs have changed this, and we're finding that the 'prototype in HTML' phase is getting close enough to production quality that it does not always warrant a a multi-month conversion to React/TypeScript/Redux to ensure the codebase can live forever.

So, after shipping two full games with this pipeline, we've knocked enough kinks out that it's ready for a more public space.

So, how do you make a game? Well first, you need a game idea - we can't help there! However, once you do, then you can start migrating it to run on Puzzmo by: yarn create puzzmo game.

@puzzmo/sdk

This repo is the SDK for building games on the Puzzmo platform. Handles communication between your game and Puzzmo.

There are a few parts here!

  1. The SDK, e.g. the runtime API for launching a game, completing it and other essentials
  2. The simulator, which provides the same message sending infrastructure Puzzmo.com will send to your game
  3. App integration information, e.g. how to make custom thumbnails for your puzzles (and more)
  4. Vite plugins which can help you get up and running faster

Install

npm install @puzzmo/sdk

yarn add @puzzmo/sdk

Quick Start

import { createPuzzmoSDK } from "@puzzmo/sdk"

const sdk = createPuzzmoSDK()

// 1. Wait for puzzle data from Puzzmo
const { puzzleString, inputString, theme, completed } = await sdk.gameReady()

// 2. Set up your game with the puzzle data
const puzzle = JSON.parse(puzzleString)
initializeGame(puzzle)
if (inputString) restoreState(inputString)

// 3. Signal that you're ready
sdk.gameLoaded()

// 4. Listen for lifecycle events
sdk.on("start", () => startGameLoop())
sdk.on("pause", () => pauseGameLoop())
sdk.on("resume", () => resumeGameLoop())
sdk.on("retry", () => resetGame())

API

createPuzzmoSDK(options?)

Creates an SDK instance. Options:

  • timeout - Timeout in ms waiting for puzzle data (default: 5000)

Lifecycle

| Method | Description | | ------------------------- | ------------------------------------------------------------------------- | | sdk.gameReady() | Async. Signals readiness, returns puzzle data and theme. | | sdk.gameLoaded(state?) | Signals game UI is ready. Host will send start. | | sdk.on(event, handler) | Listen for events: start, pause, resume, retry, settingsUpdate. | | sdk.off(event, handler) | Remove an event listener. |

Game State

| Method | Description | | --------------------------------------------------------- | ---------------------------------------------------- | | sdk.updateGameState(stateString, play?) | Save current game state for persistence. | | sdk.gameCompleted(play, config?) | Signal game completion with metrics and deeds. | | sdk.showCompletionScreen(results, gameplay, showRetry?) | Show the Puzzmo completion UI. | | sdk.hitCheckpoint(name, config, augConfig?) | Signal a gameplay milestone (for ads, leaderboards). |

Timer

The SDK manages a timer automatically (starts on start, pauses on pause, resets on retry).

sdk.timer.timeMs() // Elapsed time in ms
sdk.timer.timeSecs() // Elapsed time in seconds
sdk.timer.display() // ["1:23", "0:05"] (elapsed, penalty)
sdk.timer.addPenalty(5000) // Add 5s penalty
sdk.timer.isPaused() // Check if paused
sdk.timer.isRunning() // Check if running

Haptics

sdk.haptics.play("selection") // on placing a letter
sdk.haptics.play("success", { id: "puzzle-solved" }) // `id` names the cue in host logs

Names are the iOS feedback generators: selection, light, medium, heavy, soft, rigid, success, warning, error.

Fire-and-forget — it never reports back, and it is a no-op wherever the host can't deliver one, so never make gameplay depend on a haptic landing:

| Where | What happens | | --- | --- | | Puzzmo's iOS app | Real UIKit haptics via the native bridge | | puzzmo.com on Android/desktop | Approximated with navigator.vibrate | | Partner embeds | No haptic — see below |

The call goes to the host rather than vibrating from your game because Chrome has blocked navigator.vibrate in cross-origin iframes since Chrome 55, and games run in one. There is no Permissions-Policy opt-in, so calling it yourself is silently ignored. Partner embeds fire no haptic for the same reason: our frame is cross-origin to the partner's page, so the host is blocked too and there is no native bridge to fall back on. The cue is still forwarded to the partner page on the private message stream, so a partner can action it themselves.

Haptics are also off entirely when the player has turned them off in their Puzzmo settings, and the OS drops them in silent/DND or on hardware with no motor.

Theme

The theme object from gameReady() contains color tokens for the current Puzzmo theme:

const { theme } = await sdk.gameReady()

// Key colors
theme.g_bg // Game background
theme.fg // Foreground text
theme.key // Primary accent
theme.player // Player color (blue)
theme.alt1 // Accent green
theme.alt2 // Accent yellow
theme.alt3 // Accent purple
theme.type // "light" or "dark"

See the Theme type export for the full list of tokens.

Deeds

Deeds are gameplay statistics sent on completion:

sdk.gameCompleted(metrics, {
  deeds: [
    { id: "moves", value: 42 },
    { id: "accuracy", value: 95 },
    { id: "hit-streak", value: 8 },
  ],
})

The SDK automatically adds points and time deeds.

On-Screen Keyboard

For games that need text input on touch devices, the SDK can show Puzzmo's on-screen keyboard. The keyboard is only visible on touch devices — calling these methods on desktop is a no-op.

import { createPuzzmoSDK, defaultKeyboardConfig } from "@puzzmo/sdk"

const sdk = createPuzzmoSDK()

// Show the keyboard when the player selects an input
sdk.keyboard.show(defaultKeyboardConfig)

// Listen for key presses
sdk.on("keyboardKeyPress", ({ key }) => {
  if (key === "⌫") handleBackspace()
  else if (key === "↵") handleEnter()
  else handleLetter(key)
})

// Hide when input is dismissed
sdk.keyboard.hide()

defaultKeyboardConfig is a standard QWERTY layout with Enter and Backspace. Customize it by spreading:

sdk.keyboard.show({ ...defaultKeyboardConfig,
// Disable letters that are no longer valid given the current game state
disabled: usedLetters })
// Override inidividual keys with their own styles for emphasis
individualKeyStyles: keyStylesFromRules([
    { keys: ["?"], background: { backgroundColor: "#ffd54a" } },
    { keys: usedLetters, text: { opacity: "0.4", transition: "opacity 200ms ease-out" } },
  ]),

For games with a spatial input model (e.g. selecting a grid cell by dragging across the keyboard), enable drag cursor support and listen for the additional events:

sdk.keyboard.show({ ...defaultKeyboardConfig, supportsDragCursor: true })

sdk.on("keyboardCursorChange", ({ position }) => highlightCellAtPosition(position))
sdk.on("keyboardCursorEnd", () => confirmCellSelection())

See the KeyboardConfig type export and the add-keyboard-support skill for full field documentation.

App Integration

For games to show a dynamic thumbnail, you will need an App Bundle

import type { EditorBundle, ValidationReport } from "@puzzmo/sdk"
import { puzzleToSVG } from "./src/puzzleToSVG"

export const AppBundle = {
  renderThumbnail(puzzleString, inputString, config) {
    return puzzleToSVG(puzzleString, inputString, config)
  },
} satisfies EditorBundle

This and the editor bundle are separate JavaScript files from your main game.

There are Vite plugins to make this easy, but otherwise, they should be files in your upload named app-bundle.js and editor-bundle.js with ESM exports which match the shapes of the TypeScript types.

Thumbnail JSX

We have found over time that using JSX for thumbnails makes it a lot easier to ensure correct SVG output, but React/Preact are big runtimes, so we have a smaller JSX runtime built just for non-interactive SVGs based on understated.

To use it, point your tsconfig's JSX factory at h. Your .tsx files then compile to the SVG runtime instead of to React:

{
  "compilerOptions": {
    "jsx": "react",
    "jsxFactory": "h",
    "jsxFragmentFactory": "Fragment"
  }
}

A classic factory has to be in scope, so import h in every file that writes JSX even though nothing calls it by hand — and Fragment too, if you use <>…</>, which becomes a <g> since render produces a single node. Attribute names can be SVG's own (stroke-width) or React's (strokeWidth): camelCase is kebab-cased for you, bar the few SVG spells that way itself, like viewBox.

import { h, render } from "@puzzmo/sdk/svgJSX"

export function renderThumbnail(puzzleString: string, inputString?: string, config?: ThumbnailConfig): ThumbnailResult {
  const puzzle = JSON.parse(puzzleString)
  const size = 200

  const svg = (
    <svg xmlns="http://www.w3.org/2000/svg" viewBox={`0 0 ${size} ${size}`}>
      <rect width={size} height={size} fill={config?.theme?.g_bg ?? "#1a1a2e"} />
      <text x={size / 2} y={size / 2} textAnchor="middle" fill={config?.theme?.fg ?? "#fff"} fontSize="24">
        {puzzle.title}
      </text>
    </svg>
  )

  const el = render(svg)
  // Duck-typed rather than `instanceof Element`: the server-side renderer runs bundles against a
  // minimal SVG DOM that has no such global.
  if (!el || !("outerHTML" in el)) throw new Error("Could not render the thumbnail")
  return { svg: el.outerHTML, width: size, height: size }
}

The h function is a JSX factory that creates virtual DOM nodes, and render converts them into real DOM elements. Since thumbnails can run server-side or in a DOM-shimmed environment, the SVG is serialized via outerHTML and returned alongside its width/height so the host can size the thumbnail without parsing it.

Editor Integration

For games that support puzzle editing in Puzzmo Workshop, you will need an Editor Bundle:

import type { EditorBundle, ValidationReport } from "@puzzmo/sdk"

export const validator = {
  validate(data) {
    return { success: true, issues: [] }
  },
} satisfies EditorBundle