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

imsc-script

v2.0.2

Published

JS library to play dialogs/scripts created in IMS Creators

Readme

ImscScript JS

NPM Version

A framework agnostic JavaScript library to play dialogues and visual scripts using simple JSON schema

You can use IMS Creators (Desktop version) to create ready-to-use dialogue graphs in visual editor

Works with any web game engine (Phaser, PixiJS, or vanilla JS) and provides full control over dialog flow, branching, variables, triggers, and serialization.

Features

  • 🎭 Speech nodes with optional choices (branching dialogs)
  • 🔀 Conditional branching based on variables or expressions
  • 📦 Variable management – set, get, and use in conditions
  • Trigger / Function nodes – invoke game logic and receive outputs
  • 💾 Serializable state – save/load, undo/redo, replay
  • 🔌 Framework agnostic – works with Phaser, PixiJS, or any rendering engine
  • 📝 Expression evaluation – math, comparison, and logical operators
  • 🧩 Async support – triggers can be asynchronous
  • 📜 Sub‑scriptscallScript nodes run nested graphs with isolated variables and in/out data flow
  • 🧰 Custom nodes – register your own exec (flow) or data (expression) node types via registerCustomNode
  • ⏸️ Pause/Resume – pause execution during triggers or user input

Installation

npm install imsc-script

or include from CDN

<script src="https://cdn.jsdelivr.net/gh/ImStocker/imsc-script-js@main/dist-browser/index.iife.js"></script>

and use new ImscScript.ImscScriptPlayer to create player

Usage

1. Create a JSON graph from scratch or export script from IMS Creators

A graph is a JSON object with a start node ID and a nodes dictionary. Each node has a type field and an ID of the next node (next field, or options if it has multiple output variants). Nodes can have input data in a values field, where each value can be a fixed value or a link to another node's output via { "get": "nodeId", "param": "outputName" }. See the JSON schema for the full specification.

Example of graph:

{
  "start": "greeting",
  "nodes": {
    "greeting": {
      "type": "speech",
      "values": {
        "character": "Guard",
        "text": "Hello!"
      },
      "next": "ask"
    },
    "ask": {
      "type": "speech",
      "values": {
        "character": "Guard",
        "text": "What do you want?"
      },
      "options": [
        { "values": { "text": "I seek adventure." }, "next": "adventure" },
        { "values": { "text": "I want to trade." }, "next": "trade" },
        { "values": { "text": "Nothing, goodbye." }, "next": "end" }
      ]
    },
    "trade": {
      "type": "trigger",
      "subject": "trade",
      "next": "ask"
    },
    "adventure": {
      "type": "speech",
      "values": {
        "character": "Guard",
        "text": "Then go east, brave soul!"
      },
      "next": "end"
    },
    "end": { "type": "end" }
  }
}

You can also export graphs as JSON from IMS Creators — use the computed content of a script block as the graph input.

Example of exported file:

{
  "id": "some uuid",
  "blocks": [
    {
      "id": "some uuid",
      "type": "script",
      "computed": {
        //
        // Here is a graph
        //
      }
      // ...
    }
    // ...
  ]
  // ...
}

2. Initialize and play

import { ImscScriptPlayer } from 'imsc-script';
import myDialogGraph from './myDialog.json';

const player = new ImscScriptPlayer(myDialogGraph, {
  initialVariables: { customVar: 42 }, // overrides defaults values of variables
  events: {
    onSpeech: ({ speech, node, nodeId }) => {
      // Render speech bubble 
      console.log(`${speech.character}: ${speech.text}`);
      if (speech.options.length) {
        // Show choice buttons (option.text - text of option)
        // ...
        // buttons should call player.continue(option.index)
      } else {
        // Show "Continue" button
        // ...
        // button should call player.continue()
      }
    },
    onAction: async ({ type, subject, inputs, node, nodeId }) => {
      // Handle game logic (e.g., give item, play sound)
      console.log(`Action: ${type} - ${subject}`, inputs);
      // Return outputs and optionally override the next node
      return { outputs: { success: true, reward: 100 } };
    },
    onEnd: () => {
      console.log('Dialog finished');
    }
  }
});

// Start the dialog (returns a Promise that resolves when dialog ends)
await player.play();

See browser usage in tests/browser.html

3. Control the dialog from your UI

  • When a speech node without options appears, call player.continue() after the user clicks "Continue".

  • When a speech node with options appears, call player.continue(selectedIndex) when the user picks an option.

  • Use player.pause() to pause execution (e.g., if you need run script step by step), and player.resume() to continue. Use player.continue() to make one step forward while paused.

  • Jump to any node using player.goto(nodeId).

// Example: button click handlers
continueButton.onclick = () => player.continue();
choiceButton.onclick = () => player.continue(0);

// Pause during a long animation
player.pause();
await playAnimation();
player.resume();

API Reference

Constructor

new ImscScriptPlayer(graph: ImscScriptGraph, options?: ImscScriptPlayerOptions)

|Option|Type|Description| |--- |--- |--- | |initialVariables|AssetPropsPlainObject|Initial variable values (overrides graph defaults).| |events|ImscScriptPlayerEvents|Event handlers (see below)|

Properties

|Property|Type|Description| |--- |--- |--- | |isRunning|boolean|true if a dialog is currently playing (not ended).| |isPaused|boolean|true if the dialog is paused.| |currentNode|ImscScriptGraphNode | null|The currently active node.| |currentNodeId|string | null|ID of the currently active node.| |variables|Readonly<AssetPropsPlainObject>|Current frame's variable values (read‑only).| |globals|Readonly<AssetPropsPlainObject>|Global variable values shared across all frames (read‑only).| |frames|ImscScriptPlayerFrame[]|Frame stack (current frame is index 0).|

Methods

|Method|Description| |--- |--- | |play(startNodeId?: string): Promise<void>|Starts the dialog from the graph's start node (or a specific node). Returns a promise that resolves when the dialog ends.| |pause()|Pauses execution. The dialog will not advance until resume() or continue() is called.| |resume()|Resumes execution without advancing (e.g., after a paused trigger).| |continue(optionIndex?: number, resume = false): void|Advances to the next node from a speech node (with optionIndex selects that choice). If resume true, unpause execution| |goto(nodeId: string \| null): void|Jumps to a specific node (or ends if null).| |end(): void|Ends the current dialog (resolves the play() promise).| |setVariable(key: string, value: any): void|Sets a runtime variable.| |getVariable(key: string): any|Gets a runtime variable.| |serialize(): ImscScriptPlayerState|Returns the current state (frame stack, globals).| |load(state: ImscScriptPlayerState): void|Restores a previously serialized state.| |registerCustomNode(typeName, kind, handler)|Registers a handler for a custom node type. kind is 'exec' (flow node) or 'data' (expression node).| |on(event, handler): void|Registers an event handler. Can be only one handler per event| |inspectGraph(callback, startNodeId?)|Walk over script graph nodes. Allows to check consequences of a choice without actually playing.|

Events

All event handlers receive a single event object with named properties.

|Event|Event Properties|Description| |--- |--- |--- | |onStart|()|Dialog started.| |onEnd|()|Dialog ended.| |onNodeBeforeEnter|{ nodeId, node }|Called before node inputs are evaluated. Can be async.| |onNodeEvaluated|{ inputs, optionsInputs, node, nodeId }{ inputs?, optionsInputs? } (optional)|Node input values (and options) have been evaluated. Return modified values if needed.| |onNodeEnter|{ inputs, node, nodeId }|Player entered to node. Fires after onNodeEvaluated. Can be async.| |onNodeExit|{ nodeId, node }|Exited a node.| |onSpeech|{ speech, node, nodeId }|A speech node is active. speech contains character, text, values, and options array (each with index, condition, text, values).| |onChoice|{ optionIndex, node, nodeId }|User selected a choice (fired before moving to the next node).| |onAction|{ type, subject, inputs, node, nodeId }{ outputs?, next? } (optional)|A trigger or function node is active. type is 'trigger' or 'function'. Return outputs for downstream bindings and optionally next to override the target node.| |onLoadScript|{ scriptId }ImscScriptGraph|Load a sub‑script by ID when a callScript node is activated.| |onSubScriptEnter|{ frame }|A sub‑script frame was pushed onto the stack (entered a callScript node).| |onSubScriptExit|{ frame }|A sub‑script frame was popped from the stack (exited a callScript node).| |onVariableChange|{ variable, newValue, oldValue, frameIndex }|A variable changed in a frame.| |onError|{ error }|An error occurred.| |onStateChange|{ state }|State changed (useful for auto‑saving).|

State Serialization (Save / Load)

You can save the exact dialog state at any time and restore it later:

// Save
const savedState = player.serialize();
localStorage.setItem('dialogSave', JSON.stringify(savedState));

// Load later
const loadedState = JSON.parse(localStorage.getItem('dialogSave'));
player.load(loadedState);

License

MIT

Links

Contributing

Issues and pull requests are welcome. Please ensure your code passes the existing tests and follows the coding style.