1.0.6 β’ Published 8 months ago
@asaidimu/node v1.0.6
@asaidimu/node
A robust TypeScript library for managing hierarchical data structures with advanced features including versioning, events, validation, and hierarchical mapping.
Features
- π³ Hierarchical Data Management: Create and manage tree-like data structures with parent-child relationships
- π Versioning: Built-in versioning system for tracking changes and preventing concurrent modifications
- π’ Event System: Subscribe to changes with a flexible event system
- β Validation: Custom validation rules for nodes
- πΊοΈ Hierarchical Mapping: Convert tree structures into nested objects based on metadata keys
- π Type Safety: Full TypeScript support with generic types for data and metadata
- π Performance: Efficient operations with caching and optimized tree traversal
- πΎ Serialization: Export and import functionality for persistence
Installation
npm install @asaidimu/node
# or
yarn add @asaidimu/node
Core Concepts
Node Structure
Each node in the tree consists of:
interface Node<T, D> {
readonly data: T; // Your custom data
readonly metadata: NodeMetadata<D>; // System + custom metadata
readonly children: ReadonlySet<NodeId>; // Child references
}
interface NodeMetadata<D> extends D {
readonly id: string; // Unique identifier
readonly path: string; // Node path (e.g., 'root/child')
readonly parentId: string | null; // Parent node ID
readonly version: number; // Version for concurrency
readonly createdAt: Date; // Creation timestamp
readonly updatedAt: Date; // Last update timestamp
}
Real-World Use Cases
1. File System Management
Perfect for building in-memory file systems with support for file operations, permissions, and watching.
import { createNodeManager } from '@asaidimu/node';
// Define types
interface FileData {
type: 'file' | 'directory';
content?: string;
size: number;
mimeType?: string;
}
interface FileMetadata {
owner: string;
group: string;
permissions: number; // Unix-style permissions
isHidden: boolean;
}
// Create file system manager
const fs = createNodeManager<FileData, FileMetadata>({
validator: (context) => {
// Ensure directories don't have content or mimeType
if (context.value.type === 'directory') {
return !context.value.content && !context.value.mimeType;
}
// Ensure files have content and mimeType
return !!context.value.content && !!context.value.mimeType;
},
maxDepth: 32, // Prevent deep nesting
maxChildren: 1000, // Limit files per directory
});
// Create directory structure
const homeDir = fs.add('home',
{ type: 'directory', size: 0 },
{ owner: 'root', group: 'root', permissions: 0o755, isHidden: false }
);
// Add user directory
fs.add('home/user',
{ type: 'directory', size: 0 },
{ owner: 'user', group: 'users', permissions: 0o700, isHidden: false }
);
// Add a file
fs.add('home/user/doc.txt',
{
type: 'file',
content: 'Hello World',
size: 11,
mimeType: 'text/plain'
},
{
owner: 'user',
group: 'users',
permissions: 0o644,
isHidden: false
}
);
// Watch for changes in user directory
fs.subscribe({ event: 'update', path: 'home/user' }, (data) => {
console.log(`File changed: ${data.node.metadata.path}`);
console.log(`New size: ${data.node.data.size} bytes`);
});
// List directory contents
const listDir = (path: string) => {
const children = fs.getChildren(path);
return children.map(child => ({
name: child.metadata.path.split('/').pop(),
...child.data,
...child.metadata
}));
};
console.log(listDir('home/user'));
// Move files
fs.move('home/user/doc.txt', 'home/user/documents/doc.txt');
// Export for persistence
const snapshot = fs.export();
localStorage.setItem('fs-state', JSON.stringify(snapshot));
2. Document Management System
Ideal for managing hierarchical documentation with versioning and access control.
interface DocContent {
title: string;
content: string;
summary?: string;
}
interface DocMetadata {
author: string;
status: 'draft' | 'review' | 'published';
reviewers: string[];
tags: string[];
version: string;
key: string;
}
// Create document manager with validation
const docs = createNodeManager<DocContent, DocMetadata>({
validator: (context) => {
// Ensure required fields
if (!context.value.title || !context.value.content) {
return false;
}
// Ensure unique titles within same parent
if (context.parent) {
const siblings = docs.getChildren(context.parent.metadata.path);
return !siblings.some(sibling =>
sibling.data.title === context.value.title &&
sibling.metadata.id !== context.metadata.id
);
}
return true;
}
});
// Add documentation structure
docs.add('technical',
{
title: 'Technical Documentation',
content: 'Root for technical documentation',
summary: 'Technical documentation repository'
},
{
author: 'admin',
status: 'published',
reviewers: [],
tags: ['root'],
version: '1.0.0',
key: 'tech-docs'
}
);
// Add API documentation
docs.add('technical/api',
{
title: 'API Reference',
content: '# API Documentation\n\nThis section covers...',
summary: 'Complete API reference'
},
{
author: 'alice',
status: 'review',
reviewers: ['bob', 'charlie'],
tags: ['api', 'reference'],
version: '0.1.0',
key: 'api-docs'
}
);
// Track document reviews
docs.subscribe('update', (data) => {
if (data.node.metadata.status === 'review') {
notifyReviewers(data.node.metadata.reviewers);
}
});
// Generate documentation map
const docMap = docs.store().map('key');
console.log(docMap);
/* Output:
{
'tech-docs': {
value: 'Root for technical documentation',
'api-docs': '# API Documentation\n\nThis section covers...'
}
}
*/
// Search functionality
const searchDocs = (query: string) => {
const results: DocContent[] = [];
const processNode = (path: string) => {
const node = docs.get(path);
if (!node) return;
if (
node.data.title.includes(query) ||
node.data.content.includes(query) ||
node.metadata.tags.includes(query)
) {
results.push(node.data);
}
docs.getChildren(path).forEach(child =>
processNode(child.metadata.path)
);
};
processNode('technical');
return results;
};
3. Configuration Management
Managing hierarchical application configuration with inheritance and overrides.
interface ConfigValue {
value: any;
description: string;
type: 'string' | 'number' | 'boolean' | 'object';
}
interface ConfigMetadata {
scope: 'system' | 'user' | 'project';
encrypted: boolean;
lastModified: Date;
key: string;
}
const config = createNodeManager<ConfigValue, ConfigMetadata>({
validator: (context) => {
// Type validation
const { value, type } = context.value;
switch (type) {
case 'string':
return typeof value === 'string';
case 'number':
return typeof value === 'number';
case 'boolean':
return typeof value === 'boolean';
case 'object':
return typeof value === 'object';
default:
return false;
}
}
});
// Add system configuration
config.add('system',
{
value: {},
description: 'System configuration root',
type: 'object'
},
{
scope: 'system',
encrypted: false,
lastModified: new Date(),
key: 'system'
}
);
// Add database configuration
config.add('system/database',
{
value: {
host: 'localhost',
port: 5432,
maxConnections: 100
},
description: 'Database configuration',
type: 'object'
},
{
scope: 'system',
encrypted: true,
lastModified: new Date(),
key: 'db'
}
);
// Configuration helper
class ConfigManager {
constructor(private manager: typeof config) {}
get(path: string): any {
const node = this.manager.get(path);
if (!node) return null;
// Handle encrypted values
if (node.metadata.encrypted) {
return this.decrypt(node.data.value);
}
return node.data.value;
}
set(path: string, value: any, type: ConfigValue['type']) {
const node = this.manager.get(path);
if (node?.metadata.encrypted) {
value = this.encrypt(value);
}
return this.manager.update(path, {
value,
type,
description: node?.data.description || ''
});
}
private encrypt(value: any): any {
// Implement encryption
return value;
}
private decrypt(value: any): any {
// Implement decryption
return value;
}
}
// Usage
const configManager = new ConfigManager(config);
const dbConfig = configManager.get('system/database');
4. UI Component Tree
Managing component hierarchies with state and events.
interface ComponentData {
type: string;
props: Record<string, any>;
state: Record<string, any>;
}
interface ComponentMetadata {
isVisible: boolean;
key: string;
renderCount: number;
lastRendered: Date;
}
const ui = createNodeManager<ComponentData, ComponentMetadata>();
// Create component tree
ui.add('app',
{
type: 'div',
props: { className: 'app' },
state: {}
},
{
isVisible: true,
key: 'app',
renderCount: 0,
lastRendered: new Date()
}
);
ui.add('app/header',
{
type: 'header',
props: { style: { background: '#f0f0f0' } },
state: { menuOpen: false }
},
{
isVisible: true,
key: 'header',
renderCount: 0,
lastRendered: new Date()
}
);
// Track renders
ui.subscribe('update', (data) => {
ui.update(data.node.metadata.path, data.node.data, {
metadata: {
...data.node.metadata,
renderCount: data.node.metadata.renderCount + 1,
lastRendered: new Date()
}
});
});
// Component helper
class UIManager {
constructor(private manager: typeof ui) {}
setState(path: string, state: Partial<Record<string, any>>) {
const node = this.manager.get(path);
if (!node) return;
return this.manager.update(path, {
...node.data,
state: { ...node.data.state, ...state }
});
}
getState(path: string): Record<string, any> | null {
return this.manager.get(path)?.data.state || null;
}
render() {
const tree = this.manager.store().map('key');
return this.renderNode(tree.app);
}
private renderNode(node: any): string {
if (typeof node === 'string') return node;
const children = Object.entries(node)
.filter(([key]) => key !== 'value')
.map(([_, child]) => this.renderNode(child))
.join('');
return `<${node.value.type} ${this.propsToString(node.value.props)}>${children}</${node.value.type}>`;
}
private propsToString(props: Record<string, any>): string {
return Object.entries(props)
.map(([key, value]) => {
if (typeof value === 'object') {
value = JSON.stringify(value);
}
return `${key}="${value}"`;
})
.join(' ');
}
}
// Usage
const uiManager = new UIManager(ui);
uiManager.setState('app/header', { menuOpen: true });
console.log(uiManager.render());
API Reference
Core Methods
interface NodeManagerInterface<T, D> {
// Basic operations
get(path: string): Node<T, D> | null;
add(path: string, value: T, metadata?: D): Node<T, D>;
update(path: string, value: T, options?: UpdateOptions): Node<T, D>;
remove(path: string): boolean;
move(fromPath: string, toPath: string, options?: MoveOptions): Node<T, D>;
// Tree operations
getChildren(path: string): Node<T, D>[];
exists(path: string): boolean;
// Events
subscribe(event: NodeEventType | EventOptions, callback: EventCallback): () => void;
// Store operations
store(): Readonly<NodeStore<T, D>>;
export(): Readonly<NodeStoreData<T, D>>;
import(data: NodeStoreData<T, D>): void;
}
Events
Available event types:
'add'
: Node added'update'
: Node updated'remove'
: Node removed'move'
: Node movednode
: All te above events
Error Types
class NodeError extends Error {
code: string;
details?: unknown;
}
class ValidationError extends NodeError {}
class PathError extends NodeError {}
class ConcurrencyError extends NodeError {}
Best Practices
Path Management
- Use consistent path separators (forward slash)
- Keep paths shallow when possible
- Use meaningful path segments
Data Structure
- Keep node data focused and specific
- Use metadata for cross-cutting concerns
- Consider serialization implications
Performance
- Subscribe to specific paths when possible
- Batch operations where appropriate
- Use the map cache for repeated access
Error Handling
- Always handle PathError for path operations
- Use ValidationError for data validation
- Handle ConcurrencyError for version conflicts
License
MIT