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.
Looking for the full list of classes and methods? See the TypeScript API reference.
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 (v0.10.0). 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 ---");
};
Starting from a specific beat
By default, play() starts from the beginning of the script. To start from a specific beat, pass its name:
Loreline.play(script, onDialogue, onChoice, onFinish, "MorningScene");
Interpreter options
You can pass additional options to play() to register custom functions or apply translations:
const translations = await new Promise<Translations>(resolve =>
Loreline.loadLocale('fr', script, 'CoffeeShop.lor', handleFile, resolve));
Loreline.play(script, onDialogue, onChoice, onFinish, null, {
functions: {
roll: (interp, args) => Math.floor(Math.random() * Number(args[0])) + 1
},
translations: translations
});
loadLocale() walks the script's import tree to find a .lang.lor file next to each imported file and merges them into a single translations map. The same handleFile callback you pass to parse() is reused here.
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.