Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

WebAssembly Integration

Spindle compiles to WebAssembly for use in browsers and Node.js.

Building

Prerequisites

cargo install wasm-pack

Build for Web

cd crates/spindle-wasm
wasm-pack build --target web --release

Build for Node.js

wasm-pack build --target nodejs --release

Build for Bundlers (webpack, etc.)

wasm-pack build --target bundler --release

Installation

npm/yarn

npm install spindle-wasm
# or
yarn add spindle-wasm

From Local Build

// Point to your build output
import init, { Spindle } from './pkg/spindle_wasm.js';

Browser Usage

ES Modules

<script type="module">
import init, { Spindle } from './pkg/spindle_wasm.js';

async function main() {
    await init();

    const spindle = new Spindle();
    spindle.addFact("bird");
    spindle.addFact("penguin");
    spindle.addDefeasibleRule(["bird"], "flies");
    spindle.addDefeasibleRule(["penguin"], "~flies");
    spindle.addSuperiority("r2", "r1");

    const conclusions = spindle.reason();
    console.log(conclusions);
}

main();
</script>

With Bundler (Vite, webpack)

import init, { Spindle } from 'spindle-wasm';

async function setup() {
    await init();
    return new Spindle();
}

const spindle = await setup();

Node.js Usage

const { Spindle } = require('spindle-wasm');

const spindle = new Spindle();

spindle.parseDfl(`
    f1: >> bird
    f2: >> penguin
    r1: bird => flies
    r2: penguin => ~flies
    r2 > r1
`);

const conclusions = spindle.reason();
console.log(conclusions);

API Reference

Constructor

const spindle = new Spindle();

Creates a new empty theory.

Adding Facts

spindle.addFact("bird");
spindle.addFact("~guilty");  // Negated fact

Adding Rules

// Defeasible rules
spindle.addDefeasibleRule(["bird"], "flies");
spindle.addDefeasibleRule(["bird", "healthy"], "strong_flyer");

// Strict rules
spindle.addStrictRule(["penguin"], "bird");

// Defeaters
spindle.addDefeater(["broken_wing"], "flies");

Superiority

spindle.addSuperiority("r2", "r1");  // r2 > r1

Parsing

// Parse DFL
spindle.parseDfl(`
    f1: >> bird
    r1: bird => flies
`);

// Parse SPL
spindle.parseSpl(`
    (given bird)
    (normally r1 bird flies)
`);

Reasoning

const conclusions = spindle.reason();
// Returns: Array of conclusion objects

// Each conclusion:
{
    conclusion_type: "+D" | "-D" | "+d" | "-d",
    literal: string,
    positive: boolean
}

Scalable Reasoning

const conclusions = spindle.reasonScalable();

Query

// Check if a literal is provable
const result = spindle.query("flies");
// Returns: { status: "provable" | "not_provable", literal, conclusion_type }

What-If

const result = spindle.whatIf(["wounded"], "~flies");
// Returns: { provable: boolean, new_conclusions: Array }

Why-Not

const explanation = spindle.whyNot("flies");
// Returns: { literal, would_derive, blockers: Array }

Abduction

const solutions = spindle.abduce("goal", 3);
// Returns: { goal, solutions: Array<Array<string>> }

Reset

spindle.clear();  // Clear all rules and facts

TypeScript Types

interface Conclusion {
    conclusion_type: "+D" | "-D" | "+d" | "-d";
    literal: string;
    positive: boolean;
}

interface QueryResult {
    status: "provable" | "not_provable";
    literal: string;
    conclusion_type?: string;
}

interface WhatIfResult {
    provable: boolean;
    new_conclusions: Conclusion[];
}

interface WhyNotExplanation {
    literal: string;
    would_derive: string | null;
    blockers: Blocker[];
}

interface Blocker {
    rule: string;
    reason: string;
}

interface AbductionResult {
    goal: string;
    solutions: string[][];
}

Complete Example

import init, { Spindle } from 'spindle-wasm';

async function reasonAboutPenguins() {
    await init();

    const spindle = new Spindle();

    // Build theory
    spindle.parseDfl(`
        f1: >> bird
        f2: >> penguin

        r1: bird => flies
        r2: bird => has_feathers
        r3: penguin => ~flies
        r4: penguin => swims

        r3 > r1
    `);

    // Reason
    const conclusions = spindle.reason();

    // Filter positive conclusions
    const positive = conclusions.filter(c =>
        c.conclusion_type === "+D" || c.conclusion_type === "+d"
    );

    console.log("Provable:", positive.map(c => c.literal));

    // Query specific literal
    const fliesResult = spindle.query("flies");
    console.log("Does it fly?", fliesResult.status);

    // What-if analysis
    const whatIf = spindle.whatIf(["super_bird"], "flies");
    console.log("With super_bird:", whatIf.provable);

    // Explain why not
    if (fliesResult.status === "not_provable") {
        const explanation = spindle.whyNot("flies");
        console.log("Why not flies:", explanation.blockers);
    }
}

reasonAboutPenguins();

React Integration

import { useState, useEffect } from 'react';
import init, { Spindle } from 'spindle-wasm';

function useSpindle() {
    const [spindle, setSpindle] = useState<Spindle | null>(null);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        init().then(() => {
            setSpindle(new Spindle());
            setLoading(false);
        });
    }, []);

    return { spindle, loading };
}

function ReasoningComponent() {
    const { spindle, loading } = useSpindle();
    const [conclusions, setConclusions] = useState([]);

    const reason = () => {
        if (!spindle) return;

        spindle.reset();
        spindle.addFact("bird");
        spindle.addDefeasibleRule(["bird"], "flies");

        const result = spindle.reason();
        setConclusions(result);
    };

    if (loading) return <div>Loading WASM...</div>;

    return (
        <div>
            <button onClick={reason}>Reason</button>
            <ul>
                {conclusions.map((c, i) => (
                    <li key={i}>{c.conclusion_type} {c.literal}</li>
                ))}
            </ul>
        </div>
    );
}

Performance Notes

  1. Initialize once: Call init() once at startup
  2. Reuse Spindle instances: Create once, reset between uses
  3. Batch operations: Add all rules before reasoning
  4. Use scalable for large theories: reasonScalable() for >1000 rules

Bundle Size

Approximate sizes (gzipped):

  • Core WASM: ~150KB
  • JavaScript bindings: ~10KB

Browser Compatibility

Requires WebAssembly support:

  • Chrome 57+
  • Firefox 52+
  • Safari 11+
  • Edge 16+
  • Node.js 8+