@wity-ai/rkf-core
The headless evaluation engine for RKF documents. Parse, evaluate, serialize, validate. Zero DOM, zero dependencies. Implements the RKF v2.0 spec -- documents as state machines -- with XML as the current serialization format.
Install
npm install @wity-ai/rkf-core
# Node.js only (optional XML peer):
npm install @xmldom/xmldomImport
import {
parse, setXmlParser,
evaluate, initState, applyUpdate, applyUpdates,
serialize,
validate,
setGraphProvider,
rkfToKnowledge,
evalExpr, evalGate,
RkfEdit, splitBlocksByHeading,
SCHEMA_VERSION, ELEMENT_TAGS, MARK_TAGS, PORT_TAGS,
} from '@wity-ai/rkf-core';Setup
RKF's canonical serialization is a <wity-knowledge> document — the same model as wity-workbook: an RKF document is a knowledge graph, with elements as typed nodes. parse() and serialize() therefore delegate to graph-headless, which must be injected once at startup:
import * as graph from '@wity/graph-headless';
import { setGraphProvider, setXmlParser } from '@wity-ai/rkf-core';
setGraphProvider(graph); // { GraphAbstract, serialize, parse, setXmlParser }
// Node.js only — forwarded to graph-headless. Browser uses native DOMParser.
import { DOMParser } from '@xmldom/xmldom';
setXmlParser(new DOMParser());evaluate(), validate(), initState() and applyUpdate() operate on an already-parsed RkfDocument and do not require the graph module.
API
parse(xml) -> RkfDocument
Parse a <wity-knowledge> document into an RkfDocument. Requires the graph module (see Setup) — throws otherwise.
const doc = parse(`<wity-knowledge version="1">...</wity-knowledge>`);
// { version, id, title, vars, sections }To upgrade a document authored in the retired <wity-rkf> format, use rkfToKnowledge().
The document is a <wity-knowledge> graph on the wire, but that is an internal representation: the public API is domain-level (RkfDocument) only — the same pattern as wity-workbook (WorkbookStore) and wity-ideation (IdeationGraph), which keep the graph mapping internal. To work with the raw knowledge graph, use graph-headless directly on the same <wity-knowledge> XML.
setXmlParser(parserInstance)
Inject a DOMParser-compatible instance for Node.js environments. Forwarded to graph-headless's parser.
setGraphProvider(graph)
Inject the graph-headless module ({ GraphAbstract, serialize, parse, setXmlParser }). Required for parse() / serialize(), since RKF documents are <wity-knowledge> graphs. The same substrate also drives topological derive resolution (evaluation order independent of authoring order) and combinational cycle detection in validate().
import * as graph from '@wity/graph-headless';
setGraphProvider(graph);(Passing just the GraphAbstract class still enables the dependency-graph features, but not parse()/serialize().)
rkfToKnowledge(rkfXml) -> string
Convert a legacy <wity-rkf> document to an equivalent <wity-knowledge> document. One-time upgrade path for stored documents.
import { rkfToKnowledge } from '@wity-ai/rkf-core';
const knowledgeXml = rkfToKnowledge(oldRkfXml);initState(doc) -> Object
Build the initial state object from all rkf-var declarations in the document (document-level and nested in sections/machines).
const state = initState(doc);
// { x: 0.5, mode: 'linear', running: false, ... }evaluate(doc, state) -> ComputedFrame
Evaluate the document against current state. Returns a ComputedFrame:
const frame = evaluate(doc, state);
// {
// state: { ... }, // resolved state (including derives)
// marks: [ ... ], // all visible marks, recursively collected
// controls: [ ... ], // all visible dir="in" ports
// envs: [ ... ], // all visible environments (prose, math, image, etc.)
// gates: { ... }, // named gate values (Object<string, boolean>)
// outputs: [ ... ], // all dir="out" ports
// elastics: [ ... ], // elastic variable reset info
// sections: [ ... ], // section tree with resolved visibility
// }Pure function. No side effects. Call with any state at any time.
An evaluated machine's ports each carry the current value of their var (derives included) — what a control or a readout shows.
applyUpdate(update, state, event, gateValues?) -> Object
Apply a single transform rule to state. Returns new state (or same reference if no changes). Accepts optional gateValues parameter for pre-computed gate evaluations.
const newState = applyUpdate(tickTransform, state, 'tick');
const newState2 = applyUpdate(tickTransform, state, 'tick', { isRunning: true });Checks update.on against event, evaluates trigger (gate id or expression), applies rkf-set assignments.
applyUpdates(updates, state, event, gateValues?) -> Object
Apply all transforms sequentially for a given event. Accepts optional gateValues parameter.
state = applyUpdates(doc.transforms, state, 'tick');
state = applyUpdates(doc.transforms, state, 'tick', frame.gates);serialize(doc) -> string
Serialize an RkfDocument to a <wity-knowledge> document. Requires the graph module (see Setup). Round-trips cleanly with parse().
const xml = serialize(doc); // <wity-knowledge version="1"> … </wity-knowledge>validate(doc) ->
Validate a parsed document against the schema. Checks required attributes, duplicate var IDs, structural constraints, and — via the dependency graph — illegal combinational cycles among derives/gates (legal transform feedback such as t = t + 1 is not flagged).
const result = validate(doc);
if (!result.valid) {
console.error(result.errors);
}evalExpr(expr, state) -> any
Evaluate a single expression string against state. Safe, hand-rolled recursive descent parser.
evalExpr('-w1 / w2', { w1: 0.5, w2: -0.3 }); // 1.6667
evalExpr('epoch > 10 ? "done" : "training"', { epoch: 15 }); // "done"&& and || give the same value as JavaScript's. Both sides are always parsed; expressions have no side effects.
evalGate(gate, state) -> boolean
Evaluate a gate condition. Returns true if gate is null/undefined (always visible).
evalGate('mode == "linear"', { mode: 'linear' }); // true
evalGate(null, state); // true (no gate = always visible)Editing
RkfEdit is the headless edit API over the document model — sections, cards, blocks, links, drawing marks and machines, each addressed by a stable id. See RkfEdit — the edit API.
Schema constants
| Constant | Contents |
|---|---|
SCHEMA_VERSION | '2.0' |
ELEMENT_TAGS | All recognised RKF element tag names |
ENV_TAGS | ['rkf-env'] |
STRUCTURE_TAGS | ['rkf-section', 'rkf-machine', 'rkf-canvas', 'rkf-group'] |
STATE_TAGS | ['rkf-var', 'rkf-derive', 'rkf-gate', 'rkf-transform', 'rkf-set'] |
LAYOUT_TAGS | ['rkf-scale', 'rkf-axis'] |
MARK_TAGS | ['rkf-scatter', 'rkf-line', 'rkf-arrow', 'rkf-rect', 'rkf-arc', 'rkf-circle', 'rkf-text', 'rkf-image', 'rkf-path', 'rkf-area'] |
PORT_TAGS | ['rkf-port'] |
PORT_TYPES | ['continuous', 'choice', 'boolean', 'clock', 'increment', 'event'] |
PORT_DIRECTIONS | ['in', 'out'] |
ENV_TYPES | ['prose', 'math', 'image', 'video', 'model', 'audio'] |
BLOCK_KINDS | ['text', 'image', 'math', 'embed', 'machine', 'mermaid', 'row'] |
TEXT_ANCHOR_VALUES | ['start', 'middle', 'end'] |
SCALE_TYPE_VALUES | ['linear', 'log', 'ordinal'] |
AXIS_ORIENT_VALUES | ['bottom', 'left', 'top', 'right'] |
MATH_DISPLAY_VALUES | ['block', 'inline'] |
Types (JSDoc)
All types are defined in schema/types.js via JSDoc @typedef. Key types:
RkfDocument-- root parsed documentRkfSection,RkfEnv-- contentRkfMachine-- state machine block (recursive)RkfVar,RkfGate,RkfDerive,RkfTransform,RkfSet-- stateRkfPort-- typed I/O interface (dir=in|out)RkfCanvas,RkfScale,RkfAxis,RkfGroup-- layoutRkfScatter,RkfLine,RkfArrow,RkfRect,RkfCircle,RkfArc,RkfText-- marksComputedMark,ComputedFrame-- evaluation output
