npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ticatec/app-data-manager

v3.0.0

Published

A comprehensive TypeScript library providing hierarchical data manager classes for CRUD operations, pagination, and data management in frontend applications. Features include full list, paged, and stackable data managers with built-in caching and transfor

Downloads

520

Readme

App Data Manager

npm version TypeScript License: MIT

中文文档

Current version: 2.0.0 | A comprehensive TypeScript library that provides a hierarchical set of data manager classes for handling CRUD operations, paginated data retrieval, and tree-structured data via HTTP requests. This library simplifies data management in frontend applications by abstracting the complexity of data operations and providing a structured approach to managing different types of datasets — flat lists, paginated lists, infinite-scroll feeds, and hierarchical trees.

Note: This is a pure ESM (ES Module) package. Only import syntax is supported. The compiled output uses explicit .js extensions on every relative import, so it loads directly under Node's native ESM loader — no bundler required, even for SSR.

Features

  • Type-safe: Full TypeScript support with comprehensive type definitions
  • Hierarchical Architecture: Well-structured inheritance hierarchy for different data management needs
  • Pagination Support: Built-in pagination handling with both standard and stackable pagination
  • Tree Support: Dedicated TreeDataManager for hierarchical data, with eager or lazy (on-demand) loading
  • CRUD Operations: Complete Create, Read, Update, Delete functionality
  • Data Transformation: Built-in data conversion and validation support
  • Extensible: Easy to extend and customize for specific use cases
  • Dependency Injection: Service-based architecture for better testability
  • Zero Global Side Effects: No Array.prototype polyfills or other global mutations — safe for long-running SSR processes

Installation

npm install @ticatec/app-data-manager

TreeDataManager additionally depends on @ticatec/hierarchy-data (listed as a peer dependency). It's only required if you actually use TreeDataManager.

Quick Start

import { PagedDataManager } from '@ticatec/app-data-manager';
import { PagingDataService } from '@ticatec/app-data-service';

// 1. Create a data service
class UserService extends PagingDataService {
  constructor() {
    super('/api/users');
  }
}

// 2. Create a data manager
const userManager = new PagedDataManager(new UserService(), 'id', {
  tagData: { status: 'active' } // Default filter criteria
});

// 3. Load data
await userManager.search({ role: 'admin' });
console.log(`Found ${userManager.count} users`);
console.log(`Page ${userManager.pageNo} of ${userManager.pageCount}`);
console.log(userManager.list); // Current page users

🤖 AI-Assisted Development

Accelerate your development with AI-powered code generation!

This library includes comprehensive AI coding assistant prompts optimized for Claude Code and other AI assistants. These prompts help you generate correct, consistent code faster.

📁 Available Prompts

  • prompts/AI_CODING_ASSISTANT_EN.md - Complete guide in English
  • prompts/AI_CODING_ASSISTANT_CN.md - 完整指南(中文)
  • prompts/CLAUDE_CODE_GUIDE.md - Quick start guide
  • prompts/QUICK_REFERENCE.md - Decision trees and cheat sheets

🚀 Quick Start with AI

For Claude Code users:

# In your Claude Code conversation, simply reference the prompt:
Follow prompts/AI_CODING_ASSISTANT_EN.md

Create a user management feature with pagination, search, and CRUD operations

Claude Code will automatically:

  • ✅ Analyze your existing codebase patterns
  • ✅ Generate services and managers following best practices
  • ✅ Match your coding style and conventions
  • ✅ Place files in correct locations
  • ✅ Handle imports and exports

💡 Example Usage

# 1. Create a new feature
Follow prompts/AI_CODING_ASSISTANT_EN.md
Create product management with infinite scroll

# 2. Refactor existing code
Review all managers and apply best practices from the prompt

# 3. Add features
Add bulk delete to OrderManager following existing patterns

# 4. Understand patterns
Explain when to use PagedDataManager vs StackDataManager vs TreeDataManager

🎯 Benefits

  • Faster Development: Generate complete features in seconds
  • Consistency: Maintain patterns across your codebase
  • Best Practices: Built-in knowledge of anti-patterns and optimization tips
  • Context-Aware: AI reads your existing code and matches your style
  • Error Prevention: Common mistakes are highlighted and avoided

📚 What's Included

The AI prompts cover:

  • 🏗️ Architecture overview - Two-layer design (Service + Manager)
  • 📋 Decision trees - Choose the right class for your needs
  • 🏃 Code patterns - 6 common patterns with examples
  • ✅ Best practices - Performance, error handling, type safety
  • ⚠️ Common mistakes - Anti-patterns and how to avoid them
  • 🎓 Advanced patterns - Custom endpoints, data transformation
  • 🔍 Quick reference - Decision trees and cheat sheets

🌐 Language Support

# English projects
Follow prompts/AI_CODING_ASSISTANT_EN.md

# 中文项目
遵循 prompts/AI_CODING_ASSISTANT_CN.md 的指导

📖 Learn More


Architecture Overview

The library is built on a hierarchical structure with the following core classes:

BaseDataManager (Abstract)
├── FullListDataManager
└── CommonPagedDataManager (Abstract)
    ├── PagedDataManager
    └── StackDataManager

TreeDataManager (standalone)

TreeDataManager deliberately does not extend BaseDataManager. BaseDataManager is built around a flat array (list) as its core storage; a tree's local cache is fundamentally a HierarchyTree (from @ticatec/hierarchy-data) — a completely different storage shape. Forcing it into the same inheritance chain would only muddy the list concept, so it's a sibling class that follows the same construction/error-handling conventions instead.

Core Classes

1. BaseDataManager

Abstract base class for managing data collections with common CRUD operations.

Key Features:

  • Data collection management with local caching
  • CRUD operations with automatic local synchronization
  • remove() matches items by primary key (via checkEqual), not object reference — removal still works even if the caller holds a different object instance with the same key
  • Data transformation support via convert function, applied uniformly on both save and load paths
  • Equality checking via checkEqual function
  • Service-based architecture for data operations

Properties:

  • service: CommonDataService - Data service instance
  • checkEqual: CheckEqual - Function to compare data items
  • convert?: DataConvert - Optional data transformation function
  • list: Array<any> - Current dataset (read-only copy)

Methods:

  • save(data: any, isNew: boolean): Promise<void> - Save/update data
  • remove(item: any): Promise<void> - Delete data
  • append(item: any): void - Add item to beginning of list
  • replace(item: any): void - Replace existing item with matching key
  • removeItem(item: any): void - Remove item from local collection only

2. FullListDataManager

Concrete class inheriting from BaseDataManager for managing complete datasets.

Use Cases:

  • Dropdown lists
  • Static reference data
  • Small to medium-sized complete datasets
  • Configuration lists

Constructor:

protected constructor(
  service: T, 
  keyField: string | CheckEqual, 
  options: ManagerOptions = null
)

Additional Methods:

  • loadData(): Promise<void> - Load complete dataset from service using tagData as filter conditions

Example:

import { FullListDataManager } from '@ticatec/app-data-manager';
import { MyFullListDataService } from './services';

class CategoryManager extends FullListDataManager<MyFullListDataService> {
  constructor() {
    super(
      new MyFullListDataService(), 
      'id', 
      {
        tagData: { status: 'active', type: 'public' } // filter conditions for getList
      }
    );
  }
}

const manager = new CategoryManager();
console.log(manager.list); // [] — safe to read before loadData() resolves
await manager.loadData(); // Uses tagData as filter conditions
console.log(manager.list); // Filtered category list

3. CommonPagedDataManager

Abstract base class for paginated data management with comprehensive pagination support.

Constructor:

protected constructor(
  service: T, 
  keyField: string | CheckEqual, 
  options: PagedManagerOptions = null
)

Key Features:

  • Pagination state management (pageNo, pageCount, totalCount)
  • Query criteria handling with tagData-based initialization; search()/refresh() never mutate the criteria object you pass in, and the criteria getter always returns a clone
  • Configurable pagination parameters, either globally (static setters) or per instance (constructor options)
  • A request-sequence guard: if two searches are in flight and the older one resolves after the newer one, its response is discarded instead of overwriting fresher data
  • refresh() reloads the current page, it doesn't jump back to page 1
  • Abstract processDataResult method for custom data processing

Static Configuration (global defaults):

  • CommonPagedDataManager.setPageSize(25) / setRowsPerPage(25) - Set default rows per page
  • CommonPagedDataManager.setPageSizeKey('pageSize') / setRowsKey('pageSize') - Set page size parameter name (default is 'pageSize')
  • CommonPagedDataManager.setPageNoKey('pageNo') - Set page number parameter name (default is 'pageNo')

Instance-level Configuration (PagedManagerOptions, overrides the global defaults above for a single manager):

interface PagedManagerOptions extends ManagerOptions {
  pageSizeKey?: string;  // property name for page size, falls back to the global default ('pageSize')
  rowsKey?: string;      // alias for pageSizeKey
  pageNoKey?: string;    // property name for page number, falls back to the global default ('pageNo')
  pageSize?: number;     // page size for this instance, falls back to the global default (25)
  rowsPerPage?: number;  // alias for pageSize
}

This lets different managers in the same app talk to APIs with different pagination protocols, without needing to flip the global default back and forth.

Properties:

  • criteria: any - Current query criteria (a clone; mutating the returned object does not affect internal state)
  • count: number - Total record count

Methods:

  • search(criteria: any): Promise<void> - Search with criteria
  • setPageNo(pageNo: number): Promise<void> - Navigate to specific page
  • setPageSize(size: number): Promise<void> - Change page size
  • setRowsPage(rows: number): Promise<void> - Change page size (alias for setPageSize)
  • refresh(): Promise<void> - Refresh current page
  • resetSearch(): Promise<void> - Reset to default criteria

4. PagedDataManager

Concrete implementation of CommonPagedDataManager for standard pagination.

Use Cases:

  • Data tables with pagination
  • Search results with page navigation
  • Standard paginated lists

Additional Properties:

  • pageCount: number - Total number of pages
  • pageNo: number - Current page number

Data Processing:

  • Replaces entire dataset with new page data
  • Simple and straightforward pagination

Constructor:

constructor(
  service: T, 
  keyField: string | CheckEqual, 
  options: PagedManagerOptions = null
)

Example:

import { PagedDataManager } from '@ticatec/app-data-manager';
import { MyPagingDataService } from './services';

class UserManager extends PagedDataManager<MyPagingDataService> {
  constructor() {
    super(
      new MyPagingDataService(), 
      'userId',
      {
        tagData: { status: 'active' }, // default criteria via tagData
        rowsPerPage: 20                // instance-level override, doesn't touch the global default
      }
    );
  }
}

const manager = new UserManager();
// Will search with default criteria from tagData { status: 'active' }
await manager.resetSearch(); 
console.log(`Page ${manager.pageNo} of ${manager.pageCount}`);
console.log(`Total users: ${manager.count}`);
console.log(manager.list); // Current page users

// Navigate to next page
await manager.setPageNo(2);

// Search with additional criteria
await manager.search({ status: 'active', role: 'admin' });

5. StackDataManager

Concrete implementation of CommonPagedDataManager for stackable pagination ("infinite scroll").

Use Cases:

  • Social media feeds
  • Infinite scroll implementations
  • "Load More" functionality
  • Progressive data loading

Key Features:

  • Accumulates data across pages, but only when advancing via loadMore()
  • search(), resetSearch(), refresh(), and setRowsPage() all replace the list instead of merging into it — so switching search criteria (or refreshing) never mixes results from two different queries
  • Duplicate prevention by primary key when merging (checkEqual)

Additional Methods:

  • loadMore(): Promise<void> - Load next page and append to existing data
  • hasMore(): boolean - Check if more pages are available

Constructor:

constructor(
  service: T, 
  keyField: string | CheckEqual, 
  options: PagedManagerOptions = null
)

Example:

import { StackDataManager } from '@ticatec/app-data-manager';
import { MyPagingDataService } from './services';

class FeedManager extends StackDataManager<MyPagingDataService> {
  constructor() {
    super(
      new MyPagingDataService(), 
      'postId',
      {
        tagData: { category: 'technology' } // default criteria via tagData
      }
    );
  }
}

const manager = new FeedManager();
// Will search with default criteria from tagData { category: 'technology' }
await manager.resetSearch();

// Load more content
while (manager.hasMore()) {
  await manager.loadMore();
  console.log(`Loaded ${manager.list.length} posts`);
}

// Search with different criteria — replaces the list, does not merge with the old results
await manager.search({ category: 'science', featured: true });

6. TreeDataManager

Concrete class for managing hierarchical/tree-structured data. Wraps a HierarchyTree (a pure, synchronous, in-memory tree data structure) together with a FullListDataService — reusing that service's getList(params) to express both "fetch everything" (eager mode) and "fetch the children of node X" (lazy mode), so no dedicated tree service interface is needed.

Use Cases:

  • Tree views (org charts, category trees, file explorers) with add/edit/delete/move
  • Tree data grids with expandable rows, column-header sorting
  • Any parent/child dataset where you need lookup-by-key, ancestor/descendant traversal, or lazy loading of large subtrees

Constructor:

constructor(service: T, options: TreeManagerOptions<TEntity>)
// T extends FullListDataService

TreeManagerOptions:

interface TreeManagerOptions<T = any> {
  keyField: string | ((item: T) => string | number);
  parentKeyField: string | ((item: T) => string | number | null | undefined);
  setParentKey?: (item: T, newParentKey: string | number | null | undefined) => T; // required for moveNode() when parentKeyField is a function; auto-derived when it's a string
  convert?: DataConvert;
  tagData?: any;
  onError?: ErrorHandler;
  compare?: (a: T, b: T) => number;         // sibling sort comparator
  maxDepth?: number;                        // 1-1000, default 1000
  duplicateKeyStrategy?: 'throw' | 'replace' | 'warn'; // default 'throw'
  cycleStrategy?: 'as-root' | 'throw';      // default 'as-root'
  onDiagnostic?: (event: HierarchyDiagnosticEvent) => void;
  loadMode?: 'eager' | 'lazy';              // default 'eager'
  childrenParam?: string;                   // query param name for "children of X" in lazy mode, default 'parentId'
  isBranch?: (item: T) => boolean;          // hint for unloaded nodes in lazy mode; defaults to "assume it might be a branch"
}

Eager mode (load the whole tree up front):

import { TreeDataManager } from '@ticatec/app-data-manager';
import { MyFullListDataService } from './services';

const treeManager = new TreeDataManager(new MyFullListDataService(), {
  keyField: 'id',
  parentKeyField: 'parentId',
});

await treeManager.loadData();
console.log(treeManager.roots);           // top-level nodes
console.log(treeManager.roots[0].children); // nested children, already resolved

Lazy mode (fetch children on demand as the user expands nodes):

const treeManager = new TreeDataManager(new MyFullListDataService(), {
  keyField: 'id',
  parentKeyField: 'parentId',
  loadMode: 'lazy',
  childrenParam: 'parentId',
  isBranch: (item) => item.hasChildren, // optional hint from the backend, avoids a wasted request for known leaves
});

await treeManager.loadData(); // fetches only root-level nodes

// when the user expands a node in the UI:
await treeManager.loadChildren(nodeOrKey); // idempotent — a second call is a no-op unless force=true
treeManager.isBranch(nodeOrKey);           // true/false, usable before or after loading

CRUD and move:

await treeManager.addNode({ name: 'New Category' }, parentKey);
await treeManager.updateNode({ id: 5, name: 'Renamed' });
await treeManager.removeNode(5);          // removes the node and every descendant, on the server and locally
await treeManager.moveNode(5, newParentKey); // move a node (or its whole branch) elsewhere
treeManager.sort((a, b) => a.name.localeCompare(b.name)); // re-sort every level, e.g. for a grid column-header click

moveNode() consistency note: it persists first (service.save()), then commits the move locally (tree.move()) only on success. If service.save() fails, the local tree is 100% untouched. If service.save() succeeds but tree.move() itself then fails (maxDepth exceeded, a cycle with cycleStrategy: 'throw') — HierarchyTree.move() is transactional, so the local tree still stays exactly as it was — but the backend has already been updated by that point. That's a short-lived backend/local divergence, and the only thing that actually corrects it is calling loadData() again (a full rebuild). Don't call loadChildren(node, true) expecting it to fix this: loadChildren() is purely additive — it only appends keys the tree doesn't already have, and the moved node is already present locally (just at its old position), so it gets silently skipped under both the old and new parent. Treat a moveNode() failure as a signal to reload via loadData(), not as proof the local tree matches the backend. (Note: in lazy mode, loadData() only re-fetches root-level items and clears previously-loaded subtrees, so it's a hard reset — the UI typically needs to reset its expanded state too, so affected branches get re-fetched via loadChildren() as the user re-expands them.)

Methods:

  • loadData(): Promise<void> - Build/reload the tree (full dataset in eager mode, roots only in lazy mode)
  • loadChildren(nodeOrKey, force?= false): Promise<void> - Fetch a node's children on demand (lazy mode only; no-op in eager mode)
  • isBranch(nodeOrKey): boolean - Whether a node should show as expandable, using real structure once loaded and the isBranch predicate before that
  • isChildrenLoaded(nodeOrKey): boolean - Whether a node's children have actually been fetched (always true in eager mode)
  • addNode(data, parentKey?): Promise<IHierarchyData | undefined>
  • updateNode(data): Promise<IHierarchyData | undefined> - Updates data only, does not change parent/child relationships
  • removeNode(itemOrKey): Promise<boolean> - Removes the node and all descendants
  • moveNode(targetKey, newParentKey?, index?): Promise<boolean> - See the consistency note above
  • sort(comparator?): void / sortSiblings(parentOrKey?): void
  • roots, find(key), walk(cb), map(cb), filter(predicate), getVisibleList(expandedKeys), clear()

Nodes returned by TreeDataManager are IHierarchyData<T> instances from @ticatec/hierarchy-data — a read-only view with .data, .parent, .children, .level, .isLeaf, plus traversal helpers (.walk, .map, .filter, .descendants(), .ancestors(), .path(), .siblings()). See the @ticatec/hierarchy-data docs for the full node API.

Interface Definitions

IPagedDataManager

Interface defining the contract for paginated data management operations.

Properties:

  • list: Array<any> - Current dataset (read-only)
  • count: number - Total record count
  • pageNo: number - Current page number
  • pageSize: number - Current page size
  • pageCount: number - Total number of pages
  • criteria: any - Current query criteria

Methods:

  • refresh(): Promise<void> - Refresh current data
  • resetCriteria(): any - Reset search criteria to default
  • resetSearch(): Promise<void> - Reset to default criteria and reload
  • search(params: any): Promise<void> - Search with new criteria
  • setPageSize(size: number): Promise<void> - Change page size
  • setRowsPage(rows: number): Promise<void> - Change page size (alias for setPageSize)
  • setPageNo(value: number): Promise<void> - Navigate to specific page

Implementation:

  • PagedDataManager implements this interface for standard pagination

Example:

import { IPagedDataManager, PagedDataManager } from '@ticatec/app-data-manager';

class UserService extends PagingDataService {
  constructor() {
    super('/api/users');
  }
}

// PagedDataManager implements IPagedDataManager
const userManager: IPagedDataManager = new PagedDataManager(
  new UserService(), 
  'id'
);

await userManager.search({ status: 'active' });
console.log(`Page ${userManager.pageNo} of ${userManager.pageCount}`);
console.log(`Total: ${userManager.count} records`);

IFullListDataManager

Interface defining the contract for non-paginated, load-everything-at-once data management.

Properties:

  • list: Array<any> - Current dataset (read-only)

Methods:

  • loadData(): Promise<void> - Load the complete dataset from the service

Implementation:

  • FullListDataManager implements this interface

ITreeDataManager

Interface defining the contract for hierarchical data management — tree construction, on-demand child loading, add/update/remove, and move.

Properties:

  • roots: readonly IHierarchyData<any>[] - Root node list (read-only copy)

Methods:

  • loadData(): Promise<void>, loadChildren(nodeOrKey, force?): Promise<void>
  • isBranch(nodeOrKey): boolean, isChildrenLoaded(nodeOrKey): boolean
  • addNode(data, parentKey?), updateNode(data), removeNode(itemOrKey), moveNode(targetKey, newParentKey?, index?)
  • sort(comparator?), sortSiblings(parentOrKey?)
  • find(key), walk(cb), map(cb), filter(predicate), getVisibleList(expandedKeys), clear()

Implementation:

  • TreeDataManager implements this interface

Type Definitions

CheckEqual

type CheckEqual = (e1: any, e2: any) => boolean;

Function to determine if two data items are equal (typically by comparing primary keys).

DataConvert

type DataConvert = (item: any, isNew: boolean) => any;

Optional function to transform data items, applied uniformly on both save and load paths (query results, full-list loads, and tree loads all get converted, not just save()).

ManagerOptions

interface ManagerOptions {
  convert?: DataConvert;    // Data transformation function
  fromTop?: boolean;        // Add new items to top of list (default: true)
  tagData?: any;           // Default query criteria/filter conditions
  onError?: ErrorHandler;  // Called on save/remove failure instead of throwing, if provided
}

tagData Usage:

  • For paginated managers: Used as default query criteria for searches
  • For FullListDataManager: Used as filter conditions for the getList method
  • For TreeDataManager: Used as extra fixed query criteria sent with every loadData()/loadChildren() request

ErrorHandler

type ErrorHandler = (error: any, operation: string) => void;

Optional callback passed via options.onError. If provided, it's invoked instead of throwing when save()/remove() (and, for TreeDataManager, loadData()/loadChildren()/addNode()/updateNode()/removeNode()/moveNode()) fail. If omitted, the error is rethrown as usual.

Advanced Usage

Custom Data Manager

import { BaseDataManager } from '@ticatec/app-data-manager';

class CustomDataManager extends BaseDataManager<MyDataService> {
  constructor(service: MyDataService) {
    super(service, 'id', {
      convert: (item, isNew) => ({
        ...item,
        timestamp: isNew ? Date.now() : item.timestamp
      }),
      fromTop: true
    });
  }
  
  // Custom business logic
  async archiveItem(item: any): Promise<void> {
    const archived = { ...item, archived: true };
    await this.save(archived, false);
  }
}

Service Implementation Example

import { PagingDataService } from '@ticatec/app-data-service';

class MyPagingService extends PagingDataService {
    constructor() {
        super('/api/users');
    }
}

Best Practices

  1. Choose the Right Manager:

    • Use FullListDataManager for small, static datasets
    • Use PagedDataManager for traditional paginated tables
    • Use StackDataManager for infinite scroll or feed-like interfaces
    • Use TreeDataManager for hierarchical data — trees, org charts, nested categories
  2. Implement Proper Equality Checking:

    // Good: Use unique identifiers
    const manager = new PagedDataManager(service, 'id');
       
    // Better: Custom comparison for complex keys
    const manager = new PagedDataManager(service, (a, b) => 
      a.companyId === b.companyId && a.userId === b.userId
    );
  3. Handle Errors Gracefully:

    try {
      await manager.search({ query: 'user input' });
    } catch (error) {
      console.error('Search failed:', error);
      // Handle error appropriately
    }
  4. Use Data Conversion for Consistency:

    const options = {
      convert: (item, isNew) => ({
        ...item,
        createdAt: isNew ? new Date().toISOString() : item.createdAt,
        updatedAt: new Date().toISOString()
      })
    };
  5. Treat moveNode() failures as a reload signal, not silent data loss: since it persists to the backend before committing locally, a failure partway through means the two can briefly disagree — see the consistency note under TreeDataManager above.

Dependencies

This package has no runtime dependencies of its own — it doesn't touch Array.prototype or any other global state, which matters if you're rendering it in a long-running SSR process (no risk of polluting other requests or modules sharing the same Node process).

Performance Optimization Tips

  1. Use appropriate manager for your data size:

    • FullListDataManager: Best for datasets under 1,000 records
    • PagedDataManager: Ideal for large datasets with pagination
    • StackDataManager: Perfect for infinite scroll with frequent updates
    • TreeDataManager (lazy mode): Best for large trees where loading everything up front would be wasteful
  2. Optimize page size:

    // Configure optimal page size based on your data complexity
    CommonPagedDataManager.setRowsPerPage(50); // Default is 25
    
    // Or scope it to a single manager instead of changing the global default
    new PagedDataManager(service, 'id', { rowsPerPage: 50 });
  3. Use efficient equality checks:

    // Fast: Single field comparison
    const fastManager = new PagedDataManager(service, 'id');
    
    // Slower but necessary: Multi-field comparison
    const complexManager = new PagedDataManager(service, (a, b) =>
      a.companyId === b.companyId && a.userId === b.userId
    );
  4. Implement proper error boundaries:

    try {
      await manager.search({ query: userKeyword });
    } catch (error) {
      if (error.response?.status === 401) {
        // Handle authentication error
      } else if (error.response?.status === 429) {
        // Handle rate limiting
      } else {
        // Handle other errors
      }
    }

FAQ

Q: What's the difference between tagData and search criteria?

A: tagData in options sets default/fixed filter criteria that persist across searches, while criteria passed to search() are combined with tagData for that specific search.

Q: How do I clear all data?

A: Call resetSearch() to reset to default criteria and reload (TreeDataManager has clear() instead), or create a new manager instance.

Q: Can I use multiple managers for the same data type?

A: Yes, you can create multiple manager instances with different tagData configurations for the same service.

Q: How do I handle concurrent searches?

A: CommonPagedDataManager (and therefore PagedDataManager/StackDataManager) already guards against this: if an older search resolves after a newer one, its response is discarded instead of overwriting fresher data. You don't need to implement cancellation yourself for typical use.

Q: Is the data cached?

A: Yes, each manager maintains an in-memory cache. Use refresh() (or loadData()/loadChildren(..., true) for TreeDataManager) to reload data from the server.

Q: Should I use TreeDataManager in eager or lazy mode?

A: Eager mode (the default) is simplest and fine for trees with up to a few thousand nodes. Switch to loadMode: 'lazy' once the full tree is too large to fetch and render up front — children are then fetched only as the user expands nodes.

Browser Support

  • Chrome/Edge 88+
  • Firefox 85+
  • Safari 14+
  • Node.js 14+ (native ESM, no bundler required)

Contributing

Issues and pull requests are welcome. Please ensure:

  1. npm run build compiles without errors
  2. Code follows TypeScript best practices
  3. Documentation is updated for new features
  4. Examples are provided for new functionality

License

Copyright © 2023 Ticatec. All rights reserved.

This library is released under the MIT License. See LICENSE file for details.

Contact

  • Email: [email protected]
  • GitHub: https://github.com/ticatec/web-commons/tree/main/packages/app-data-manager
  • Issues: https://github.com/ticatec/web-commons/issues