1.0.6 β€’ Published 12 months ago

@asaidimu/node v1.0.6

Weekly downloads
-
License
MIT
Repository
github
Last release
12 months ago

@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 moved
  • node: 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

  1. Path Management

    • Use consistent path separators (forward slash)
    • Keep paths shallow when possible
    • Use meaningful path segments
  2. Data Structure

    • Keep node data focused and specific
    • Use metadata for cross-cutting concerns
    • Consider serialization implications
  3. Performance

    • Subscribe to specific paths when possible
    • Batch operations where appropriate
    • Use the map cache for repeated access
  4. Error Handling

    • Always handle PathError for path operations
    • Use ValidationError for data validation
    • Handle ConcurrencyError for version conflicts

License

MIT

1.0.6

12 months ago

1.0.5

12 months ago

1.0.4

12 months ago

1.0.3

12 months ago

1.0.2

12 months ago

1.0.1

12 months ago

1.0.0

12 months ago