Skip to content

@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 ​

bash
npm install @wity-ai/rkf-core
# Node.js only (optional XML peer):
npm install @xmldom/xmldom

Import ​

js
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:

js
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.

js
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().

js
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.

js
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).

js
const state = initState(doc);
// { x: 0.5, mode: 'linear', running: false, ... }

evaluate(doc, state) -> ComputedFrame ​

Evaluate the document against current state. Returns a ComputedFrame:

js
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.

js
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.

js
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().

js
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).

js
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.

js
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).

js
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 ​

ConstantContents
SCHEMA_VERSION'2.0'
ELEMENT_TAGSAll 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 document
  • RkfSection, RkfEnv -- content
  • RkfMachine -- state machine block (recursive)
  • RkfVar, RkfGate, RkfDerive, RkfTransform, RkfSet -- state
  • RkfPort -- typed I/O interface (dir=in|out)
  • RkfCanvas, RkfScale, RkfAxis, RkfGroup -- layout
  • RkfScatter, RkfLine, RkfArrow, RkfRect, RkfCircle, RkfArc, RkfText -- marks
  • ComputedMark, ComputedFrame -- evaluation output