1.0.6 β€’ Published 8 months ago

@asaidimu/node v1.0.6

Weekly downloads
-
License
MIT
Repository
github
Last release
8 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

8 months ago

1.0.5

8 months ago

1.0.4

8 months ago

1.0.3

8 months ago

1.0.2

8 months ago

1.0.1

8 months ago

1.0.0

8 months ago