gitoriaLog in with ident

mpackdb

All repositories: gitoria

ReadmeCodePull requestsReleasesTicketsSettings
Commitb4db6391b4db6391initial commitcaramboleyob4db6391/README.md

10.0 KB

  1. # MPackDB
  2. A fast, local, append-only JSON database with [MessagePack](https://msgpack.org/) serialization for Node.js.
  3. ## Features
  4. - **🚀 High Performance** - Append-only writes with MessagePack binary serialization
  5. - **📇 Flexible Indexing** - Optional numeric and lexical indexes for fast queries
  6. - **🔒 Concurrent Access** - File-based locking for safe multi-process access
  7. - **💾 Auto-Persistence** - Configurable automatic index persistence
  8. - **🗜️ Auto-Compaction** - Removes deleted records on startup
  9. - **🎯 Simple API** - Intuitive CRUD operations with async/await
  10. - **📦 Zero Dependencies** - Only requires `msgpackr`
  11. ## Installation
  12. ```bash
  13. npm install mpackdb
  14. ```
  15. ## Quick Start
  16. ```javascript
  17. import MPackDB from 'mpackdb';
  18. // Create a database with auto-increment numeric primary key
  19. const db = new MPackDB('data/users', {
  20. primaryKey: '*id', // * prefix = numeric auto-increment
  21. indexes: ['email', '*age'] // Index email (lexical) and age (numeric)
  22. });
  23. // Insert records
  24. await db.insert({ name: 'Alice', email: '[email protected]', age: 30 });
  25. await db.insert({ name: 'Bob', email: '[email protected]', age: 25 });
  26. // Find all records
  27. for await (const user of db.find()) {
  28. console.log(user);
  29. }
  30. // Find by primary key
  31. const users = await db.find(0); // Returns array with Alice
  32. // Query with function
  33. for await (const user of db.find(r => r.age > 28)) {
  34. console.log(user.name); // Alice
  35. }
  36. // Update records
  37. await db.update(0, { age: 31 });
  38. await db.update(r => r.age < 26, { status: 'junior' });
  39. // Delete records
  40. await db.delete(r => r.age < 18);
  41. // Close database (persists all changes)
  42. await db.close();
  43. ```
  44. ## API Reference
  45. ### Constructor
  46. ```javascript
  47. new MPackDB(dbFile, options)
  48. ```
  49. **Parameters:**
  50. - `dbFile` (string): Path to database file (without extension)
  51. - `options` (object):
  52. - `primaryKey` (string): Primary key field name with optional prefix:
  53. - `*field` - Numeric auto-increment (e.g., `*id`)
  54. - `@field` - UUID (e.g., `@uuid`)
  55. - `field` - String (e.g., `username`)
  56. - `primaryKeyType` (PrimaryKeyType): Explicit type override
  57. - `indexes` (string[]): Fields to index (use prefixes like primary key)
  58. - `debug` (boolean): Enable debug logging (default: `false`)
  59. - `indexPersistInterval` (number): Auto-persist interval in ms (default: `60000`, `0` to disable)
  60. - `indexPersistThreshold` (number): Auto-persist after N changes (default: `1000`)
  61. **Examples:**
  62. functional style:
  63. ```javascript
  64. import MPackDB from '@worldapi.org/mpackdb';
  65. const db = new MPackDB('data/products', {
  66. primaryKey: '*id',
  67. indexes: ['category', '*price', '@sku'],
  68. indexPersistThreshold: 100
  69. });
  70. ```
  71. OOP style:
  72. ```javascript
  73. import { MPackDB, Model, PrimaryKeyType } from 'mpackdb';
  74. export class Product extends Model {
  75. id = 0;
  76. name = '';
  77. price = 0;
  78. category = '';
  79. sku = '';
  80. }
  81. export class Products extends MPackDB {
  82. _classToUse = Product;
  83. _primaryKey = 'id';
  84. _primaryKeyType = PrimaryKeyType.UUID;
  85. _indexes = ['category', '*price', '@sku'];
  86. }
  87. export default products = new Products('data/products');
  88. ```
  89. ### insert(record, options)
  90. Insert a new record into the database.
  91. ```javascript
  92. await db.insert({ name: 'Alice', age: 30 });
  93. // Returns: { id: 0, name: 'Alice', age: 30 }
  94. ```
  95. **Parameters:**
  96. - `record` (object): The record to insert
  97. - `options` (object):
  98. - `skipPrimaryKey` (boolean): Don't auto-generate primary key
  99. **Returns:** Promise<Object> - The inserted record with primary key
  100. ### find(query, options)
  101. Find records in the database. Returns an async iterable Cursor.
  102. ```javascript
  103. // Find all
  104. for await (const record of db.find()) {
  105. console.log(record);
  106. }
  107. // Find by primary key
  108. const users = await db.find(0);
  109. // Find with query function
  110. for await (const user of db.find(r => r.age > 30)) {
  111. console.log(user.name);
  112. }
  113. ```
  114. **Parameters:**
  115. - `query` (undefined|string|Function):
  116. - `undefined/null` - Returns all records
  117. - `string` - Primary key value
  118. - `Function` - Query function `(record) => boolean`
  119. - `options` (object):
  120. - `mode` (string): Return mode - `'record'`, `'raw'`, or `'mixed'`
  121. **Returns:** Cursor (async iterable)
  122. ### update(query, data, options)
  123. Update records matching a query.
  124. ```javascript
  125. // Update by primary key
  126. await db.update(0, { age: 31 });
  127. // Update with query function
  128. await db.update(r => r.age > 30, { status: 'senior' });
  129. // Update with callback
  130. await db.update(r => r.age > 30, r => ({ ...r, age: r.age + 1 }));
  131. ```
  132. **Parameters:**
  133. - `query` (string|Function): Primary key or query function
  134. - `data` (object|Function): Data to update or callback function
  135. - `options` (object):
  136. - `upsert` (boolean): Insert if no records match
  137. **Returns:** Promise<Object[]> - Array of updated records
  138. ### upsert(query, data)
  139. Update records or insert if not found.
  140. ```javascript
  141. await db.upsert(r => r.email === '[email protected]', {
  142. name: 'Alice',
  143. email: '[email protected]',
  144. age: 30
  145. });
  146. ```
  147. ### delete(query, callback)
  148. Delete records matching a query.
  149. ```javascript
  150. // Delete by primary key
  151. await db.delete(0);
  152. // Delete with query function
  153. await db.delete(r => r.age < 18);
  154. // Delete with callback
  155. await db.delete(r => r.age < 18, async (record) => {
  156. console.log('Deleting:', record.name);
  157. return record;
  158. });
  159. ```
  160. **Parameters:**
  161. - `query` (string|Function): Primary key or query function
  162. - `callback` (Function): Optional callback for each deleted record
  163. **Returns:** Promise<Object[]> - Array of deleted records
  164. ### compact()
  165. Compact the database by removing deleted records. This rewrites the data file without tombstones.
  166. ```javascript
  167. await db.compact();
  168. ```
  169. **Note:** Compaction happens automatically on database initialization.
  170. ### close()
  171. Close the database and persist all pending changes. Should be called before process exit.
  172. ```javascript
  173. await db.close();
  174. ```
  175. ## Primary Key Types
  176. MPackDB supports three primary key types:
  177. ### Numeric (Auto-increment)
  178. ```javascript
  179. const db = new MPackDB('data/users', {
  180. primaryKey: '*id' // * prefix
  181. });
  182. await db.insert({ name: 'Alice' });
  183. // { id: 0, name: 'Alice' }
  184. ```
  185. ### UUID
  186. ```javascript
  187. const db = new MPackDB('data/sessions', {
  188. primaryKey: '@sessionId' // @ prefix
  189. });
  190. await db.insert({ data: 'session data' });
  191. // { sessionId: 'lz7gdcwh9x4r', data: 'session data' }
  192. ```
  193. ### String
  194. ```javascript
  195. const db = new MPackDB('data/users', {
  196. primaryKey: 'username' // No prefix
  197. });
  198. await db.insert({ username: 'alice', name: 'Alice' });
  199. // { username: 'alice', name: 'Alice' }
  200. ```
  201. ## Indexes
  202. Indexes dramatically improve query performance for large datasets.
  203. ### Index Types
  204. - **Lexical** (default): String sorting, good for text fields
  205. - **Numeric**: Number sorting, good for integers/floats
  206. - **UUID**: Treated as lexical (string)
  207. ### Creating Indexes
  208. ```javascript
  209. const db = new MPackDB('data/products', {
  210. primaryKey: '*id',
  211. indexes: [
  212. 'category', // Lexical index
  213. '*price', // Numeric index
  214. '*stock', // Numeric index
  215. '@sku' // UUID index (lexical)
  216. ]
  217. });
  218. ```
  219. ### Index Persistence
  220. Indexes are automatically persisted based on:
  221. - **Threshold**: After N changes (default: 1000)
  222. - **Interval**: Every N milliseconds (default: 60000)
  223. - **On close**: When `db.close()` is called
  224. ```javascript
  225. const db = new MPackDB('data/users', {
  226. primaryKey: '*id',
  227. indexes: ['email'],
  228. indexPersistThreshold: 100, // Persist after 100 changes
  229. indexPersistInterval: 30000 // Persist every 30 seconds
  230. });
  231. ```
  232. ## File Structure
  233. MPackDB creates the following files:
  234. ```
  235. data/
  236. users.mpack # Main data file (MessagePack binary)
  237. users.meta.json # Metadata (nextId, deleted offsets)
  238. users.id.txt # Primary key index
  239. users.email.txt # Email field index
  240. users.age.txt # Age field index
  241. users.lock # Lock file (temporary)
  242. ```
  243. ## Concurrency
  244. MPackDB uses file-based locking to ensure safe concurrent access:
  245. ```javascript
  246. // Process 1
  247. const db1 = new MPackDB('data/users', { primaryKey: '*id' });
  248. await db1.insert({ name: 'Alice' });
  249. // Process 2 (waits for lock)
  250. const db2 = new MPackDB('data/users', { primaryKey: '*id' });
  251. await db2.insert({ name: 'Bob' });
  252. ```
  253. ## Performance Tips
  254. 1. **Use indexes** for frequently queried fields
  255. 2. **Adjust persist thresholds** based on your write patterns
  256. 3. **Call `compact()`** periodically if you have many deletes
  257. 4. **Use numeric indexes** for number fields
  258. 5. **Batch operations** when possible
  259. ## Examples
  260. ### User Management System
  261. ```javascript
  262. import MPackDB from '@worldapi.org/mpackdb';
  263. const users = new MPackDB('data/users', {
  264. primaryKey: '*id',
  265. indexes: ['email', '*age', 'role']
  266. });
  267. // Register user
  268. await users.insert({
  269. email: '[email protected]',
  270. name: 'Alice',
  271. age: 30,
  272. role: 'admin'
  273. });
  274. // Find by email
  275. for await (const user of users.find(u => u.email === '[email protected]')) {
  276. console.log('Found user:', user.name);
  277. }
  278. // Get all admins
  279. for await (const admin of users.find(u => u.role === 'admin')) {
  280. console.log('Admin:', admin.name);
  281. }
  282. // Update age
  283. await users.update(u => u.email === '[email protected]', { age: 31 });
  284. // Delete inactive users
  285. await users.delete(u => u.lastLogin < Date.now() - 90 * 24 * 60 * 60 * 1000);
  286. await users.close();
  287. ```
  288. ### Product Catalog
  289. ```javascript
  290. const products = new MPackDB('data/products', {
  291. primaryKey: '*id',
  292. indexes: ['category', '*price', '@sku']
  293. });
  294. // Add products
  295. await products.insert({ sku: 'ABC-123', name: 'Laptop', category: 'Electronics', price: 999 });
  296. await products.insert({ sku: 'DEF-456', name: 'Mouse', category: 'Electronics', price: 29 });
  297. // Find by category
  298. for await (const product of products.find(p => p.category === 'Electronics')) {
  299. console.log(product.name, product.price);
  300. }
  301. // Find products under $50
  302. for await (const product of products.find(p => p.price < 50)) {
  303. console.log('Affordable:', product.name);
  304. }
  305. // Update price
  306. await products.update(p => p.sku === 'ABC-123', { price: 899 });
  307. await products.close();
  308. ```
  309. ## License
  310. MIT
  311. ## Contributing
  312. Contributions are welcome! Please open an issue or submit a pull request.
  313. ## Related Projects
  314. - [**BsonDB**](https://bsondb.gitoria.worldapi.org) - Similar database using BSON serialization

Branches

Latest commits

  • b4db6391initial commitcaramboleyo