Observed since 20 Oct, 19:32 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
3d-ast-generator
A TypeScript library for parsing Merfolk syntax and generating 3D abstract syntax trees for visualization
A TypeScript library for parsing Merfolk syntax and generating 3D abstract syntax trees for visualization. Perfect for creating 3D diagrams of application architectures, data flows, and system relationships.
npm install 3d-ast-generator
import { MarkdownProcessor, AST3DGenerator } from '3d-ast-generator';
const generator = new AST3DGenerator();
const syntax = `
%% My Application Architecture
App{Component: Main Application}
DataService[Function: Process Data]
UserInterface{Component: User Interface}
Database[[Store: Database]]
ThreeJS<Library: Three.js>
%% Data flow connections
App --> UserInterface : "renders"
App --> DataService : "uses"
DataService --> Database : "queries"
UserInterface --> App : "user events"
App --> ThreeJS : "3D rendering"
`;
const graph = generator.generate(syntax);
console.log(
`Generated ${graph.nodes.size} nodes and ${graph.connections.size} connections`
);
// Export for your 3D application
const jsonData = generator.generateJSON(syntax);
// Or process markdown files with multiple diagrams
const processor = new MarkdownProcessor({
layout: { algorithm: 'hierarchical', nodeSpacing: 4.0 },
visual: { theme: 'dark' },
});
const markdownContent = `
# My Architecture
\`\`\`merfolk
%% System Overview
API[Function: API Server]
UI{Component: User Interface}
Database[[Store: Database]]
API --> UI : "data"
API --> Database : "queries"
\`\`\`
`;
const diagrams = processor.processMarkdown(markdownContent);
diagrams.forEach((diagram) => {
if (diagram.errors.length === 0) {
console.log(`Diagram: ${diagram.block.title || 'Untitled'}`);
console.log(`Nodes: ${diagram.graph.nodes.size}`);
}
});
| Syntax | Type | Geometry | Use Case |
|---|---|---|---|
A{Component: name} | Component | Dodecahedron | Components, modules |
B[Function: name] | Function | Cube | Functions, methods |
C[[Store: name]] | Store | Cube | Databases, data stores |
D((Service: name)) | Service | Tetrahedron | External services, APIs |
E<Library: name> | Library | Cube | External libraries |
F[Hook: name] | Hook | Cube | React hooks, custom hooks |
Node Type Detection: The 3D AST generator automatically detects node types based on the bracket style and content label (e.g., "Component:", "Function:", "Store:", "Hook:", etc.).
| Syntax | Type | Style | Use Case |
| ---------- | ------------ | ------------ | ------------------------ | ----------- | ----------------- |
| A --> B | Data Flow | Solid arrow | Data passing |
| A --> | "label" | B | Labeled Flow | Solid arrow | Labeled data flow |
| A -.-> B | Control Flow | Dashed arrow | Event/control flow |
| A --- B | Association | Solid line | General relationships |
| A == B | Inheritance | Thick line | Inheritance/dependencies |
Labeled Connections: Use -->|"label"| syntax to add descriptive labels to connections, which will be displayed on the 3D connection lines.
Connect to specific faces of 3D objects:
A@front --> B@back : "direct connection"
C@top --> D@bottom : "vertical flow"
Available faces:
front, back, top, bottom, left, rightface_0 through face_11A --> B : "Connection Label"
C{Component: MyComp} {color: "blue", scale: "2,1,1"}
Flow paths let you define and trace complete data paths that span multiple nodes across your application — not just individual point-to-point connections.
flowpath DirectiveDefine a named, multi-hop data path in a single line. This auto-creates tagged connections between each adjacent pair of nodes:
flowpath "userDataFlow" : A --> B --> C --> D
This creates 3 connections (A→B, B→C, C→D), all tagged with the userDataFlow identifier so the entire path can be queried and traced as a unit.
Full syntax:
%% Basic flow path
flowpath "name" : NodeA --> NodeB --> NodeC
%% With a custom arrow type (applies to all connections in the path)
flowpath "eventPipeline" (-.->): Input --> Transform --> Output
%% With a description
flowpath "requestLifecycle" : Client --> API --> DB --> API --> Client : "full request cycle"
#tag Syntax on ConnectionsTag individual connections with one or more flow path names using #:
A --> B : "payload" #userDataFlow
B --> C #userDataFlow #auditTrail
C --> D #auditTrail
This is useful when you want to manually compose flow paths from existing connections rather than auto-generating them.
You can freely mix flowpath directives with #tag connections. If a flowpath references a connection that already exists, it tags the existing connection instead of creating a duplicate:
%% Nodes
UI{Component: User Interface}
API[Function: API Handler]
Auth[Function: Auth Service]
DB[[Store: Database]]
Cache[Function: Cache Layer]
%% Explicit connections
UI --> API : "request" #userFlow
API --> Auth : "validate"
%% Flow path reuses existing UI-->API connection, creates the rest
flowpath "userFlow" : UI --> API --> Auth --> DB
%% A separate flow path through the cache layer
flowpath "cachedRead" : UI --> API --> Cache --> DB
const graph = generator.generate(syntax);
// Trace a complete data path — returns ordered nodes and connections
const trace = graph.traceFlowPath('userDataFlow');
console.log(trace.nodes.map(n => n.name)); // ['UI', 'API Handler', 'Auth Service', 'DB']
console.log(trace.connections.length); // 3
// Which flow paths pass through a specific node?
const apiFlows = graph.getNodeFlowPaths('API');
console.log(apiFlows.map(fp => fp.name)); // ['userFlow', 'cachedRead']
// Get all connections belonging to a flow path
const conns = graph.getFlowPathConnections('cachedRead');
// Find all flow paths that connect two nodes (even indirectly)
const paths = graph.getFlowPathsBetween('UI', 'DB');
console.log(paths.length); // 2 (userFlow and cachedRead both reach from UI to DB)
// List all defined flow paths
const all = graph.getAllFlowPaths();
Flow paths are included in generateJSON() output:
{
"flowPaths": [
{
"id": "flowpath_0",
"name": "userDataFlow",
"nodeSequence": ["UI", "API", "Auth", "DB"],
"connectionIds": ["conn_0", "conn_1", "conn_2"]
}
]
}
Each connection also carries a flowPaths array listing which flow paths it belongs to, making it easy to highlight or filter connections by path in your 3D visualization.
The 3D AST generator automatically creates nested grouping when functions are connected to components:
%% Define components and functions
UserService{Component: User Service}
AuthService{Component: Authentication Service}
validateUser[Function: User Validation]
authenticateToken[Function: Token Authentication]
%% Connect functions to components (creates nesting)
validateUser --> UserService : "validates"
authenticateToken --> AuthService : "authenticates"
Result: Functions automatically become nested inside their connected components:
const generator = new AST3DGenerator({
layout: {
algorithm: 'hierarchical', // 'hierarchical' | 'force-directed' | 'circular' | 'grid'
nodeSpacing: 3.0,
layers: 4,
},
visual: {
theme: 'dark', // 'dark' | 'light' | 'custom'
colors: {
function: '#4CAF50',
component: '#2196F3',
datapath: '#FF9800',
},
material: {
metalness: 0.1,
roughness: 0.7,
opacity: 0.9,
},
},
});
import {
// Main Classes
AST3DGenerator,
MarkdownProcessor,
// Core Models
Node,
Connection,
Graph,
// Parsers
MermaidParser,
ASTBuilder,
// Utilities
PositionCalculator,
Validator,
Helpers,
// Types
NodeType,
ConnectionType,
GeometryType,
FlowPath,
ParsedFlowPath,
// Configuration
DEFAULT_CONFIG,
} from '3d-ast-generator';
Main class for generating 3D AST graphs from Merfolk syntax.
generate(input: string): Graph - Parse and generate 3D graphgenerateJSON(input: string): AST3DGraph - Generate serializable formatvalidate(input: string): ValidationResult - Validate syntaxparseOnly(input: string): ParsedGraph - Parse without buildingProcess markdown files containing multiple Merfolk code blocks.
const processor = new MarkdownProcessor(config?: {
layout?: { algorithm: string, nodeSpacing: number },
visual?: { theme: string, colors: object }
});
processMarkdown(content: string): ProcessedDiagram[] - Extract and process all Merfolk blocksextractMerfolkBlocks(content: string): MerfolkBlock[] - Extract code blocks onlyRepresents a 3D AST graph with nodes and connections.
nodes: Map<string, Node> - All nodes in the graphconnections: Map<string, Connection> - All connectionsflowPaths: Map<string, FlowPath> - All registered flow pathsbounds: BoundingBox - Overall graph bounding boxaddNode(node: Node): void - Add a nodeaddConnection(connection: Connection): void - Add a connectiongetBounds(): BoundingBox - Calculate bounding boxgetRootNodes(): Node[] - Get nodes with no parentsgetLeafNodes(): Node[] - Get nodes with no childrenaddFlowPath(flowPath: FlowPath): void - Register a flow pathremoveFlowPath(flowPathId: string): void - Remove a flow pathtraceFlowPath(name: string): { nodes: Node[], connections: Connection[] } | null - Trace a complete data pathgetNodeFlowPaths(nodeId: string): FlowPath[] - Get all flow paths through a nodegetFlowPathConnections(name: string): Connection[] - Get connections in a flow pathgetFlowPathsBetween(nodeA: string, nodeB: string): FlowPath[] - Find flow paths connecting two nodesgetFlowPathByName(name: string): FlowPath | undefined - Look up a flow path by namegetAllFlowPaths(): FlowPath[] - List all registered flow pathsRepresents a 3D node with geometry and connections.
id: string - Unique identifiertype: NodeType - Node type (function, component, etc.)geometry: GeometryType - 3D geometry typetransform: Transform3D - Position, rotation, scalefaces: Face[] - Available connection facesvisual: VisualProperties - Visual stylingRepresents a connection between two nodes.
source: ConnectionPoint - Source node and facetarget: ConnectionPoint - Target node and facetype: ConnectionType - Connection typewaypoints: Position3D[] - Path waypointsvisual: VisualProperties - Visual stylingflowPaths: string[] - Flow path names this connection belongs toaddFlowPath(name: string): void - Tag this connection with a flow pathremoveFlowPath(name: string): void - Remove a flow path tagbelongsToFlowPath(name: string): boolean - Check if connection is part of a flow pathThe library outputs standardized 3D data that can be consumed by various 3D engines:
import * as THREE from 'three';
import { MarkdownProcessor } from '3d-ast-generator';
// Process markdown with Merfolk diagrams
const processor = new MarkdownProcessor();
const markdownContent = `
\`\`\`merfolk
%% My System Architecture
WebServer[Function: Web Server]
Database{Component: Database Service}
APIGateway((Service: API Gateway))
APIGateway --> WebServer : "routes requests"
WebServer --> Database : "queries data"
\`\`\`
`;
const diagrams = processor.processMarkdown(markdownContent);
// Create Three.js scene
const scene = new THREE.Scene();
diagrams.forEach((diagram, diagramIndex) => {
if (diagram.errors.length === 0) {
const graph = diagram.graph;
// Add nodes as 3D objects
for (const [nodeId, node] of graph.nodes) {
let geometry;
switch (node.geometry) {
case 'cube':
geometry = new THREE.BoxGeometry(
node.transform.scale.x,
node.transform.scale.y,
node.transform.scale.z
);
break;
case 'dodecahedron':
geometry = new THREE.DodecahedronGeometry(1);
break;
case 'tetrahedron':
geometry = new THREE.TetrahedronGeometry(1);
break;
}
const material = new THREE.MeshStandardMaterial({
color: node.visual.color || '#ffffff',
opacity: node.visual.opacity || 1.0,
transparent: true,
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(
node.transform.position.x + diagramIndex * 20,
node.transform.position.y,
node.transform.position.z
);
scene.add(mesh);
}
// Add connections as lines
for (const [connId, connection] of graph.connections) {
const points = [
new THREE.Vector3(
connection.source.anchor?.x || 0,
connection.source.anchor?.y || 0,
connection.source.anchor?.z || 0
),
...connection.waypoints.map((p) => new THREE.Vector3(p.x, p.y, p.z)),
new THREE.Vector3(
connection.target.anchor?.x || 0,
connection.target.anchor?.y || 0,
connection.target.anchor?.z || 0
),
];
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const material = new THREE.LineBasicMaterial({
color: connection.visual.color || '#ffffff',
});
const line = new THREE.Line(geometry, material);
scene.add(line);
}
}
});
See the examples/ directory for complete usage examples:
basic-usage.ts - Simple API usagesample.md - Complex application architecture examplegit checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)MIT License - see LICENSE file for details.
You can automatically generate 3D diagrams by uploading markdown files containing Merfolk syntax:
npm install 3d-ast-generator
import { MarkdownProcessor } from '3d-ast-generator';
// Create processor with configuration
const processor = new MarkdownProcessor({
layout: {
algorithm: 'hierarchical',
nodeSpacing: 4.0,
layers: 5,
},
visual: {
theme: 'dark',
colors: {
function: '#4CAF50',
component: '#2196F3',
datapath: '#FF9800',
},
},
});
// Process markdown file upload
const handleFileUpload = async (file: File) => {
try {
const content = await file.text();
const diagrams = processor.processMarkdown(content);
const validDiagrams = diagrams.filter((d) => d.errors.length === 0);
const errors = diagrams
.filter((d) => d.errors.length > 0)
.flatMap((d) => d.errors);
if (validDiagrams.length > 0) {
console.log(`Generated ${validDiagrams.length} 3D diagrams!`);
// Process each diagram for your 3D application
validDiagrams.forEach((diagram, index) => {
const graph = diagram.graph;
console.log(`Diagram ${index + 1}:`, {
title: diagram.block.title || 'Untitled',
nodes: graph.nodes.size,
connections: graph.connections.size,
bounds: graph.getBounds(),
});
// Add to your 3D scene here
addDiagramToScene(graph, index);
});
} else {
console.error('No valid diagrams found:', errors);
}
} catch (error) {
console.error('Upload failed:', error);
}
};
// Helper function to add diagram to your 3D scene
function addDiagramToScene(graph, diagramIndex) {
// Implementation depends on your 3D library (Three.js, Babylon.js, etc.)
// See the Three.js example above for complete implementation
}
// Simple file upload handler
function setupFileUpload() {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.md,.markdown';
fileInput.multiple = true;
fileInput.addEventListener('change', async (e) => {
const files = Array.from(e.target.files || []);
for (const file of files) {
if (file.name.endsWith('.md') || file.name.endsWith('.markdown')) {
await handleFileUpload(file);
}
}
});
// Trigger file selection
fileInput.click();
}
// Or drag and drop
function setupDragAndDrop(dropZone: HTMLElement) {
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('dragover');
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('dragover');
});
dropZone.addEventListener('drop', async (e) => {
e.preventDefault();
dropZone.classList.remove('dragover');
const files = Array.from(e.dataTransfer?.files || []);
for (const file of files) {
if (file.type === 'text/markdown' || file.name.endsWith('.md')) {
await handleFileUpload(file);
}
}
});
}
Your markdown files should contain Merfolk code blocks:
# My Architecture
This document describes our system architecture.
\`\`\`merfolk
%% System Overview
WebServer[Function: Web Server]
Database{Component: Database Service}
APIGateway((Service: API Gateway))
Cache[Function: Cache System]
APIGateway --> WebServer : "routes requests"
WebServer --> Database : "queries data"
Database --> Cache : "caches results"
\`\`\`
## Data Flow
\`\`\`merfolk
%% User Registration Flow
SignupForm{Component: Signup Form}
UserAPI[Function: User API]
UserDB[[Store: User Database]]
EmailService((Service: Email Service))
SignupForm --> UserAPI : "submits form"
UserAPI --> UserDB : "stores user"
UserAPI -.-> EmailService : "sends welcome email"
\`\`\`
Here's a comprehensive example showing all geometry types and connection features:
\`\`\`merfolk
%% Complete Application Architecture
%% Components (Dodecahedrons, can become containers for functions)
App{Component: Main Application}
UI{Component: User Interface}
API{Component: API Gateway}
%% Functions (Cubes, can be nested in components)
processData[Function: Data Processing]
validateUser[Function: User Validation]
renderUI[Function: UI Rendering]
%% Hooks (Cubes, for React hooks and custom hooks)
useAuth[Hook: useAuth]
useForm[Hook: useForm]
useTheme[Hook: useTheme]
%% Stores (Cubes)
UserDB[[Store: User Database]]
ConfigDB[[Store: Configuration Store]]
%% External Services (Tetrahedrons)
PaymentAPI((Service: Payment Gateway))
EmailService((Service: Email Provider))
%% Libraries (Cubes)
ReactLib<Library: React>
ExpressLib<Library: Express.js>
%% Labeled connections with nested grouping
processData --> API : "processes requests"
validateUser --> API : "validates tokens"
renderUI --> UI : "renders components"
%% Hook connections
useAuth --> UI : "provides auth state"
useForm --> UI : "manages form state"
useTheme --> UI : "provides theme"
%% Component connections
App --> UI : "renders"
App --> API : "calls"
API --> UserDB : "queries"
API --> PaymentAPI : "payment processing"
API -.-> EmailService : "notifications"
%% Library dependencies
UI --> ReactLib : "uses"
API --> ExpressLib : "uses"
%% Face-specific connections
App@front --> UI@back : "direct rendering"
API@top --> UserDB@bottom : "data flow"
\`\`\`
Result: This creates:
Each Merfolk syntax element becomes a 3D object:
{Component: name} → Dodecahedron mesh (becomes container when containing functions)[Function: name] → Cube mesh (can be nested inside components)[Hook: name] → Cube mesh (pink #E91E63) for React hooks and custom hooks[[Store: name]] → Cube mesh for databases and data stores((Service: name)) → Tetrahedron mesh for external services<Library: name> → Cube mesh for external librariesimport { MarkdownProcessor } from '3d-ast-generator';
async function processArchitectureFile(file: File) {
const processor = new MarkdownProcessor({
visual: {
theme: 'dark',
colors: {
function: '#4CAF50', // Green cubes
component: '#2196F3', // Blue dodecahedrons
datapath: '#FF9800', // Orange planes
},
},
});
try {
const content = await file.text();
const diagrams = processor.processMarkdown(content);
console.log(`Processed ${file.name}:`);
diagrams.forEach((diagram, i) => {
if (diagram.errors.length === 0) {
const graph = diagram.graph;
console.log(
` Diagram ${i + 1}: ${graph.nodes.size} nodes, ${
graph.connections.size
} connections`
);
// Convert to your 3D application format
const diagramData = {
title: diagram.block.title,
nodes: Array.from(graph.nodes.values()).map((node) => ({
id: node.id,
type: node.type,
geometry: node.geometry,
position: node.transform.position,
color: node.visual.color,
})),
connections: Array.from(graph.connections.values()).map((conn) => ({
source: conn.source.nodeId,
target: conn.target.nodeId,
type: conn.type,
})),
};
// Add to your 3D scene, save to database, etc.
addToScene(diagramData);
} else {
console.error(` Diagram ${i + 1} has errors:`, diagram.errors);
}
});
} catch (error) {
console.error('Processing failed:', error);
}
}
This makes it incredibly easy to convert documentation into interactive 3D visualizations!
Download counts and popularity will appear here as the collector receives them.
| Package / target | Requirement | Relationship |
|---|---|---|
commanderEnvironment{
"local_name": "commander",
"alias": false,
"peer_metadata": null
} | ^11.0.0 | runtime |
@types/jestEnvironment{
"local_name": "@types/jest",
"alias": false,
"peer_metadata": null
} | ^29.5.0 | development |
@types/nodeEnvironment{
"local_name": "@types/node",
"alias": false,
"peer_metadata": null
} | ^20.0.0 | development |
@typescript-eslint/eslint-pluginEnvironment{
"local_name": "@typescript-eslint/eslint-plugin",
"alias": false,
"peer_metadata": null
} | ^6.0.0 | development |
@typescript-eslint/parserEnvironment{
"local_name": "@typescript-eslint/parser",
"alias": false,
"peer_metadata": null
} | ^6.0.0 | development |
eslintEnvironment{
"local_name": "eslint",
"alias": false,
"peer_metadata": null
} | ^8.0.0 | development |
jestEnvironment{
"local_name": "jest",
"alias": false,
"peer_metadata": null
} | ^29.5.0 | development |
rimrafEnvironment{
"local_name": "rimraf",
"alias": false,
"peer_metadata": null
} | ^5.0.0 | development |
ts-jestEnvironment{
"local_name": "ts-jest",
"alias": false,
"peer_metadata": null
} | ^29.1.0 | development |
typescriptEnvironment{
"local_name": "typescript",
"alias": false,
"peer_metadata": null
} | ^5.0.0 | development |
Source distribution
{
"integrity": "sha512-DFh7xV8wF1On1tiY4XJkDzmzTTCnOJzY4auDFn2hWlg8llzovDRWh7KZFB7KYYd5ZJeo1O7LBLk+jMifE+bn6w==",
"shasum": "3196ad615064c35051a4395da4701c29a5bea92c",
"tarball": "https://registry.npmjs.org/3d-ast-generator/-/3d-ast-generator-1.0.18.tgz",
"fileCount": 79,
"unpackedSize": 359799,
"signatures": [
{
"keyid": "SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U",
"sig": "MEUCIQD/9cgi+0UfqkLKQ+7+m8kyIXZukzXfuiaBDr5CKimKWAIgUEh0t881yZr07v7ACXgb+MHhoXZeL+daZ37H84z0dNo="
}
]
}Shared repository data · GitHub
Observed since 20 Oct, 19:32 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package_version:3cfe4833167e7a1d6b00ce72d370da2e84ec6ea0f004c1396a13289932d2b1a2