This document describes the integration of link-cli (clink) as the database solution for storing sidebar menu items and other hierarchical data in the DronDoc application.
Issue: #1800 - База данных Implementation Date: 2025-11-02 Technology: link-cli (clink) v2.2.2 - Associative link-based data storage
link-cli (also called clink, CLInk, or cLINK) is a command-line tool for manipulating links using a single substitution operation. It's built on:
- Associative theory and the Links Notation protocol
- Markov algorithms for pattern-matching substitutions
- Turing-complete operation model
- C# implementation of a links data store
A "link" in link-cli is a triple: (id: source target)
- id: Unique identifier for the link
- source: Source node/value
- target: Target node/value
Links can represent:
- Parent-child relationships in hierarchies
- Key-value pairs
- Graph structures
- Any associative relationship
- .NET SDK 8.0+ (already installed on the system)
dotnet tool install --global clinkVerify installation:
clink --helpThe integration uses a hybrid approach:
- link-cli database (
menu.links): Stores relationships and hierarchical structure - JSON files (
data/menu-items/*.json): Stores actual menu item content
- Links are perfect for: Hierarchical relationships, parent-child structures
- JSON files are better for: Complex objects with multiple properties
- Together: Efficient storage and retrieval of hierarchical menu structures
Link: (menuItemId, parentId)
Where:
- menuItemId: Hash-based ID derived from menu item content
- parentId: ID of parent menu item (0 = root level)
Example:
(3: 100 0) // Menu item 100 is at root level (parent = 0)
(4: 200 0) // Menu item 200 is at root level
(5: 101 100) // Menu item 101 is child of item 100
backend/monolith/
├── src/
│ ├── services/
│ │ └── linkdb/
│ │ ├── link-db-service.js # Low-level link-cli wrapper
│ │ └── menu-storage-service.js # High-level menu storage
│ └── api/
│ └── routes/
│ ├── menuConfigLinkDB.js # New link-cli based API routes
│ └── menuConfig.js # Legacy JSON-based routes
└── data/
├── menu.links # Link database file
└── menu-items/ # Menu item JSON files
├── 12345678.json
└── 87654321.json
Low-level service for executing link-cli commands.
Key Methods:
executeQuery(query, options): Execute a LiNo querycreateLink(source, target): Create a new linkreadAllLinks(): Read all linksupdateLink(id, newSource, newTarget): Update a linkdeleteLink(id): Delete a link
Example Usage:
import LinkDBService from './services/link-db-service.js';
const linkDB = new LinkDBService();
// Create a link
const link = await linkDB.createLink(100, 0); // (id: 100 0)
// Read all links
const allLinks = await linkDB.readAllLinks();
// Delete a link
await linkDB.deleteLink(link.id);High-level service for menu storage with link-cli.
Key Methods:
storeMenuItem(item, parentId): Store a single menu itemstoreMenuStructure(menuItems, parentId): Store menu hierarchy recursivelygetMenuStructure(parentId): Retrieve menu hierarchygetAllMenuItems(): Get flat list of all itemsdeleteMenuItem(itemId): Delete item and childrenclearAllMenus(): Clear all menu data
Example Usage:
import MenuStorageService from './services/menu-storage-service.js';
const menuStorage = new MenuStorageService();
// Store menu structure
const menu = [
{
label: 'Home',
icon: 'pi pi-home',
to: '/home'
},
{
label: 'Settings',
icon: 'pi pi-cog',
items: [
{ label: 'Profile', to: '/settings/profile' },
{ label: 'Security', to: '/settings/security' }
]
}
];
await menuStorage.storeMenuStructure(menu, 0);
// Retrieve menu
const retrievedMenu = await menuStorage.getMenuStructure(0);Base Path: /api/menu/*
Get current menu configuration from link-cli database.
Response:
{
"success": true,
"response": {
"config": "[{\"label\":\"Home\",\"icon\":\"pi pi-home\",...}]",
"updatedAt": "2025-11-02T10:30:00.000Z",
"source": "linkdb"
}
}Save menu configuration to link-cli database.
Request:
{
"config": "[{\"label\":\"Home\",\"icon\":\"pi pi-home\",...}]"
}Response:
{
"success": true,
"message": "Menu configuration saved successfully to link-cli database",
"response": {
"updatedAt": "2025-11-02T10:30:00.000Z",
"source": "linkdb"
}
}Delete all menu configuration.
Get all menu items as a flat list.
Add a single menu item.
Request:
{
"item": {
"label": "New Item",
"icon": "pi pi-star",
"to": "/new"
},
"parentId": 0
}Delete a menu item and all its children.
Get storage statistics.
Response:
{
"success": true,
"response": {
"totalLinks": 15,
"totalFiles": 15,
"rootItems": 5,
"source": "linkdb"
}
}Base Path: /api/menu-legacy/*
The old JSON file-based routes are still available at /api/menu-legacy/config for migration and compatibility.
Replace nothing with something:
clink '() ((1 1))' --changes --afterCreates link: (1: 1 1)
Match and return (no net change):
clink '((($i: $s $t)) (($i: $s $t)))' --afterReads all links.
Substitute one pattern for another:
clink '((1: 1 1)) ((1: 1 2))' --changes --afterUpdates link 1 to (1: 1 2).
Replace something with nothing:
clink '((1 2)) ()' --changes --afterDeletes the link.
Variables in queries:
$i: Match any value (typically used for ID)$s: Match any value (typically used for source)$t: Match any value (typically used for target)
| Option | Description |
|---|---|
--db <path> |
Database file path (default: db.links) |
--changes / -c |
Show applied changes |
--after / -a |
Display database state post-operation |
--before / -b |
Display pre-operation state |
--trace / -t |
Enable verbose output |
Two test scripts are provided:
-
Direct link-cli test:
experiments/test-linkdb-direct.js- Tests raw link-cli commands
- Verifies create, read, update, delete operations
- Tests hierarchical relationships
-
Menu storage test:
experiments/test-linkdb-menu.js- Tests MenuStorageService
- Verifies menu hierarchy storage and retrieval
- Tests menu item CRUD operations
# Direct link-cli test
cd /tmp/gh-issue-solver-1762073356311
node experiments/test-linkdb-direct.js
# Menu storage test (requires monolith dependencies)
cd backend/monolith
npm install
cd ../../
node experiments/test-linkdb-menu.js=== Testing Link-CLI Commands Directly ===
Test 1: Create a link (100, 0)
Result: () ((3: 100 0))
Test 2: Read all links
Result: (3: 100 0)
(4: 200 0)
✓ Tests completed
The system supports both storage methods simultaneously:
- New installations: Automatically use link-cli
- Existing installations: Can migrate gradually
- Export existing menu from
/api/menu-legacy/config - Import to link-cli via
/api/menu/config - Verify menu works correctly
- (Optional) Delete legacy JSON file
// Migration example
const legacyResponse = await fetch('/api/menu-legacy/config');
const legacyData = await legacyResponse.json();
if (legacyData.response.config) {
await fetch('/api/menu/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ config: legacyData.response.config })
});
}- Efficient hierarchical queries: link-cli is optimized for associative data
- Fast parent-child lookups: Direct link traversal
- Scalable: Can handle large menu structures
- Flexible: Easy to add new relationship types
- External dependency: Requires .NET runtime and clink tool
- Command execution overhead: Each operation spawns a process
- Hybrid storage: Requires managing both links and JSON files
- Batch operations: Store entire menu structures at once
- Cache retrieved menus: Reduce database queries
- Lazy load children: Only load submenu items when needed
Error: clink: command not found
Solution:
dotnet tool install --global clink
# Add to PATH if needed
export PATH="$PATH:$HOME/.dotnet/tools"Error: Database file is locked
Solution: Ensure only one process accesses the database at a time. Use proper locking mechanisms if needed.
Error: Failed to parse query
Solution: Check LiNo query syntax. Common issues:
- Missing parentheses
- Incorrect variable names
- Quote escaping in shell commands
Issue: Menu appears empty after storage
Debug Steps:
- Check link database:
clink '((($i: $s $t)) (($i: $s $t)))' --db menu.links --after - Check JSON files:
ls -la backend/monolith/data/menu-items/ - Check logs: Look for errors in backend logs
- Verify item IDs match between links and files
- Full-text search: Index menu items for searching
- Menu versioning: Store menu history with timestamps
- User-specific menus: Link menus to user IDs
- Permission-based filtering: Filter menu items by user permissions
- Menu templates: Store and reuse menu templates
- Analytics: Track menu item usage
link-cli can be extended for:
- User preferences storage
- Configuration management
- Relationship mapping (users, roles, permissions)
- Workflow definitions
- Document hierarchies
- link-cli Repository: https://github.com/link-foundation/link-cli
- Links Notation: Associative theory-based data representation
- Issue #1800: https://github.com/unidel2035/dronedoc2025/issues/1800
- Backend Guidelines: See
CLAUDE.mdfor backend architecture rules
For issues or questions:
- Check this documentation
- Review test scripts in
experiments/ - Check backend logs:
backend/monolith/logs/ - Refer to link-cli documentation: https://github.com/link-foundation/link-cli
Last Updated: 2025-11-02 Author: Claude AI (Issue Solver) Version: 1.0.0