nodedb-json
Version:
A lightweight JSON-based database for Node.js with TypeScript support, indexing, and complex query capabilities
559 lines (434 loc) • 16.8 kB
Markdown
## nodedb-json
**nodedb-json** is a lightweight JSON file database tool designed for Node.js. It provides an easy-to-use API for setting, reading, querying, updating, and deleting data stored in JSON files. Supports both CommonJS and ES6 module syntax, as well as TypeScript.
### Features
- **Simple and Intuitive**: Easy-to-use API for seamless interaction with JSON files.
- **Persistent Storage**: Data is stored persistently in JSON files, perfect for Node.js.
- **Flexible Querying**: Supports querying and filtering of collections.
- **Array Operations**: Provides robust methods for manipulating arrays, including adding, removing, and updating elements.
- **Lightweight**: Minimal dependencies, ensuring fast performance.
- **TypeScript Support**: Full TypeScript support with type definitions.
- **Batch Operations**: Support for executing multiple operations in batch.
- **Indexing**: Supports creating indexes on array fields for faster lookups.
### Installation
```bash
npm install nodedb-json
```
### Usage
#### CommonJS
```javascript
const NodedbJson = require('nodedb-json');
const db = new NodedbJson('path/to/db.json');
db.set('name', 'John Doe');
console.log(db.get('name')); // Outputs: John Doe
```
#### ES6
```javascript
import NodedbJson from 'nodedb-json';
const db = new NodedbJson('path/to/db.json');
db.set('name', 'John Doe');
console.log(db.get('name')); // Outputs: John Doe
```
#### TypeScript
```typescript
import NodedbJson from 'nodedb-json';
// Define your data types
interface User {
id: number;
name: string;
age: number;
}
// Create database with options
const db = new NodedbJson<User>('path/to/db.json', {
autoSave: true,
createIfNotExists: true,
defaultValue: { users: [] }
});
// Type-safe operations
db.push('users', { id: 1, name: 'John', age: 30 });
const user = db.find<User>('users', user => user.id === 1);
```
### Basic Operations
#### Set
```javascript
db.set("key", "value");
```
#### Get
```javascript
const value = db.get("key");
```
#### Update
```javascript
// Update an object
db.update("key", { newField: "newValue" });
// Update an array item
db.update("arrayKey", (item) => item.id === 1, { name: "Updated Name" });
```
#### Delete
```javascript
// Delete a key
db.delete("key");
// Delete array items using a predicate
db.delete("arrayKey", (item) => item.id === 1);
// Batch delete array items by specified field
db.delete("arrayKey", [1, 3]); // Deletes items with id 1 and 3
db.delete("arrayKey", ["Alice", "Charlie"], "name"); // Deletes items with name 'Alice' and 'Charlie'
```
#### Push
```javascript
db.push("users", { name: "Bob", age: 30 }).push("users", { name: "Charlie", age: 35 });
db.push("users", [
{ name: "Bob", age: 30 },
{ name: "Charlie", age: 35 },
]);
```
### Advanced Operations
#### Find
```javascript
const item = db.find("arrayKey", (item) => item.id === 2);
```
#### Filter
```javascript
const items = db.filter("arrayKey", (item) => item.isActive);
```
#### Batch Operations
```javascript
db.batch([
{ method: "set", args: ["config.theme", "dark"] },
{ method: "set", args: ["config.language", "zh-CN"] },
{ method: "push", args: ["logs", { time: new Date().toISOString(), action: "配置更新" }] }
]);
```
#### Manual Save
```javascript
// Configure auto-save
const db = new NodedbJson('path/to/db.json', { autoSave: false });
// Make changes
db.set("key1", "value1");
db.set("key2", "value2");
// Manually save when ready
db.save();
```
### Indexing
Indexing can significantly improve lookup performance for large datasets:
```javascript
// Create a unique index on the 'id' field
db.createIndex("users", { field: "id", type: "unique" });
// Create a multi-value index on the 'age' field
db.createIndex("users", { field: "age", type: "multi" });
// Find using index
const user = db.findByField("users", "id", 1);
// Filter using index
const adults = db.filterByField("users", "age", [30, 40, 50]);
// Drop an index when no longer needed
db.dropIndex("users", "age");
```
## Changelog
### [1.3.0] - 2025-06-03
- **Major Feature Update: Complex Query Support**
- Added `query()` method with comprehensive query operations
- Support for multi-field sorting
- Support for pagination queries
- Support for aggregation operations: count, sum, avg, min, max, group
- Support for field selection/projection
- Added convenient query methods: `orderBy()`, `paginate()`, `count()`, `aggregate()`, `distinct()`
- Intelligent index utilization with automatic query optimization
- Detailed query statistics (execution time, index usage, etc.)
- **Performance Optimizations**
- Query operations with index acceleration support
- Lazy evaluation for optimized large dataset processing
- Execution time monitoring and performance metrics
- **Developer Experience Improvements**
- Complete TypeScript type support
- Rich documentation and examples
- Added `npm run example:query` demonstration script
### [1.2.0] - 2025-05-26
- Added indexing support for faster lookups
- Added `findByField` and `filterByField` methods for index-based queries
- Performance improvements for large datasets
### [1.1.0] - 2025-05-13
- Added TypeScript support with type definitions
- Added batch operations
- Added configuration options
- Added manual save functionality
- Improved error handling
### [1.0.0] - 2024-05-27
- Major version update.
- Added support for ES6 module syntax.
- Enhanced `delete` method to support batch deletion by specifying a field.
- Added JSDoc comments for all methods.
### [0.1.4] - 2024-05-20
- Initial release with basic CRUD operations.
- Support for array operations with `push`, `find`, and `filter`.
## API Documentation
### Constructor Options
```typescript
interface DbOptions {
autoSave?: boolean; // Auto save after each operation (default: true)
createIfNotExists?: boolean; // Create file if it doesn't exist (default: true)
defaultValue?: Record<string, any>; // Default value for new database
enableIndexing?: boolean; // Enable indexing functionality (default: true)
autoIndex?: boolean; // Auto rebuild indexes on start (default: true)
}
```
### Basic Methods
#### `set(key, value)`
Sets a value in the JSON data.
- **Parameters:**
- `key` (string): The key to set.
- `value` (any): The value to set.
- **Returns:** `NodedbJson` - The instance of the database for chaining.
#### `get(key)`
Gets a value from the JSON data.
- **Parameters:**
- `key` (string): The key to get.
- **Returns:** `any` - The value.
#### `has(key)`
Checks if a key exists in the JSON data.
- **Parameters:**
- `key` (string): The key to check.
- **Returns:** `boolean` - True if the key exists, otherwise false.
#### `update(key, predicateOrUpdater, updater)`
Updates a value in the JSON data.
- **Parameters:**
- `key` (string): The key to update.
- `predicateOrUpdater` (function|object): The predicate function or updater object.
- `updater` (object) [optional]: The updater object if a predicate function is provided.
- **Returns:** `NodedbJson` - The instance of the database for chaining.
#### `delete(key, predicateOrKeys, field)`
Deletes a value from the JSON data.
- **Parameters:**
- `key` (string): The key to delete.
- `predicateOrKeys` (function|string[]) [optional]: The predicate function or array of keys to delete.
- `field` (string) [optional]: The field to match for array deletion. Default is 'id'.
- **Returns:** `NodedbJson` - The instance of the database for chaining.
#### `find(key, predicate)`
Finds a value in the JSON data.
- **Parameters:**
- `key` (string): The key to find.
- `predicate` (function): The predicate function to match.
- **Returns:** `any` - The found value.
#### `filter(key, predicate)`
Filters values in the JSON data.
- **Parameters:**
- `key` (string): The key to filter.
- `predicate` (function): The predicate function to match.
- **Returns:** `any[]` - The filtered values.
#### `push(key, value)`
Pushes a value into an array in the JSON data.
- **Parameters:**
- `key` (string): The key to push to.
- `value` (any|any[]): The value or values to push.
- **Returns:** `NodedbJson` - The instance of the database for chaining.
### Advanced Methods
#### `batch(operations)`
Executes multiple operations in batch.
- **Parameters:**
- `operations` (Array<{method: string, args: any[]}>): Array of operations to execute.
- **Returns:** `NodedbJson` - The instance of the database for chaining.
#### `save()`
Manually save changes to file.
- **Returns:** `NodedbJson` - The instance of the database for chaining.
### Indexing Methods
#### `createIndex(key, indexDefinition)`
Creates an index on a field for faster lookups.
- **Parameters:**
- `key` (string): The collection path to index.
- `indexDefinition` (IndexDefinition): The index definition with field and type.
- **Returns:** `NodedbJson` - The instance of the database for chaining.
#### `findByField(key, field, value)`
Finds a value by field using an index if available.
- **Parameters:**
- `key` (string): The key to find.
- `field` (string): The field to match.
- `value` (any): The value to match.
- **Returns:** `any` - The found value.
#### `filterByField(key, field, values)`
Filters values by field and possible values using an index if available.
- **Parameters:**
- `key` (string): The key to filter.
- `field` (string): The field to match.
- `values` (any[]): The values to match.
- **Returns:** `any[]` - The filtered values.
#### `dropIndex(key, field)`
Removes an index.
- **Parameters:**
- `key` (string): The collection path.
- `field` (string): The field name.
- **Returns:** `NodedbJson` - The instance of the database for chaining.
#### `getIndexes()`
Gets all index definitions.
- **Returns:** `Record<string, Record<string, IndexDefinition>>` - The index definitions.
## Complex Query Operations
NodeDB-JSON supports powerful complex query operations including sorting, pagination, aggregation, and more. All query operations can leverage indexes for optimal performance.
### Core Query Method
#### `query(key, options)`
Executes a complex query with multiple options.
- **Parameters:**
- `key` (string): The collection path to query.
- `options` (QueryOptions): Query configuration object.
- **Returns:** `QueryResult` - Comprehensive query results with data, pagination, aggregations, and statistics.
### Query Options
```typescript
interface QueryOptions<T = any> {
where?: PredicateFunction<T> | Record<string, any>; // Filter conditions
sort?: SortOption | SortOption[]; // Sorting options
pagination?: PaginationOption; // Pagination settings
aggregation?: AggregationOption[]; // Aggregation operations
select?: string[]; // Field selection (projection)
limit?: number; // Limit results
skip?: number; // Skip records
}
```
### Basic Examples
#### Filtering and Sorting
```javascript
// Find tech department employees, sorted by salary (descending)
const result = db.query('users', {
where: { department: '技术部' },
sort: { field: 'salary', direction: 'desc' }
});
console.log('Found:', result.data.length, 'records');
console.log('Execution time:', result.stats.executionTime, 'ms');
console.log('Used index:', result.stats.usedIndex);
```
#### Multi-field Sorting
```javascript
// Sort by department (ascending), then by salary (descending)
const result = db.query('users', {
sort: [
{ field: 'department', direction: 'asc' },
{ field: 'salary', direction: 'desc' }
]
});
```
#### Pagination
```javascript
// Get page 2 with 5 records per page
const result = db.query('users', {
sort: { field: 'salary', direction: 'desc' },
pagination: { page: 2, pageSize: 5 }
});
console.log('Current page:', result.pagination.currentPage);
console.log('Total pages:', result.pagination.totalPages);
console.log('Has next page:', result.pagination.hasNext);
```
#### Field Selection (Projection)
```javascript
// Select only specific fields
const result = db.query('users', {
where: { department: '技术部' },
select: ['name', 'salary', 'age'],
sort: { field: 'salary', direction: 'desc' }
});
```
### Aggregation Operations
#### Basic Aggregations
```javascript
// Get various statistics
const result = db.query('users', {
aggregation: [
{ type: 'count' }, // Count records
{ type: 'avg', field: 'salary' }, // Average salary
{ type: 'sum', field: 'salary' }, // Total salary
{ type: 'min', field: 'salary' }, // Minimum salary
{ type: 'max', field: 'salary' }, // Maximum salary
]
});
result.aggregations?.forEach(agg => {
console.log(`${agg.type}:`, agg.value);
});
```
#### Group By
```javascript
// Group by department
const result = db.query('users', {
aggregation: [
{ type: 'group', groupBy: 'department' }
]
});
const groups = result.aggregations?.[0]?.value as Record<string, any[]>;
Object.entries(groups).forEach(([dept, users]) => {
console.log(`${dept}: ${users.length} employees`);
});
```
### Complex Query Example
```javascript
// Complex query combining multiple features
const result = db.query('users', {
where: user => user.department === '技术部' && user.salary > 12000,
sort: { field: 'age', direction: 'asc' },
pagination: { page: 1, pageSize: 10 },
select: ['name', 'age', 'salary'],
aggregation: [
{ type: 'count' },
{ type: 'avg', field: 'salary' }
]
});
console.log('Results:', result.data);
console.log('Total matching records:', result.aggregations?.[0]?.value);
console.log('Average salary:', result.aggregations?.[1]?.value);
```
### Convenience Methods
For common operations, convenience methods are available:
#### `orderBy(key, sort, limit?)`
Quick sorting with optional limit.
```javascript
// Get top 5 highest paid employees
const topEarners = db.orderBy('users',
{ field: 'salary', direction: 'desc' },
5
);
```
#### `paginate(key, page, pageSize, where?)`
Quick pagination with optional filtering.
```javascript
// Get first page of sales department
const salesPage = db.paginate('users', 1, 10, { department: '销售部' });
```
#### `count(key, where?)`
Count records with optional filtering.
```javascript
// Count tech department employees
const techCount = db.count('users', { department: '技术部' });
```
#### `aggregate(key, aggregations, where?)`
Execute aggregations with optional filtering.
```javascript
// Get tech department salary statistics
const techStats = db.aggregate('users', [
{ type: 'count' },
{ type: 'avg', field: 'salary' },
{ type: 'max', field: 'salary' }
], { department: '技术部' });
```
#### `distinct(key, field)`
Get unique values for a field.
```javascript
// Get all unique departments
const departments = db.distinct('users', 'department');
console.log('Departments:', departments);
```
### Performance Features
- **Index-aware filtering**: Automatically uses indexes when available for object-based where conditions
- **Optimized sorting**: Leverages lodash's efficient orderBy implementation
- **Lazy evaluation**: Pagination and limits are applied efficiently
- **Execution statistics**: Get detailed performance metrics for each query
### Query Result Structure
```typescript
interface QueryResult<T> {
data: T[]; // Query results
pagination?: PaginationInfo; // Pagination details (if used)
aggregations?: AggregationResult[]; // Aggregation results (if used)
stats: {
totalRecords: number; // Total records before filtering
filteredRecords: number; // Records after filtering
executionTime: number; // Query execution time (ms)
usedIndex: boolean; // Whether indexes were used
};
}
```
### License
MIT
### Contact
For any questions or feedback, please contact me at [douyaj33@gmail.com](mailto:douyaj33@gmail.com).
For issues and support, visit the [GitHub Issues page](https://github.com/zxbb1190/nodedb-json/issues).