Using Loreline with TypeScript

Loreline ships with full TypeScript type definitions, giving you type-safe access to the entire API. This guide shows how to set up a TypeScript project, load a .lor script, and handle dialogue, choices, and script completion with typed handlers.

Installing the library

Install via npm:

npm install loreline

The package includes loreline.js (ES module) and loreline.d.ts (type definitions). No separate @types package is needed.

You can also download loreline-js.zip from the GitHub releases page. It contains js/loreline.js and js/loreline.d.ts. Copy them into your project.

Project setup

Create an ESM project with TypeScript. Your package.json should include:

{
  "type": "module",
  "scripts": {
    "start": "tsx src/main.ts"
  },
  "dependencies": {
    "loreline": "^0.5.0",
    "tsx": "^4.21.0",
    "typescript": "^5.0.0"
  },
  "devDependencies": {
    "@types/node": "^25.0.0"
  }
}

And a tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ES2020",
    "moduleResolution": "node",
    "strict": true,
    "esModuleInterop": true,
    "outDir": "dist"
  },
  "include": ["src"]
}

Using tsx lets you run TypeScript directly without a build step. You can also compile with tsc and run the output with Node.js if you prefer.

Loading a script

Import types and classes from the loreline package:

import { Loreline, Script } from "loreline";
import { readFileSync } from "fs";

const content: string = readFileSync("story/CoffeeShop.lor", "utf-8");

function handleFile(path: string, callback: (content: string) => void): void {
  callback(readFileSync("story/" + path, "utf-8"));
}

const script: Script = Loreline.parse(content, "CoffeeShop.lor", handleFile)!;

Loreline.parse() returns Script | null. Use the non-null assertion ! when you are sure the source is valid, or check for null explicitly.

If your script has no import statements, you can omit the file handler:

const script: Script = Loreline.parse(content)!;

Handling dialogue

Use the DialogueHandler type for your callback. The handler receives the interpreter, a character identifier (or null for narrative text), the dialogue text, tags, and an advance callback:

import { DialogueHandler } from "loreline";

const onDialogue: DialogueHandler = (interpreter, character, text, tags, advance) => {
  if (character) {
    const name = interpreter.getCharacterField(character, "name");
    console.log(`${name || character}: ${text}`);
  } else {
    console.log(text);
  }
  advance();
};

Handling choices

Use the ChoiceHandler type. Each option has text, tags, and enabled fields:

import { ChoiceHandler } from "loreline";

const onChoice: ChoiceHandler = (interpreter, options, select) => {
  for (let i = 0; i < options.length; i++) {
    if (options[i].enabled) {
      console.log(`  [${i + 1}] ${options[i].text}`);
    }
  }
  select(0); // select first option
};

Handling script completion

Use the FinishHandler type:

import { FinishHandler } from "loreline";

const onFinish: FinishHandler = (interpreter) => {
  console.log("--- The End ---");
};

Complete example

Here is a complete Node.js application that loads and plays a Loreline script with full type annotations:

import { readFileSync } from "fs";
import {
  Loreline, Script, Interpreter,
  DialogueHandler, ChoiceHandler, FinishHandler
} from "loreline";

const storyDir = "story/";
const content: string = readFileSync(storyDir + "CoffeeShop.lor", "utf-8");

function handleFile(path: string, callback: (content: string) => void): void {
  try {
    callback(readFileSync(storyDir + path, "utf-8"));
  } catch {
    callback("");
  }
}

const script: Script | null = Loreline.parse(content, "CoffeeShop.lor", handleFile);

if (!script) {
  console.error("Failed to parse script");
  process.exit(1);
}

const onDialogue: DialogueHandler = (interp, character, text, tags, advance) => {
  if (character) {
    const name = interp.getCharacterField(character, "name");
    console.log(`${name || character}: ${text}`);
  } else {
    console.log(text);
  }
  console.log();
  advance();
};

const onChoice: ChoiceHandler = (interp, options, select) => {
  for (let i = 0; i < options.length; i++) {
    if (options[i].enabled) {
      console.log(`  [${i + 1}] ${options[i].text}`);
    }
  }
  select(0);
};

const onFinish: FinishHandler = (interp) => {
  console.log("--- The End ---");
};

const interp: Interpreter = Loreline.play(script, onDialogue, onChoice, onFinish);

Run it with:

npm run start

Going further

For a polished browser-based example with animations, smooth scrolling, and styled output, download loreline-web.zip from the GitHub releases page.