Inkweaver SDK
Scripts imported by your plugin.json’s main script share a single, global state object. Expressions in the screenplay have access to this state as well.
Plugins, on the other hand, each have their own private state. Anything a plugin wants to expose will be available through its selectors (see getSelectors).
Your state is a plain object. There are no special variable declarations or serialization constraints to worry about. Just store what you need, and Inkweaver manages it for you when a bookmark is created.
getState
Section titled “getState”Returns the current novel state.
getState<T>(): TExample
const trust = getState().trust;setState
Section titled “setState”Replaces the current novel state with a new value.
setState<T>(newState: T | ((currentState: T) => T)): voidYou can pass a new state object directly…
Example
setState({ trust: 1, achievements: ["first-blood"],});…or a function that receives the current state and returns the new one. The functional form is recommended when your update depends on the current value:
Example
setState((current) => ({ ...current, trust: current.trust + 1,}));updateState
Section titled “updateState”Updates the current novel state by deeply merging the provided value into the existing state.
updateState<T>(newState: T | ((currentState: T) => T)): voidLike setState, you can pass a new value directly or a function that receives the current state and returns the new one:
Example
// Update a single value without affecting the rest of state:updateState({ trust: 8 });
// Use the functional form when your update depends on the current value:updateState((current) => ({ trust: current.trust + 1 }));getSelectors
Section titled “getSelectors”Returns a collection of functions that plugins use to expose their state to your scripts. Rather than accessing plugin internals directly, selectors give you a safe, managed way to read derived or private state.
getSelectors(): Record<string, Function>Selectors work naturally with onChange for reactive updates:
Example
const { currentScene } = getSelectors();onChange(currentScene, (scene) => console.log(`Scene changed to ${scene.text}`),);See the plugins documentation for a comprehensive list of core plugins, and their selectors.
Event Processing
Section titled “Event Processing”Event handlers are how your script reacts to what’s happening in the novel. Use onChange to respond when state values change, and onLabel to intercept and control lines in the screenplay as they’re encountered. Together with state, these two functions are the primary tools for building dynamic, reactive stories.
onChange
Section titled “onChange”Registers a reactive callback that is triggered whenever a tracked value changes.
onChange<T>( selector: () => T, onUpdate: (currentValue: T) => void, onCleanup?: (lastValue: T) => void): voidselector is a function that returns the value you want to watch. Whenever any application state changes, the value returned by selector is compared to the last value that it returned. If the value changed, onCleanup is called with the old value, then onUpdate is called with the new one.
Example
onChange( () => getState().trust, (trust) => console.log(`Trust is now ${trust}`), (lastTrust) => console.log(`Trust was ${lastTrust}`),);onLabel
Section titled “onLabel”Registers a callback that is triggered whenever a matching label is encountered in the screenplay.
onLabel<T extends BlockLexia>( pattern: string | RegExp, callback: (lexia: T, groups: Record<string, string> | undefined) => Promise<undefined | T | boolean>): voidThe pattern can be a string for exact matches, or a regular expression for parametric labels. The callback receives two arguments: the current lexia, and any named capture groups from the regex (or undefined if the pattern is a plain string).
The callback may be asynchronous. The engine will await its return value before deciding how to handle the line.
The callback can return:
trueorundefined— display the line normallyfalse— skip the line entirely- A new
BlockLexiaof the same type — replace the line with the returned value
Example
/* * Screenplay Usage: * * SOPHIA <if-trustworthy> * I have something important to say... */onLabel("if-trustworthy", (lexia) => { const { trust } = getState(); return trust >= 5;});Named capture groups make labels parametric. In this example, the required trust range is defined in the screenplay rather than the script:
Example
/* * Screenplay Usage: * * SOPHIA * I'm not sure I can share this with you... <if-trustworthy 0-3> * I have something important to say... <if-trustworthy 4-10> */onLabel( /if-trustworthy (?<from>[0-9]+)-(?<to>[0-9]+)/, (lexia, { from, to }) => { const { trust } = getState(); return trust >= parseInt(from) && trust <= parseInt(to); },);Navigation
Section titled “Navigation”These functions control the reader’s position in the story.
stepForward
Section titled “stepForward”Advances the novel by one step. This does nothing if the reader is at the tip of the state history.
stepForward(): Promise<void>Example
await stepForward();stepBack
Section titled “stepBack”Steps the novel backward by one step, restoring the previous state.
stepBack(): Promise<void>Example
await stepBack();isRewinding
Section titled “isRewinding”Returns true if the reader is currently stepping backward through the story.
isRewinding(): booleanExample
const rewinding = isRewinding();Bookmarks
Section titled “Bookmarks”Bookmarks are Inkweaver’s save system. Each bookmark captures a snapshot of the novel’s state at a point in time, allowing the reader to return to it later.
interface Bookmark { id: string; image: string; // A base64-encoded image used as the bookmark's thumbnail time: number; type: "auto" | "quick" | "user";}getBookmarks
Section titled “getBookmarks”Returns a list of all saved bookmarks for the current novel.
getBookmarks(): Promise<Array<Bookmark>>Example
const bookmarks = await getBookmarks();saveBookmark
Section titled “saveBookmark”Saves a bookmark, capturing the current state of the novel.
saveBookmark(bookmark: Bookmark): Promise<void>Example
await saveBookmark({ id: "Act III", image: base64EncodedImage, time: Date.now(), type: "auto",});loadBookmark
Section titled “loadBookmark”Loads a previously saved bookmark, restoring the novel to the state captured at that point.
loadBookmark(bookmark: Bookmark): Promise<void>Example
const bookmarks = await getBookmarks();await loadBookmark(bookmarks[0]);Assets & File Management
Section titled “Assets & File Management”These functions handle asset resolution and file I/O across platforms. Use resolve to map logical asset paths to their build-time filenames, and readFile / writeFile for persistent storage outside of Inkweaver’s state system.
resolve
Section titled “resolve”Resolves a logical asset path to its physical, build-time filename using the asset manifest.
resolve<T extends RequestInfo | URL>(url: T, locale?: string): TWhen Inkweaver builds your novel, assets are saved using content-addressed filenames. resolve uses an asset manifest to find the physical file that corresponds to the logical path you provide.
For localized assets, see the localization guide.
Example
const url = resolve("/assets/shop.json");const response = await fetch(url);readFile
Section titled “readFile”Reads a file from the platform’s filesystem.
readFile(input: RequestInfo | URL, init?: RequestInit): Promise<Response>The underlying storage mechanism depends on the build target defined in your project.json:
- Web — data is persisted to IndexedDB.
- Desktop — reads from a sandboxed OS-specific application data folder at
<appDataPath>/<appName>/files.
Example
const response = await readFile("/notes.txt");const text = await response.text();writeFile
Section titled “writeFile”Writes a file to the platform’s filesystem.
writeFile(input: RequestInfo | URL, data: any, init?: RequestInit): Promise<Response>Uses the same platform-specific storage as readFile.
Example
await writeFile("/notes.txt", "The butler did it.");Utilities
Section titled “Utilities”immutable
Section titled “immutable”Runs an async function and calls onError if the novel state changed during its execution.
immutable<T>( fn: () => Promise<T>, onError: (changes: string) => void): Promise<T>This is a development-time guard against unintended state changes. If fn causes any state changes, onError is called with a description of what changed.
await immutable( () => doSomeWork(), (changes) => console.warn(`Unexpected state changes: ${changes}`),);Extensibility
Section titled “Extensibility”These functions let you extend the engine’s command and selector systems. Commands are how the engine processes updates, and selectors are how plugins expose their state. Through decoration, your scripts can wrap either one to modify or extend behavior.
Any code you write has the same power and privileges as the engine’s built-in systems.
decorateCommand
Section titled “decorateCommand”Defines middleware that wraps a system command. The decorator receives the original arguments and a next function that calls the previous implementation.
decorateCommand<TName extends keyof Commands>( commandName: TName, fn: (context: Parameters<Commands[TName]>[0], next: Commands[TName]) => ReturnType<Commands[TName]>, priority?: number): voidDecorators are applied in priority order — higher numbers run first. If no priority is supplied, 0 is assumed. As a guideline:
- > 0 — runs before the engine’s built-in behavior
- < 0 — runs after the engine’s built-in behavior
Number.MAX_VALUE/Number.MIN_VALUE— for decorators that must run absolutely first or last
Example
decorateCommand("init", (context, next) => { console.log("Before init"); const result = next(context); console.log("After init"); return result;});decorateSelector
Section titled “decorateSelector”Defines middleware that wraps a selector. Works the same way as decorateCommand, but for selectors.
decorateSelector<TName extends keyof Selectors>( name: TName, fn: (context: Parameters<Selectors[TName]>[0], next: Selectors[TName]) => ReturnType<Selectors[TName]>, priority?: number): voidExample
decorateSelector("currentScene", (context, next) => { const scene = next(context); return { ...scene, customData: getState().extraSceneInfo };});Core Features
Section titled “Core Features”These functions are the building blocks that core plugins use to process screenplay content. You won’t need them in a typical script. But if you’re decorating commands to customize how the engine handles text in the screenplay, this is how it’s done under the hood.
getCommands advanced
Section titled “getCommands ”Returns a collection of system commands that control the engine’s behavior. Commands are the primary way that the engine processes updates, and plugins can decorate them to extend or modify behavior.
getCommands(): CommandsExample
const { processDialogue } = getCommands();See the plugins documentation for a comprehensive list of core plugins, and their commands.
commitState advanced
Section titled “commitState ”Flushes the current state to the engine’s state history.
commitState(): Promise<void>Inkweaver tracks state changes over time to support stepping forward and backward. commitState creates a checkpoint in this history. Under normal operation, the engine handles this automatically. You only need to call this if you are building a custom command that modifies state and needs to ensure those changes are captured as a discrete step.
Example
await commitState();processLabel advanced
Section titled “processLabel ”Invokes any onLabel callbacks that match the provided lexia.
processLabel<T extends BlockLexia>(lexia: T): Promise<undefined | T | boolean>Example
processLabel(lexia);evaluateExpression advanced
Section titled “evaluateExpression ”Evaluates an expression block in the screenplay.
evaluateExpression(expression: Expression): anyExample
dialogueLexia.expressions.forEach((expression) => { console.log(evaluateExpression(expression));});