nodejs-json-db 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ 'use strict';var d=require('fs'),$=require('path'),bson=require('bson');function _interopNamespace(e){if(e&&e.__esModule)return e;var n=Object.create(null);if(e){Object.keys(e).forEach(function(k){if(k!=='default'){var d=Object.getOwnPropertyDescriptor(e,k);Object.defineProperty(n,k,d.get?d:{enumerable:true,get:function(){return e[k]}});}})}n.default=e;return Object.freeze(n)}var d__namespace=/*#__PURE__*/_interopNamespace(d);var $__namespace=/*#__PURE__*/_interopNamespace($);var g=class extends Error{constructor(t){super(t),this.name="JsonDBError",Error.captureStackTrace?.(this,this.constructor);}},Q=class extends g{constructor(t,e){super(e?`Document with id "${e}" not found in collection "${t}"`:`Document not found in collection "${t}"`),this.name="DocumentNotFoundError";}},y=class extends g{constructor(t,e){super(`Duplicate key error: document with id "${e}" already exists in collection "${t}"`),this.name="DuplicateKeyError";}},w=class extends g{collectionName;issues;field;value;constructor(t,e,i,r){let n=e.map(s=>`${s.path.join(".")}: ${s.message}`).join("; ");super(`Validation failed for collection "${t}": ${n}`),this.name="ValidationError",this.collectionName=t,this.issues=e,this.field=i,this.value=r;}},f=class extends g{cause;constructor(t,e){super(t),this.name="StorageError",this.cause=e;}},P=class extends g{constructor(t){super(t),this.name="CollectionError";}};var b=class{dataDir;fileExtension;prettyPrint;cache=new Map;locks=new Map;constructor(t){this.dataDir=$__namespace.resolve(t.dataDir),this.fileExtension=t.fileExtension||".json",this.prettyPrint=t.prettyPrint??true,this.ensureDirectory();}ensureDirectory(){try{d__namespace.existsSync(this.dataDir)||d__namespace.mkdirSync(this.dataDir,{recursive:!0});}catch(t){throw new f(`Failed to create data directory: ${this.dataDir}`,t)}}getFilePath(t){return $__namespace.join(this.dataDir,`${t}${this.fileExtension}`)}async acquireLock(t){for(;this.locks.has(t);)await this.locks.get(t);let e=()=>{},i=new Promise(r=>{e=r;});return this.locks.set(t,i),()=>{this.locks.delete(t),e();}}async read(t){if(this.cache.has(t))return this.cache.get(t);let e=this.getFilePath(t);try{if(!d__namespace.existsSync(e))return null;let i=await d__namespace.promises.readFile(e,"utf-8"),r=JSON.parse(i);return this.cache.set(t,r),r}catch(i){throw new f(`Failed to read collection "${t}"`,i)}}async write(t,e){let i=this.getFilePath(t),r=`${i}.tmp.${Date.now()}`,n=await this.acquireLock(t);try{e.updatedAt=new Date().toISOString();let s=this.prettyPrint?JSON.stringify(e,null,2):JSON.stringify(e);await d__namespace.promises.writeFile(r,s,"utf-8"),await d__namespace.promises.rename(r,i),this.cache.set(t,e);}catch(s){try{d__namespace.existsSync(r)&&await d__namespace.promises.unlink(r);}catch{}throw new f(`Failed to write collection "${t}"`,s)}finally{n();}}async exists(t){let e=this.getFilePath(t);return d__namespace.existsSync(e)}async delete(t){let e=this.getFilePath(t),i=await this.acquireLock(t);try{d__namespace.existsSync(e)&&await d__namespace.promises.unlink(e),this.cache.delete(t);}catch(r){throw new f(`Failed to delete collection "${t}"`,r)}finally{i();}}async list(){try{return (await d__namespace.promises.readdir(this.dataDir)).filter(e=>e.endsWith(this.fileExtension)).map(e=>e.slice(0,-this.fileExtension.length))}catch(t){throw new f("Failed to list collections",t)}}clearCache(t){t?this.cache.delete(t):this.cache.clear();}getDataDir(){return this.dataDir}};function v(a){return new bson.ObjectId().toHexString()}function H(a){return typeof a!="string"||a.length===0||a.length>64?false:a.length===24?bson.ObjectId.isValid(a):true}function l(a){if(a===null||typeof a!="object")return a;if(a instanceof Date)return new Date(a.getTime());if(a instanceof RegExp)return new RegExp(a.source,a.flags);if(Array.isArray(a))return a.map(e=>l(e));let t={};for(let e in a)Object.prototype.hasOwnProperty.call(a,e)&&(t[e]=l(a[e]));return t}function A(a,t){let e=t.split("."),i=a;for(let r of e){if(i==null||typeof i!="object")return;i=i[r];}return i}function W(a,t,e){let i=t.split("."),r=a;for(let n=0;n<i.length-1;n++){let s=i[n];(!(s in r)||typeof r[s]!="object"||r[s]===null)&&(r[s]={}),r=r[s];}r[i[i.length-1]]=e;}function q(a,t){let e=t.split("."),i=a;for(let n=0;n<e.length-1;n++){let s=e[n];if(!(s in i)||typeof i[s]!="object"||i[s]===null)return false;i=i[s];}let r=e[e.length-1];return r in i?(delete i[r],true):false}function _(a){return typeof a=="object"&&a!==null&&!Array.isArray(a)&&!(a instanceof Date)&&!(a instanceof RegExp)}var T=class{filter(t,e){return !e||Object.keys(e).length===0?t:t.filter(i=>this.matches(i,e))}matches(t,e){if("$and"in e&&e.$and)return e.$and.every(i=>this.matches(t,i));if("$or"in e&&e.$or)return e.$or.some(i=>this.matches(t,i));if("$not"in e&&e.$not)return !this.matches(t,e.$not);for(let[i,r]of Object.entries(e)){if(i.startsWith("$"))continue;let n=A(t,i);if(!this.matchesCondition(n,r))return false}return true}matchesCondition(t,e){if(!_(e)||!this.hasOperators(e))return this.isEqual(t,e);let i=e;if("$eq"in i&&!this.isEqual(t,i.$eq)||"$ne"in i&&this.isEqual(t,i.$ne)||"$gt"in i&&!this.compareValues(t,i.$gt,">")||"$gte"in i&&!this.compareValues(t,i.$gte,">=")||"$lt"in i&&!this.compareValues(t,i.$lt,"<")||"$lte"in i&&!this.compareValues(t,i.$lte,"<=")||"$in"in i&&Array.isArray(i.$in)&&!i.$in.some(r=>this.isEqual(t,r))||"$nin"in i&&Array.isArray(i.$nin)&&i.$nin.some(r=>this.isEqual(t,r)))return false;if("$exists"in i){let r=t!=null;if(i.$exists!==r)return false}return !("$regex"in i&&(typeof t!="string"||!(i.$regex instanceof RegExp?i.$regex:new RegExp(i.$regex)).test(t))||"$startsWith"in i&&(typeof t!="string"||typeof i.$startsWith!="string"||!t.startsWith(i.$startsWith))||"$endsWith"in i&&(typeof t!="string"||typeof i.$endsWith!="string"||!t.endsWith(i.$endsWith))||"$contains"in i&&(!Array.isArray(t)||!t.some(r=>this.isEqual(r,i.$contains))))}hasOperators(t){return Object.keys(t).some(e=>e.startsWith("$"))}isEqual(t,e){if(t===e)return true;if(t===null||e===null||t===void 0||e===void 0)return t===e;if(typeof t!=typeof e)return false;if(t instanceof Date&&e instanceof Date)return t.getTime()===e.getTime();if(Array.isArray(t)&&Array.isArray(e))return t.length!==e.length?false:t.every((i,r)=>this.isEqual(i,e[r]));if(typeof t=="object"&&typeof e=="object"){let i=Object.keys(t),r=Object.keys(e);return i.length!==r.length?false:i.every(n=>this.isEqual(t[n],e[n]))}return false}compareValues(t,e,i){if(typeof t=="number"&&typeof e=="number")switch(i){case ">":return t>e;case ">=":return t>=e;case "<":return t<e;case "<=":return t<=e}if(typeof t=="string"&&typeof e=="string")switch(i){case ">":return t>e;case ">=":return t>=e;case "<":return t<e;case "<=":return t<=e}if(t instanceof Date&&e instanceof Date)switch(i){case ">":return t.getTime()>e.getTime();case ">=":return t.getTime()>=e.getTime();case "<":return t.getTime()<e.getTime();case "<=":return t.getTime()<=e.getTime()}return false}};var D=class{name;storage;queryEngine;autoSave;saveDebounce;idGenerator;schema;saveTimeout=null;pendingSave=null;constructor(t,e,i={}){this.name=t,this.storage=e,this.queryEngine=new T,this.autoSave=i.autoSave??true,this.saveDebounce=i.saveDebounce??0,this.idGenerator=i.idGenerator??v,this.schema=i.schema;}validate(t){if(!this.schema)return t;let e=this.schema.safeParse(t);if(!e.success)throw new w(this.name,e.error.issues);return e.data}async getData(){let t=await this.storage.read(this.name);if(!t){let e={name:this.name,documents:[],createdAt:new Date().toISOString(),updatedAt:new Date().toISOString()};return await this.storage.write(this.name,e),e}return t}async save(t){if(this.autoSave){if(this.saveDebounce>0)return this.saveTimeout&&clearTimeout(this.saveTimeout),new Promise(e=>{this.saveTimeout=setTimeout(async()=>{await this.storage.write(this.name,t),this.saveTimeout=null,e();},this.saveDebounce);});await this.storage.write(this.name,t);}}async flush(){this.saveTimeout&&(clearTimeout(this.saveTimeout),this.saveTimeout=null),this.pendingSave&&await this.pendingSave;let t=await this.getData();await this.storage.write(this.name,t);}async insert(t){let e=await this.getData(),i=t._id||this.idGenerator();if(e.documents.some(s=>s._id===i))throw new y(this.name,i);let r={...l(t),_id:i},n=this.validate(r);return e.documents.push(n),await this.save(e),l(n)}async insertFast(t){let e=await this.getData(),i=t._id||this.idGenerator(),r={...l(t),_id:i},n=this.validate(r);return e.documents.push(n),await this.save(e),l(n)}async insertMany(t){if(t.length===0)return [];let e=await this.getData(),i=[],r=new Set(e.documents.map(n=>n._id));for(let n of t){let s=n._id||this.idGenerator();if(r.has(s))throw new y(this.name,s);r.add(s);let o={...l(n),_id:s},c=this.validate(o);e.documents.push(c),i.push(l(c));}return await this.save(e),i}async find(t,e){let i=await this.getData(),r=this.queryEngine.filter(i.documents,t);return e?.sort&&(r=this.sortDocuments(r,e.sort)),e?.skip&&e.skip>0&&(r=r.slice(e.skip)),e?.limit&&e.limit>0&&(r=r.slice(0,e.limit)),r.map(n=>l(n))}async findOne(t){let e=await this.find(t,{limit:1});return e.length>0?e[0]:null}async findById(t){return this.findOne({_id:t})}async count(t){let e=await this.getData();return this.queryEngine.filter(e.documents,t).length}async update(t,e){let i=await this.getData(),r=this.queryEngine.filter(i.documents,t);if(r.length===0)return 0;for(let n of r)this.applyUpdate(n,e);return await this.save(i),r.length}async updateOne(t,e){let i=await this.getData(),r=this.queryEngine.filter(i.documents,t);if(r.length===0)return null;let n=r[0];return this.applyUpdate(n,e),await this.save(i),l(n)}async updateById(t,e){return this.updateOne({_id:t},e)}async delete(t){let e=await this.getData(),i=e.documents.length;e.documents=e.documents.filter(n=>!this.queryEngine.matches(n,t));let r=i-e.documents.length;return r>0&&await this.save(e),r}async deleteOne(t){let e=await this.getData(),i=e.documents.findIndex(n=>this.queryEngine.matches(n,t));if(i===-1)return null;let[r]=e.documents.splice(i,1);return await this.save(e),l(r)}async deleteById(t){return this.deleteOne({_id:t})}async getAll(){return this.find()}async clear(){let t=await this.getData();t.documents=[],await this.save(t);}async drop(){await this.storage.delete(this.name);}getName(){return this.name}applyUpdate(t,e){if(!Object.keys(e).some(n=>n.startsWith("$"))){Object.assign(t,e);return}let r=e;if(r.$set)for(let[n,s]of Object.entries(r.$set))n!=="_id"&&W(t,n,s);if(r.$unset)for(let n of Object.keys(r.$unset))n!=="_id"&&q(t,n);if(r.$inc)for(let[n,s]of Object.entries(r.$inc)){let o=t[n];typeof o=="number"&&typeof s=="number"&&(t[n]=o+s);}if(r.$push)for(let[n,s]of Object.entries(r.$push)){let o=t[n];Array.isArray(o)&&o.push(s);}if(r.$pull)for(let[n,s]of Object.entries(r.$pull)){let o=t[n];if(Array.isArray(o)){let c=o.findIndex(u=>JSON.stringify(u)===JSON.stringify(s));c!==-1&&o.splice(c,1);}}if(r.$addToSet)for(let[n,s]of Object.entries(r.$addToSet)){let o=t[n];Array.isArray(o)&&(o.some(u=>JSON.stringify(u)===JSON.stringify(s))||o.push(s));}}sortDocuments(t,e){let i=Object.entries(e);return [...t].sort((r,n)=>{for(let[s,o]of i){let c=r[s],u=n[s],h=0;if(c===u?h=0:c==null?h=1:u==null?h=-1:typeof c=="number"&&typeof u=="number"?h=c-u:typeof c=="string"&&typeof u=="string"?h=c.localeCompare(u):c instanceof Date&&u instanceof Date?h=c.getTime()-u.getTime():h=String(c).localeCompare(String(u)),h!==0)return h*(o===-1||o==="desc"?-1:1)}return 0})}};var C=class{batchSize;flushInterval;coalesceWrites;batchProcessor;queue=new Map;flushTimer=null;pendingFlush=null;totalQueued=0;isShuttingDown=false;constructor(t,e={}){this.batchSize=e.batchSize??1e3,this.flushInterval=e.flushInterval??100,this.coalesceWrites=e.coalesceWrites??true,this.batchProcessor=t;}async enqueue(t){if(this.isShuttingDown)throw new Error("WriteQueue is shutting down, no new writes accepted");return new Promise((e,i)=>{let r={operation:t,resolve:e,reject:i,timestamp:Date.now()},n=t.collectionName,s=this.queue.get(n);s||(s=[],this.queue.set(n,s)),!(this.coalesceWrites&&this.tryCoalesce(n,r))&&(s.push(r),this.totalQueued++,this.scheduleFlush(),this.totalQueued>=this.batchSize&&this.flush().catch(i));})}tryCoalesce(t,e){let i=this.queue.get(t);if(!i||i.length===0)return false;let r=e.operation;if(r.type==="update")for(let n=i.length-1;n>=0;n--){let s=i[n].operation;if(s.type==="update"&&s.documentId===r.documentId){s.changes={...s.changes,...r.changes};let o=i[n].resolve;return i[n].resolve=()=>{o(),e.resolve();},true}}if(r.type==="delete")for(let n=i.length-1;n>=0;n--){let s=i[n].operation;(s.type==="insert"&&s.document._id===r.documentId||s.type==="update"&&s.documentId===r.documentId)&&(i.splice(n,1),this.totalQueued--);}if(r.type==="clear"||r.type==="fullWrite"){for(let n of i)n.resolve();i.length=0,this.totalQueued-=i.length;}return false}scheduleFlush(){this.flushTimer===null&&(this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flush().catch(t=>{console.error("WriteQueue flush error:",t);});},this.flushInterval));}async flush(){if(this.pendingFlush)return this.pendingFlush;if(this.flushTimer!==null&&(clearTimeout(this.flushTimer),this.flushTimer=null),this.totalQueued===0)return;let t=this.queue;this.queue=new Map,this.totalQueued=0;let e=new Map;for(let[i,r]of t)e.set(i,r.map(n=>n.operation));this.pendingFlush=this.processBatch(t,e),await this.pendingFlush,this.pendingFlush=null;}async processBatch(t,e){try{await this.batchProcessor(e);for(let i of t.values())for(let r of i)r.resolve();}catch(i){for(let r of t.values())for(let n of r)n.reject(i);}}async shutdown(){this.isShuttingDown=true,await this.flush();}pending(){return this.totalQueued}isEmpty(){return this.totalQueued===0}isClosing(){return this.isShuttingDown}};var x=class{dataDir;partitionCount;prettyPrint;fileExtension;cache=new Map;locks=new Map;constructor(t,e={}){this.dataDir=$__namespace.resolve(t),this.partitionCount=e.partitionCount??16,this.prettyPrint=e.prettyPrint??true,this.fileExtension=e.fileExtension??".json",this.ensureDirectory();}ensureDirectory(){try{d__namespace.existsSync(this.dataDir)||d__namespace.mkdirSync(this.dataDir,{recursive:!0});}catch(t){throw new f(`Failed to create data directory: ${this.dataDir}`,t)}}getPartitionIndex(t){let e=0;for(let i=0;i<t.length;i++){let r=t.charCodeAt(i);e=(e<<5)-e+r,e=e&e;}return Math.abs(e)%this.partitionCount}getPartitionFileName(t,e){return `${t}_p${e.toString().padStart(3,"0")}${this.fileExtension}`}getPartitionFilePath(t,e){return $__namespace.join(this.dataDir,this.getPartitionFileName(t,e))}getCacheKey(t,e){return `${t}:${e}`}async acquireLock(t){for(;this.locks.has(t);)await this.locks.get(t);let e=()=>{},i=new Promise(r=>{e=r;});return this.locks.set(t,i),()=>{this.locks.delete(t),e();}}async readPartition(t,e){let i=this.getCacheKey(t,e);if(this.cache.has(i))return this.cache.get(i);let r=this.getPartitionFilePath(t,e);try{if(!d__namespace.existsSync(r))return null;let n=await d__namespace.promises.readFile(r,"utf-8"),s=JSON.parse(n);return this.cache.set(i,s),s}catch(n){throw new f(`Failed to read partition ${e} for collection "${t}"`,n)}}async writePartition(t,e,i){let r=this.getPartitionFilePath(t,e),n=`${r}.tmp.${Date.now()}`,s=this.getCacheKey(t,e),o=await this.acquireLock(s);try{i.updatedAt=new Date().toISOString();let c=this.prettyPrint?JSON.stringify(i,null,2):JSON.stringify(i);await d__namespace.promises.writeFile(n,c,"utf-8"),await d__namespace.promises.rename(n,r),this.cache.set(s,i);}catch(c){try{d__namespace.existsSync(n)&&await d__namespace.promises.unlink(n);}catch{}throw new f(`Failed to write partition ${e} for collection "${t}"`,c)}finally{o();}}async readAllPartitions(t){let e=new Map,i=[];for(let r=0;r<this.partitionCount;r++)i.push(this.readPartition(t,r).then(n=>{n&&e.set(r,n);}));return await Promise.all(i),e}async writePartitions(t,e){let i=[];for(let[r,n]of e)i.push(this.writePartition(t,r,n));await Promise.all(i);}async initializePartitions(t){let e=[];for(let i=0;i<this.partitionCount;i++)if(!d__namespace.existsSync(this.getPartitionFilePath(t,i))){let n={name:t,documents:[],createdAt:new Date().toISOString(),updatedAt:new Date().toISOString()};e.push(this.writePartition(t,i,n));}await Promise.all(e);}async getPartitionInfo(t){let e=await this.readAllPartitions(t),i=[];for(let r=0;r<this.partitionCount;r++){let n=e.get(r);i.push({name:this.getPartitionFileName(t,r),partitionIndex:r,documentCount:n?.documents.length??0});}return i}async getAllDocuments(t){let e=await this.readAllPartitions(t),i=[];for(let r of e.values())i.push(...r.documents);return i}async findById(t,e){let i=this.getPartitionIndex(e),r=await this.readPartition(t,i);return r?r.documents.find(n=>n._id===e)??null:null}async deleteCollection(t){let e=[];for(let i=0;i<this.partitionCount;i++){let r=this.getPartitionFilePath(t,i),n=this.getCacheKey(t,i);e.push((async()=>{let s=await this.acquireLock(n);try{d__namespace.existsSync(r)&&await d__namespace.promises.unlink(r),this.cache.delete(n);}finally{s();}})());}await Promise.all(e);}clearCache(t){if(t)for(let e=0;e<this.partitionCount;e++)this.cache.delete(this.getCacheKey(t,e));else this.cache.clear();}getPartitionCount(){return this.partitionCount}async listCollections(){try{let t=await d__namespace.promises.readdir(this.dataDir),e=new Set,i=new RegExp(`^(.+)_p\\d{3}\\${this.fileExtension}$`);for(let r of t){let n=r.match(i);n&&e.add(n[1]);}return Array.from(e)}catch(t){throw new f("Failed to list partitioned collections",t)}}};var S=class{maxConcurrent;maxQueueSize;queue=[];activeCount=0;completedCount=0;failedCount=0;isShuttingDown=false;constructor(t={}){this.maxConcurrent=t.maxConcurrent??4,this.maxQueueSize=t.maxQueueSize??1e4;}async submit(t,e=0){if(this.isShuttingDown)throw new Error("WorkerPool is shutting down, no new tasks accepted");if(this.queue.length>=this.maxQueueSize)throw new Error("WorkerPool queue is full, task rejected (backpressure)");return new Promise((i,r)=>{let n={task:t,resolve:i,reject:r,priority:e},s=false;for(let o=0;o<this.queue.length;o++)if(e>this.queue[o].priority){this.queue.splice(o,0,n),s=true;break}s||this.queue.push(n),this.processNext();})}async submitAll(t,e=0){let i=t.map(r=>this.submit(r,e));return Promise.all(i)}async*submitStream(t,e=0){let i=t.map(r=>this.submit(r,e));for(let r of i)yield await r;}processNext(){if(this.activeCount>=this.maxConcurrent||this.queue.length===0)return;let t=this.queue.shift();t&&(this.activeCount++,t.task().then(e=>{this.completedCount++,t.resolve(e);}).catch(e=>{this.failedCount++,t.reject(e);}).finally(()=>{this.activeCount--,this.processNext();}));}async drain(){return new Promise(t=>{let e=()=>{this.activeCount===0&&this.queue.length===0?t():setImmediate(e);};e();})}async shutdown(){this.isShuttingDown=true,await this.drain();}getStats(){return {activeWorkers:this.activeCount,queuedTasks:this.queue.length,completedTasks:this.completedCount,failedTasks:this.failedCount}}isIdle(){return this.activeCount===0&&this.queue.length===0}isClosing(){return this.isShuttingDown}queueSize(){return this.queue.length}activeWorkers(){return this.activeCount}};async function I(a,t,e){let i=[],r=0;async function n(){let o=r++;o>=a.length||(i[o]=await e(a[o],o),await n());}let s=Array(Math.min(t,a.length)).fill(null).map(()=>n());return await Promise.all(s),i}var R={partitions:16,batchSize:1e3,flushInterval:100,maxConcurrentIO:4,coalesceWrites:true},k=class{partitionManager;writeQueue;workerPool;options;constructor(t){let e=t.highConcurrency;this.options={...R,...e},this.partitionManager=new x(t.dataDir,{partitionCount:this.options.partitions,prettyPrint:t.prettyPrint??true,fileExtension:t.fileExtension??".json"}),this.workerPool=new S({maxConcurrent:this.options.maxConcurrentIO});let i=this.processBatch.bind(this);this.writeQueue=new C(i,{batchSize:this.options.batchSize,flushInterval:this.options.flushInterval,coalesceWrites:this.options.coalesceWrites});}async processBatch(t){let e=Array.from(t.entries());await I(e,this.options.maxConcurrentIO,async([i,r])=>{await this.processCollectionOperations(i,r);});}async processCollectionOperations(t,e){let i=new Map,r=n=>{let s=i.get(n);return s||(s=[],i.set(n,s)),s};for(let n of e)if(n.type==="fullWrite"||n.type==="clear")for(let s=0;s<this.partitionManager.getPartitionCount();s++)r(s).push(n);else if(n.type==="insert"){let s=this.partitionManager.getPartitionIndex(n.document._id);r(s).push(n);}else if(n.type==="update"||n.type==="delete"){let s=this.partitionManager.getPartitionIndex(n.documentId);r(s).push(n);}else if(n.type==="bulkInsert")for(let s of n.documents){let o=this.partitionManager.getPartitionIndex(s._id);r(o).push({type:"insert",collectionName:t,document:s});}else if(n.type==="bulkUpdate"||n.type==="bulkDelete")for(let s of n.documentIds){let o=this.partitionManager.getPartitionIndex(s);n.type==="bulkUpdate"?r(o).push({type:"update",collectionName:t,documentId:s,changes:n.changes}):r(o).push({type:"delete",collectionName:t,documentId:s});}await I(Array.from(i.entries()),this.options.maxConcurrentIO,async([n,s])=>{await this.processPartitionOperations(t,n,s);});}async processPartitionOperations(t,e,i){let r=await this.partitionManager.readPartition(t,e);r||(r={name:t,documents:[],createdAt:new Date().toISOString(),updatedAt:new Date().toISOString()});for(let n of i)switch(n.type){case "insert":r.documents.push(n.document);break;case "update":{let s=r.documents.findIndex(o=>o._id===n.documentId);s!==-1&&Object.assign(r.documents[s],n.changes);break}case "delete":{let s=r.documents.findIndex(o=>o._id===n.documentId);s!==-1&&r.documents.splice(s,1);break}case "clear":r.documents=[];break;case "fullWrite":{let s=n.data.documents.filter(o=>this.partitionManager.getPartitionIndex(o._id)===e);r.documents=s;break}}await this.partitionManager.writePartition(t,e,r);}async insert(t,e){await this.writeQueue.enqueue({type:"insert",collectionName:t,document:e});}async insertMany(t,e){await this.writeQueue.enqueue({type:"bulkInsert",collectionName:t,documents:e});}async update(t,e,i){await this.writeQueue.enqueue({type:"update",collectionName:t,documentId:e,changes:i});}async delete(t,e){await this.writeQueue.enqueue({type:"delete",collectionName:t,documentId:e});}async clear(t){await this.writeQueue.enqueue({type:"clear",collectionName:t});}async findById(t,e){return await this.writeQueue.flush(),this.partitionManager.findById(t,e)}async readAll(t){return await this.writeQueue.flush(),this.partitionManager.getAllDocuments(t)}async exists(t){return (await this.partitionManager.listCollections()).includes(t)}async initializeCollection(t){await this.partitionManager.initializePartitions(t);}async deleteCollection(t){await this.writeQueue.flush(),await this.partitionManager.deleteCollection(t);}async listCollections(){return this.partitionManager.listCollections()}async flush(){await this.writeQueue.flush();}async shutdown(){await this.writeQueue.shutdown(),await this.workerPool.shutdown();}pendingWrites(){return this.writeQueue.pending()}getStats(){return {pendingWrites:this.writeQueue.pending(),workerPool:this.workerPool.getStats(),partitions:this.options.partitions}}clearCache(t){this.partitionManager.clearCache(t);}};var O=class{name;storage;queryEngine;idGenerator;schema;constructor(t,e,i={}){this.name=t,this.storage=e,this.queryEngine=new T,this.idGenerator=i.idGenerator??v,this.schema=i.schema;}validate(t){if(!this.schema)return t;let e=this.schema.safeParse(t);if(!e.success)throw new w(this.name,e.error.issues);return e.data}async insert(t){let e=t._id||this.idGenerator();if(await this.storage.findById(this.name,e))throw new y(this.name,e);let r={...l(t),_id:e},n=this.validate(r);return await this.storage.insert(this.name,n),n}async insertFast(t){let e=t._id||this.idGenerator(),i={...l(t),_id:e},r=this.validate(i);return await this.storage.insert(this.name,r),r}async insertMany(t){if(t.length===0)return [];let e=[],i=[];for(let r of t){let n=r._id||this.idGenerator(),s={...l(r),_id:n},o=this.validate(s);i.push(o),e.push(l(o));}return await this.storage.insertMany(this.name,i),e}async find(t,e){let i=await this.storage.readAll(this.name),r=this.queryEngine.filter(i,t);return e?.sort&&(r=this.sortDocuments(r,e.sort)),e?.skip&&e.skip>0&&(r=r.slice(e.skip)),e?.limit&&e.limit>0&&(r=r.slice(0,e.limit)),r.map(n=>l(n))}async findOne(t){let e=await this.find(t,{limit:1});return e.length>0?e[0]:null}async findById(t){let e=await this.storage.findById(this.name,t);return e?l(e):null}async count(t){let e=await this.storage.readAll(this.name);return this.queryEngine.filter(e,t).length}async update(t,e){let i=await this.storage.readAll(this.name),r=this.queryEngine.filter(i,t);if(r.length===0)return 0;for(let n of r){let s=this.getUpdateChanges(n,e);await this.storage.update(this.name,n._id,s);}return r.length}async updateOne(t,e){let i=await this.storage.readAll(this.name),r=this.queryEngine.filter(i,t);if(r.length===0)return null;let n=r[0],s=this.getUpdateChanges(n,e);return await this.storage.update(this.name,n._id,s),Object.assign(n,s),l(n)}async updateById(t,e){return this.updateOne({_id:t},e)}async delete(t){let e=await this.storage.readAll(this.name),i=this.queryEngine.filter(e,t);if(i.length===0)return 0;for(let r of i)await this.storage.delete(this.name,r._id);return i.length}async deleteOne(t){let e=await this.storage.readAll(this.name),i=this.queryEngine.filter(e,t);if(i.length===0)return null;let r=i[0];return await this.storage.delete(this.name,r._id),l(r)}async deleteById(t){return this.deleteOne({_id:t})}async getAll(){return this.find()}async clear(){await this.storage.clear(this.name);}async drop(){await this.storage.deleteCollection(this.name);}async flush(){await this.storage.flush();}getName(){return this.name}getUpdateChanges(t,e){if(!Object.keys(e).some(s=>s.startsWith("$")))return e;let r=e,n={};if(r.$set)for(let[s,o]of Object.entries(r.$set))s!=="_id"&&(n[s]=o);if(r.$inc)for(let[s,o]of Object.entries(r.$inc)){let c=t[s];typeof c=="number"&&typeof o=="number"&&(n[s]=c+o);}if(r.$push)for(let[s,o]of Object.entries(r.$push)){let c=t[s];Array.isArray(c)&&(n[s]=[...c,o]);}if(r.$pull)for(let[s,o]of Object.entries(r.$pull)){let c=t[s];Array.isArray(c)&&(n[s]=c.filter(u=>JSON.stringify(u)!==JSON.stringify(o)));}if(r.$addToSet)for(let[s,o]of Object.entries(r.$addToSet)){let c=t[s];Array.isArray(c)&&(c.some(h=>JSON.stringify(h)===JSON.stringify(o))?n[s]=c:n[s]=[...c,o]);}return n}sortDocuments(t,e){let i=Object.entries(e);return [...t].sort((r,n)=>{for(let[s,o]of i){let c=r[s],u=n[s],h=0;if(c===u?h=0:c==null?h=1:u==null?h=-1:typeof c=="number"&&typeof u=="number"?h=c-u:typeof c=="string"&&typeof u=="string"?h=c.localeCompare(u):c instanceof Date&&u instanceof Date?h=c.getTime()-u.getTime():h=String(c).localeCompare(String(u)),h!==0)return h*(o===-1||o==="desc"?-1:1)}return 0})}};var V={autoSave:true,saveDebounce:0,prettyPrint:true,fileExtension:".json"},F=class{options;storage;hcStorage;collections=new Map;isHighConcurrency;connected=false;constructor(t){this.options={...V,...t},this.isHighConcurrency=t.highConcurrency?.enabled??false,this.isHighConcurrency?(this.storage=null,this.hcStorage=new k(this.options)):(this.storage=new b(this.options),this.hcStorage=null);}getStorage(){if(this.storage===null)throw new Error("Storage is not available in high-concurrency mode");return this.storage}getHCStorage(){if(this.hcStorage===null)throw new Error("HighConcurrencyStorage is not available in standard mode");return this.hcStorage}async connect(){if(!this.connected){if(this.isHighConcurrency){let t=await this.getHCStorage().listCollections();for(let e of t)this.getOrCreateCollection(e);}else {let t=await this.getStorage().list();for(let e of t)this.getOrCreateCollection(e);}this.connected=true;}}async close(){if(this.connected){for(let t of this.collections.values())await t.flush();this.isHighConcurrency&&this.hcStorage&&await this.hcStorage.shutdown(),this.collections.clear(),this.storage&&this.storage.clearCache(),this.hcStorage&&this.hcStorage.clearCache(),this.connected=false;}}collection(t,e){if(!t||typeof t!="string")throw new P("Collection name must be a non-empty string");if(!/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(t))throw new P("Collection name must start with a letter or underscore and contain only letters, numbers, underscores, and hyphens");return this.getOrCreateCollection(t,e)}getOrCreateCollection(t,e){if(this.collections.has(t))return this.collections.get(t);if(this.isHighConcurrency){let i=new O(t,this.getHCStorage(),{schema:e?.schema});this.collections.set(t,i);}else {let i=new D(t,this.getStorage(),{autoSave:this.options.autoSave,saveDebounce:this.options.saveDebounce,schema:e?.schema});this.collections.set(t,i);}return this.collections.get(t)}async hasCollection(t){return this.isHighConcurrency?this.getHCStorage().exists(t):this.getStorage().exists(t)}async listCollections(){return this.isHighConcurrency?this.getHCStorage().listCollections():this.getStorage().list()}async dropCollection(t){let e=this.collections.get(t);e?(await e.drop(),this.collections.delete(t)):this.isHighConcurrency?await this.getHCStorage().deleteCollection(t):await this.getStorage().delete(t);}async drop(){if(this.isHighConcurrency){let t=this.getHCStorage(),e=await t.listCollections();for(let i of e)await t.deleteCollection(i);}else {let t=this.getStorage(),e=await t.list();for(let i of e)await t.delete(i);}this.collections.clear(),this.storage?.clearCache(),this.hcStorage?.clearCache();}getDataDir(){return this.isHighConcurrency?this.options.dataDir:this.getStorage().getDataDir()}isConnected(){return this.connected}isHighConcurrencyMode(){return this.isHighConcurrency}getStats(){return !this.isHighConcurrency||!this.hcStorage?null:this.hcStorage.getStats()}async flush(){for(let t of this.collections.values())await t.flush();}};exports.Collection=D;exports.CollectionError=P;exports.DocumentNotFoundError=Q;exports.DuplicateKeyError=y;exports.HighConcurrencyCollection=O;exports.HighConcurrencyStorage=k;exports.JsonDB=F;exports.JsonDBError=g;exports.PartitionManager=x;exports.StorageError=f;exports.ValidationError=w;exports.WorkerPool=S;exports.WriteQueue=C;exports.generateId=v;exports.isValidId=H;exports.parallelLimit=I;
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ import*as d from'fs';import*as E from'path';import {ObjectId}from'bson';var y=class extends Error{constructor(t){super(t),this.name="JsonDBError",Error.captureStackTrace?.(this,this.constructor);}},A=class extends y{constructor(t,e){super(e?`Document with id "${e}" not found in collection "${t}"`:`Document not found in collection "${t}"`),this.name="DocumentNotFoundError";}},w=class extends y{constructor(t,e){super(`Duplicate key error: document with id "${e}" already exists in collection "${t}"`),this.name="DuplicateKeyError";}},T=class extends y{collectionName;issues;field;value;constructor(t,e,i,r){let n=e.map(s=>`${s.path.join(".")}: ${s.message}`).join("; ");super(`Validation failed for collection "${t}": ${n}`),this.name="ValidationError",this.collectionName=t,this.issues=e,this.field=i,this.value=r;}},f=class extends y{cause;constructor(t,e){super(t),this.name="StorageError",this.cause=e;}},v=class extends y{constructor(t){super(t),this.name="CollectionError";}};var $=class{dataDir;fileExtension;prettyPrint;cache=new Map;locks=new Map;constructor(t){this.dataDir=E.resolve(t.dataDir),this.fileExtension=t.fileExtension||".json",this.prettyPrint=t.prettyPrint??true,this.ensureDirectory();}ensureDirectory(){try{d.existsSync(this.dataDir)||d.mkdirSync(this.dataDir,{recursive:!0});}catch(t){throw new f(`Failed to create data directory: ${this.dataDir}`,t)}}getFilePath(t){return E.join(this.dataDir,`${t}${this.fileExtension}`)}async acquireLock(t){for(;this.locks.has(t);)await this.locks.get(t);let e=()=>{},i=new Promise(r=>{e=r;});return this.locks.set(t,i),()=>{this.locks.delete(t),e();}}async read(t){if(this.cache.has(t))return this.cache.get(t);let e=this.getFilePath(t);try{if(!d.existsSync(e))return null;let i=await d.promises.readFile(e,"utf-8"),r=JSON.parse(i);return this.cache.set(t,r),r}catch(i){throw new f(`Failed to read collection "${t}"`,i)}}async write(t,e){let i=this.getFilePath(t),r=`${i}.tmp.${Date.now()}`,n=await this.acquireLock(t);try{e.updatedAt=new Date().toISOString();let s=this.prettyPrint?JSON.stringify(e,null,2):JSON.stringify(e);await d.promises.writeFile(r,s,"utf-8"),await d.promises.rename(r,i),this.cache.set(t,e);}catch(s){try{d.existsSync(r)&&await d.promises.unlink(r);}catch{}throw new f(`Failed to write collection "${t}"`,s)}finally{n();}}async exists(t){let e=this.getFilePath(t);return d.existsSync(e)}async delete(t){let e=this.getFilePath(t),i=await this.acquireLock(t);try{d.existsSync(e)&&await d.promises.unlink(e),this.cache.delete(t);}catch(r){throw new f(`Failed to delete collection "${t}"`,r)}finally{i();}}async list(){try{return (await d.promises.readdir(this.dataDir)).filter(e=>e.endsWith(this.fileExtension)).map(e=>e.slice(0,-this.fileExtension.length))}catch(t){throw new f("Failed to list collections",t)}}clearCache(t){t?this.cache.delete(t):this.cache.clear();}getDataDir(){return this.dataDir}};function D(a){return new ObjectId().toHexString()}function j(a){return typeof a!="string"||a.length===0||a.length>64?false:a.length===24?ObjectId.isValid(a):true}function l(a){if(a===null||typeof a!="object")return a;if(a instanceof Date)return new Date(a.getTime());if(a instanceof RegExp)return new RegExp(a.source,a.flags);if(Array.isArray(a))return a.map(e=>l(e));let t={};for(let e in a)Object.prototype.hasOwnProperty.call(a,e)&&(t[e]=l(a[e]));return t}function W(a,t){let e=t.split("."),i=a;for(let r of e){if(i==null||typeof i!="object")return;i=i[r];}return i}function q(a,t,e){let i=t.split("."),r=a;for(let n=0;n<i.length-1;n++){let s=i[n];(!(s in r)||typeof r[s]!="object"||r[s]===null)&&(r[s]={}),r=r[s];}r[i[i.length-1]]=e;}function _(a,t){let e=t.split("."),i=a;for(let n=0;n<e.length-1;n++){let s=e[n];if(!(s in i)||typeof i[s]!="object"||i[s]===null)return false;i=i[s];}let r=e[e.length-1];return r in i?(delete i[r],true):false}function F(a){return typeof a=="object"&&a!==null&&!Array.isArray(a)&&!(a instanceof Date)&&!(a instanceof RegExp)}var P=class{filter(t,e){return !e||Object.keys(e).length===0?t:t.filter(i=>this.matches(i,e))}matches(t,e){if("$and"in e&&e.$and)return e.$and.every(i=>this.matches(t,i));if("$or"in e&&e.$or)return e.$or.some(i=>this.matches(t,i));if("$not"in e&&e.$not)return !this.matches(t,e.$not);for(let[i,r]of Object.entries(e)){if(i.startsWith("$"))continue;let n=W(t,i);if(!this.matchesCondition(n,r))return false}return true}matchesCondition(t,e){if(!F(e)||!this.hasOperators(e))return this.isEqual(t,e);let i=e;if("$eq"in i&&!this.isEqual(t,i.$eq)||"$ne"in i&&this.isEqual(t,i.$ne)||"$gt"in i&&!this.compareValues(t,i.$gt,">")||"$gte"in i&&!this.compareValues(t,i.$gte,">=")||"$lt"in i&&!this.compareValues(t,i.$lt,"<")||"$lte"in i&&!this.compareValues(t,i.$lte,"<=")||"$in"in i&&Array.isArray(i.$in)&&!i.$in.some(r=>this.isEqual(t,r))||"$nin"in i&&Array.isArray(i.$nin)&&i.$nin.some(r=>this.isEqual(t,r)))return false;if("$exists"in i){let r=t!=null;if(i.$exists!==r)return false}return !("$regex"in i&&(typeof t!="string"||!(i.$regex instanceof RegExp?i.$regex:new RegExp(i.$regex)).test(t))||"$startsWith"in i&&(typeof t!="string"||typeof i.$startsWith!="string"||!t.startsWith(i.$startsWith))||"$endsWith"in i&&(typeof t!="string"||typeof i.$endsWith!="string"||!t.endsWith(i.$endsWith))||"$contains"in i&&(!Array.isArray(t)||!t.some(r=>this.isEqual(r,i.$contains))))}hasOperators(t){return Object.keys(t).some(e=>e.startsWith("$"))}isEqual(t,e){if(t===e)return true;if(t===null||e===null||t===void 0||e===void 0)return t===e;if(typeof t!=typeof e)return false;if(t instanceof Date&&e instanceof Date)return t.getTime()===e.getTime();if(Array.isArray(t)&&Array.isArray(e))return t.length!==e.length?false:t.every((i,r)=>this.isEqual(i,e[r]));if(typeof t=="object"&&typeof e=="object"){let i=Object.keys(t),r=Object.keys(e);return i.length!==r.length?false:i.every(n=>this.isEqual(t[n],e[n]))}return false}compareValues(t,e,i){if(typeof t=="number"&&typeof e=="number")switch(i){case ">":return t>e;case ">=":return t>=e;case "<":return t<e;case "<=":return t<=e}if(typeof t=="string"&&typeof e=="string")switch(i){case ">":return t>e;case ">=":return t>=e;case "<":return t<e;case "<=":return t<=e}if(t instanceof Date&&e instanceof Date)switch(i){case ">":return t.getTime()>e.getTime();case ">=":return t.getTime()>=e.getTime();case "<":return t.getTime()<e.getTime();case "<=":return t.getTime()<=e.getTime()}return false}};var C=class{name;storage;queryEngine;autoSave;saveDebounce;idGenerator;schema;saveTimeout=null;pendingSave=null;constructor(t,e,i={}){this.name=t,this.storage=e,this.queryEngine=new P,this.autoSave=i.autoSave??true,this.saveDebounce=i.saveDebounce??0,this.idGenerator=i.idGenerator??D,this.schema=i.schema;}validate(t){if(!this.schema)return t;let e=this.schema.safeParse(t);if(!e.success)throw new T(this.name,e.error.issues);return e.data}async getData(){let t=await this.storage.read(this.name);if(!t){let e={name:this.name,documents:[],createdAt:new Date().toISOString(),updatedAt:new Date().toISOString()};return await this.storage.write(this.name,e),e}return t}async save(t){if(this.autoSave){if(this.saveDebounce>0)return this.saveTimeout&&clearTimeout(this.saveTimeout),new Promise(e=>{this.saveTimeout=setTimeout(async()=>{await this.storage.write(this.name,t),this.saveTimeout=null,e();},this.saveDebounce);});await this.storage.write(this.name,t);}}async flush(){this.saveTimeout&&(clearTimeout(this.saveTimeout),this.saveTimeout=null),this.pendingSave&&await this.pendingSave;let t=await this.getData();await this.storage.write(this.name,t);}async insert(t){let e=await this.getData(),i=t._id||this.idGenerator();if(e.documents.some(s=>s._id===i))throw new w(this.name,i);let r={...l(t),_id:i},n=this.validate(r);return e.documents.push(n),await this.save(e),l(n)}async insertFast(t){let e=await this.getData(),i=t._id||this.idGenerator(),r={...l(t),_id:i},n=this.validate(r);return e.documents.push(n),await this.save(e),l(n)}async insertMany(t){if(t.length===0)return [];let e=await this.getData(),i=[],r=new Set(e.documents.map(n=>n._id));for(let n of t){let s=n._id||this.idGenerator();if(r.has(s))throw new w(this.name,s);r.add(s);let o={...l(n),_id:s},c=this.validate(o);e.documents.push(c),i.push(l(c));}return await this.save(e),i}async find(t,e){let i=await this.getData(),r=this.queryEngine.filter(i.documents,t);return e?.sort&&(r=this.sortDocuments(r,e.sort)),e?.skip&&e.skip>0&&(r=r.slice(e.skip)),e?.limit&&e.limit>0&&(r=r.slice(0,e.limit)),r.map(n=>l(n))}async findOne(t){let e=await this.find(t,{limit:1});return e.length>0?e[0]:null}async findById(t){return this.findOne({_id:t})}async count(t){let e=await this.getData();return this.queryEngine.filter(e.documents,t).length}async update(t,e){let i=await this.getData(),r=this.queryEngine.filter(i.documents,t);if(r.length===0)return 0;for(let n of r)this.applyUpdate(n,e);return await this.save(i),r.length}async updateOne(t,e){let i=await this.getData(),r=this.queryEngine.filter(i.documents,t);if(r.length===0)return null;let n=r[0];return this.applyUpdate(n,e),await this.save(i),l(n)}async updateById(t,e){return this.updateOne({_id:t},e)}async delete(t){let e=await this.getData(),i=e.documents.length;e.documents=e.documents.filter(n=>!this.queryEngine.matches(n,t));let r=i-e.documents.length;return r>0&&await this.save(e),r}async deleteOne(t){let e=await this.getData(),i=e.documents.findIndex(n=>this.queryEngine.matches(n,t));if(i===-1)return null;let[r]=e.documents.splice(i,1);return await this.save(e),l(r)}async deleteById(t){return this.deleteOne({_id:t})}async getAll(){return this.find()}async clear(){let t=await this.getData();t.documents=[],await this.save(t);}async drop(){await this.storage.delete(this.name);}getName(){return this.name}applyUpdate(t,e){if(!Object.keys(e).some(n=>n.startsWith("$"))){Object.assign(t,e);return}let r=e;if(r.$set)for(let[n,s]of Object.entries(r.$set))n!=="_id"&&q(t,n,s);if(r.$unset)for(let n of Object.keys(r.$unset))n!=="_id"&&_(t,n);if(r.$inc)for(let[n,s]of Object.entries(r.$inc)){let o=t[n];typeof o=="number"&&typeof s=="number"&&(t[n]=o+s);}if(r.$push)for(let[n,s]of Object.entries(r.$push)){let o=t[n];Array.isArray(o)&&o.push(s);}if(r.$pull)for(let[n,s]of Object.entries(r.$pull)){let o=t[n];if(Array.isArray(o)){let c=o.findIndex(u=>JSON.stringify(u)===JSON.stringify(s));c!==-1&&o.splice(c,1);}}if(r.$addToSet)for(let[n,s]of Object.entries(r.$addToSet)){let o=t[n];Array.isArray(o)&&(o.some(u=>JSON.stringify(u)===JSON.stringify(s))||o.push(s));}}sortDocuments(t,e){let i=Object.entries(e);return [...t].sort((r,n)=>{for(let[s,o]of i){let c=r[s],u=n[s],h=0;if(c===u?h=0:c==null?h=1:u==null?h=-1:typeof c=="number"&&typeof u=="number"?h=c-u:typeof c=="string"&&typeof u=="string"?h=c.localeCompare(u):c instanceof Date&&u instanceof Date?h=c.getTime()-u.getTime():h=String(c).localeCompare(String(u)),h!==0)return h*(o===-1||o==="desc"?-1:1)}return 0})}};var x=class{batchSize;flushInterval;coalesceWrites;batchProcessor;queue=new Map;flushTimer=null;pendingFlush=null;totalQueued=0;isShuttingDown=false;constructor(t,e={}){this.batchSize=e.batchSize??1e3,this.flushInterval=e.flushInterval??100,this.coalesceWrites=e.coalesceWrites??true,this.batchProcessor=t;}async enqueue(t){if(this.isShuttingDown)throw new Error("WriteQueue is shutting down, no new writes accepted");return new Promise((e,i)=>{let r={operation:t,resolve:e,reject:i,timestamp:Date.now()},n=t.collectionName,s=this.queue.get(n);s||(s=[],this.queue.set(n,s)),!(this.coalesceWrites&&this.tryCoalesce(n,r))&&(s.push(r),this.totalQueued++,this.scheduleFlush(),this.totalQueued>=this.batchSize&&this.flush().catch(i));})}tryCoalesce(t,e){let i=this.queue.get(t);if(!i||i.length===0)return false;let r=e.operation;if(r.type==="update")for(let n=i.length-1;n>=0;n--){let s=i[n].operation;if(s.type==="update"&&s.documentId===r.documentId){s.changes={...s.changes,...r.changes};let o=i[n].resolve;return i[n].resolve=()=>{o(),e.resolve();},true}}if(r.type==="delete")for(let n=i.length-1;n>=0;n--){let s=i[n].operation;(s.type==="insert"&&s.document._id===r.documentId||s.type==="update"&&s.documentId===r.documentId)&&(i.splice(n,1),this.totalQueued--);}if(r.type==="clear"||r.type==="fullWrite"){for(let n of i)n.resolve();i.length=0,this.totalQueued-=i.length;}return false}scheduleFlush(){this.flushTimer===null&&(this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flush().catch(t=>{console.error("WriteQueue flush error:",t);});},this.flushInterval));}async flush(){if(this.pendingFlush)return this.pendingFlush;if(this.flushTimer!==null&&(clearTimeout(this.flushTimer),this.flushTimer=null),this.totalQueued===0)return;let t=this.queue;this.queue=new Map,this.totalQueued=0;let e=new Map;for(let[i,r]of t)e.set(i,r.map(n=>n.operation));this.pendingFlush=this.processBatch(t,e),await this.pendingFlush,this.pendingFlush=null;}async processBatch(t,e){try{await this.batchProcessor(e);for(let i of t.values())for(let r of i)r.resolve();}catch(i){for(let r of t.values())for(let n of r)n.reject(i);}}async shutdown(){this.isShuttingDown=true,await this.flush();}pending(){return this.totalQueued}isEmpty(){return this.totalQueued===0}isClosing(){return this.isShuttingDown}};var S=class{dataDir;partitionCount;prettyPrint;fileExtension;cache=new Map;locks=new Map;constructor(t,e={}){this.dataDir=E.resolve(t),this.partitionCount=e.partitionCount??16,this.prettyPrint=e.prettyPrint??true,this.fileExtension=e.fileExtension??".json",this.ensureDirectory();}ensureDirectory(){try{d.existsSync(this.dataDir)||d.mkdirSync(this.dataDir,{recursive:!0});}catch(t){throw new f(`Failed to create data directory: ${this.dataDir}`,t)}}getPartitionIndex(t){let e=0;for(let i=0;i<t.length;i++){let r=t.charCodeAt(i);e=(e<<5)-e+r,e=e&e;}return Math.abs(e)%this.partitionCount}getPartitionFileName(t,e){return `${t}_p${e.toString().padStart(3,"0")}${this.fileExtension}`}getPartitionFilePath(t,e){return E.join(this.dataDir,this.getPartitionFileName(t,e))}getCacheKey(t,e){return `${t}:${e}`}async acquireLock(t){for(;this.locks.has(t);)await this.locks.get(t);let e=()=>{},i=new Promise(r=>{e=r;});return this.locks.set(t,i),()=>{this.locks.delete(t),e();}}async readPartition(t,e){let i=this.getCacheKey(t,e);if(this.cache.has(i))return this.cache.get(i);let r=this.getPartitionFilePath(t,e);try{if(!d.existsSync(r))return null;let n=await d.promises.readFile(r,"utf-8"),s=JSON.parse(n);return this.cache.set(i,s),s}catch(n){throw new f(`Failed to read partition ${e} for collection "${t}"`,n)}}async writePartition(t,e,i){let r=this.getPartitionFilePath(t,e),n=`${r}.tmp.${Date.now()}`,s=this.getCacheKey(t,e),o=await this.acquireLock(s);try{i.updatedAt=new Date().toISOString();let c=this.prettyPrint?JSON.stringify(i,null,2):JSON.stringify(i);await d.promises.writeFile(n,c,"utf-8"),await d.promises.rename(n,r),this.cache.set(s,i);}catch(c){try{d.existsSync(n)&&await d.promises.unlink(n);}catch{}throw new f(`Failed to write partition ${e} for collection "${t}"`,c)}finally{o();}}async readAllPartitions(t){let e=new Map,i=[];for(let r=0;r<this.partitionCount;r++)i.push(this.readPartition(t,r).then(n=>{n&&e.set(r,n);}));return await Promise.all(i),e}async writePartitions(t,e){let i=[];for(let[r,n]of e)i.push(this.writePartition(t,r,n));await Promise.all(i);}async initializePartitions(t){let e=[];for(let i=0;i<this.partitionCount;i++)if(!d.existsSync(this.getPartitionFilePath(t,i))){let n={name:t,documents:[],createdAt:new Date().toISOString(),updatedAt:new Date().toISOString()};e.push(this.writePartition(t,i,n));}await Promise.all(e);}async getPartitionInfo(t){let e=await this.readAllPartitions(t),i=[];for(let r=0;r<this.partitionCount;r++){let n=e.get(r);i.push({name:this.getPartitionFileName(t,r),partitionIndex:r,documentCount:n?.documents.length??0});}return i}async getAllDocuments(t){let e=await this.readAllPartitions(t),i=[];for(let r of e.values())i.push(...r.documents);return i}async findById(t,e){let i=this.getPartitionIndex(e),r=await this.readPartition(t,i);return r?r.documents.find(n=>n._id===e)??null:null}async deleteCollection(t){let e=[];for(let i=0;i<this.partitionCount;i++){let r=this.getPartitionFilePath(t,i),n=this.getCacheKey(t,i);e.push((async()=>{let s=await this.acquireLock(n);try{d.existsSync(r)&&await d.promises.unlink(r),this.cache.delete(n);}finally{s();}})());}await Promise.all(e);}clearCache(t){if(t)for(let e=0;e<this.partitionCount;e++)this.cache.delete(this.getCacheKey(t,e));else this.cache.clear();}getPartitionCount(){return this.partitionCount}async listCollections(){try{let t=await d.promises.readdir(this.dataDir),e=new Set,i=new RegExp(`^(.+)_p\\d{3}\\${this.fileExtension}$`);for(let r of t){let n=r.match(i);n&&e.add(n[1]);}return Array.from(e)}catch(t){throw new f("Failed to list partitioned collections",t)}}};var k=class{maxConcurrent;maxQueueSize;queue=[];activeCount=0;completedCount=0;failedCount=0;isShuttingDown=false;constructor(t={}){this.maxConcurrent=t.maxConcurrent??4,this.maxQueueSize=t.maxQueueSize??1e4;}async submit(t,e=0){if(this.isShuttingDown)throw new Error("WorkerPool is shutting down, no new tasks accepted");if(this.queue.length>=this.maxQueueSize)throw new Error("WorkerPool queue is full, task rejected (backpressure)");return new Promise((i,r)=>{let n={task:t,resolve:i,reject:r,priority:e},s=false;for(let o=0;o<this.queue.length;o++)if(e>this.queue[o].priority){this.queue.splice(o,0,n),s=true;break}s||this.queue.push(n),this.processNext();})}async submitAll(t,e=0){let i=t.map(r=>this.submit(r,e));return Promise.all(i)}async*submitStream(t,e=0){let i=t.map(r=>this.submit(r,e));for(let r of i)yield await r;}processNext(){if(this.activeCount>=this.maxConcurrent||this.queue.length===0)return;let t=this.queue.shift();t&&(this.activeCount++,t.task().then(e=>{this.completedCount++,t.resolve(e);}).catch(e=>{this.failedCount++,t.reject(e);}).finally(()=>{this.activeCount--,this.processNext();}));}async drain(){return new Promise(t=>{let e=()=>{this.activeCount===0&&this.queue.length===0?t():setImmediate(e);};e();})}async shutdown(){this.isShuttingDown=true,await this.drain();}getStats(){return {activeWorkers:this.activeCount,queuedTasks:this.queue.length,completedTasks:this.completedCount,failedTasks:this.failedCount}}isIdle(){return this.activeCount===0&&this.queue.length===0}isClosing(){return this.isShuttingDown}queueSize(){return this.queue.length}activeWorkers(){return this.activeCount}};async function Q(a,t,e){let i=[],r=0;async function n(){let o=r++;o>=a.length||(i[o]=await e(a[o],o),await n());}let s=Array(Math.min(t,a.length)).fill(null).map(()=>n());return await Promise.all(s),i}var V={partitions:16,batchSize:1e3,flushInterval:100,maxConcurrentIO:4,coalesceWrites:true},O=class{partitionManager;writeQueue;workerPool;options;constructor(t){let e=t.highConcurrency;this.options={...V,...e},this.partitionManager=new S(t.dataDir,{partitionCount:this.options.partitions,prettyPrint:t.prettyPrint??true,fileExtension:t.fileExtension??".json"}),this.workerPool=new k({maxConcurrent:this.options.maxConcurrentIO});let i=this.processBatch.bind(this);this.writeQueue=new x(i,{batchSize:this.options.batchSize,flushInterval:this.options.flushInterval,coalesceWrites:this.options.coalesceWrites});}async processBatch(t){let e=Array.from(t.entries());await Q(e,this.options.maxConcurrentIO,async([i,r])=>{await this.processCollectionOperations(i,r);});}async processCollectionOperations(t,e){let i=new Map,r=n=>{let s=i.get(n);return s||(s=[],i.set(n,s)),s};for(let n of e)if(n.type==="fullWrite"||n.type==="clear")for(let s=0;s<this.partitionManager.getPartitionCount();s++)r(s).push(n);else if(n.type==="insert"){let s=this.partitionManager.getPartitionIndex(n.document._id);r(s).push(n);}else if(n.type==="update"||n.type==="delete"){let s=this.partitionManager.getPartitionIndex(n.documentId);r(s).push(n);}else if(n.type==="bulkInsert")for(let s of n.documents){let o=this.partitionManager.getPartitionIndex(s._id);r(o).push({type:"insert",collectionName:t,document:s});}else if(n.type==="bulkUpdate"||n.type==="bulkDelete")for(let s of n.documentIds){let o=this.partitionManager.getPartitionIndex(s);n.type==="bulkUpdate"?r(o).push({type:"update",collectionName:t,documentId:s,changes:n.changes}):r(o).push({type:"delete",collectionName:t,documentId:s});}await Q(Array.from(i.entries()),this.options.maxConcurrentIO,async([n,s])=>{await this.processPartitionOperations(t,n,s);});}async processPartitionOperations(t,e,i){let r=await this.partitionManager.readPartition(t,e);r||(r={name:t,documents:[],createdAt:new Date().toISOString(),updatedAt:new Date().toISOString()});for(let n of i)switch(n.type){case "insert":r.documents.push(n.document);break;case "update":{let s=r.documents.findIndex(o=>o._id===n.documentId);s!==-1&&Object.assign(r.documents[s],n.changes);break}case "delete":{let s=r.documents.findIndex(o=>o._id===n.documentId);s!==-1&&r.documents.splice(s,1);break}case "clear":r.documents=[];break;case "fullWrite":{let s=n.data.documents.filter(o=>this.partitionManager.getPartitionIndex(o._id)===e);r.documents=s;break}}await this.partitionManager.writePartition(t,e,r);}async insert(t,e){await this.writeQueue.enqueue({type:"insert",collectionName:t,document:e});}async insertMany(t,e){await this.writeQueue.enqueue({type:"bulkInsert",collectionName:t,documents:e});}async update(t,e,i){await this.writeQueue.enqueue({type:"update",collectionName:t,documentId:e,changes:i});}async delete(t,e){await this.writeQueue.enqueue({type:"delete",collectionName:t,documentId:e});}async clear(t){await this.writeQueue.enqueue({type:"clear",collectionName:t});}async findById(t,e){return await this.writeQueue.flush(),this.partitionManager.findById(t,e)}async readAll(t){return await this.writeQueue.flush(),this.partitionManager.getAllDocuments(t)}async exists(t){return (await this.partitionManager.listCollections()).includes(t)}async initializeCollection(t){await this.partitionManager.initializePartitions(t);}async deleteCollection(t){await this.writeQueue.flush(),await this.partitionManager.deleteCollection(t);}async listCollections(){return this.partitionManager.listCollections()}async flush(){await this.writeQueue.flush();}async shutdown(){await this.writeQueue.shutdown(),await this.workerPool.shutdown();}pendingWrites(){return this.writeQueue.pending()}getStats(){return {pendingWrites:this.writeQueue.pending(),workerPool:this.workerPool.getStats(),partitions:this.options.partitions}}clearCache(t){this.partitionManager.clearCache(t);}};var b=class{name;storage;queryEngine;idGenerator;schema;constructor(t,e,i={}){this.name=t,this.storage=e,this.queryEngine=new P,this.idGenerator=i.idGenerator??D,this.schema=i.schema;}validate(t){if(!this.schema)return t;let e=this.schema.safeParse(t);if(!e.success)throw new T(this.name,e.error.issues);return e.data}async insert(t){let e=t._id||this.idGenerator();if(await this.storage.findById(this.name,e))throw new w(this.name,e);let r={...l(t),_id:e},n=this.validate(r);return await this.storage.insert(this.name,n),n}async insertFast(t){let e=t._id||this.idGenerator(),i={...l(t),_id:e},r=this.validate(i);return await this.storage.insert(this.name,r),r}async insertMany(t){if(t.length===0)return [];let e=[],i=[];for(let r of t){let n=r._id||this.idGenerator(),s={...l(r),_id:n},o=this.validate(s);i.push(o),e.push(l(o));}return await this.storage.insertMany(this.name,i),e}async find(t,e){let i=await this.storage.readAll(this.name),r=this.queryEngine.filter(i,t);return e?.sort&&(r=this.sortDocuments(r,e.sort)),e?.skip&&e.skip>0&&(r=r.slice(e.skip)),e?.limit&&e.limit>0&&(r=r.slice(0,e.limit)),r.map(n=>l(n))}async findOne(t){let e=await this.find(t,{limit:1});return e.length>0?e[0]:null}async findById(t){let e=await this.storage.findById(this.name,t);return e?l(e):null}async count(t){let e=await this.storage.readAll(this.name);return this.queryEngine.filter(e,t).length}async update(t,e){let i=await this.storage.readAll(this.name),r=this.queryEngine.filter(i,t);if(r.length===0)return 0;for(let n of r){let s=this.getUpdateChanges(n,e);await this.storage.update(this.name,n._id,s);}return r.length}async updateOne(t,e){let i=await this.storage.readAll(this.name),r=this.queryEngine.filter(i,t);if(r.length===0)return null;let n=r[0],s=this.getUpdateChanges(n,e);return await this.storage.update(this.name,n._id,s),Object.assign(n,s),l(n)}async updateById(t,e){return this.updateOne({_id:t},e)}async delete(t){let e=await this.storage.readAll(this.name),i=this.queryEngine.filter(e,t);if(i.length===0)return 0;for(let r of i)await this.storage.delete(this.name,r._id);return i.length}async deleteOne(t){let e=await this.storage.readAll(this.name),i=this.queryEngine.filter(e,t);if(i.length===0)return null;let r=i[0];return await this.storage.delete(this.name,r._id),l(r)}async deleteById(t){return this.deleteOne({_id:t})}async getAll(){return this.find()}async clear(){await this.storage.clear(this.name);}async drop(){await this.storage.deleteCollection(this.name);}async flush(){await this.storage.flush();}getName(){return this.name}getUpdateChanges(t,e){if(!Object.keys(e).some(s=>s.startsWith("$")))return e;let r=e,n={};if(r.$set)for(let[s,o]of Object.entries(r.$set))s!=="_id"&&(n[s]=o);if(r.$inc)for(let[s,o]of Object.entries(r.$inc)){let c=t[s];typeof c=="number"&&typeof o=="number"&&(n[s]=c+o);}if(r.$push)for(let[s,o]of Object.entries(r.$push)){let c=t[s];Array.isArray(c)&&(n[s]=[...c,o]);}if(r.$pull)for(let[s,o]of Object.entries(r.$pull)){let c=t[s];Array.isArray(c)&&(n[s]=c.filter(u=>JSON.stringify(u)!==JSON.stringify(o)));}if(r.$addToSet)for(let[s,o]of Object.entries(r.$addToSet)){let c=t[s];Array.isArray(c)&&(c.some(h=>JSON.stringify(h)===JSON.stringify(o))?n[s]=c:n[s]=[...c,o]);}return n}sortDocuments(t,e){let i=Object.entries(e);return [...t].sort((r,n)=>{for(let[s,o]of i){let c=r[s],u=n[s],h=0;if(c===u?h=0:c==null?h=1:u==null?h=-1:typeof c=="number"&&typeof u=="number"?h=c-u:typeof c=="string"&&typeof u=="string"?h=c.localeCompare(u):c instanceof Date&&u instanceof Date?h=c.getTime()-u.getTime():h=String(c).localeCompare(String(u)),h!==0)return h*(o===-1||o==="desc"?-1:1)}return 0})}};var B={autoSave:true,saveDebounce:0,prettyPrint:true,fileExtension:".json"},M=class{options;storage;hcStorage;collections=new Map;isHighConcurrency;connected=false;constructor(t){this.options={...B,...t},this.isHighConcurrency=t.highConcurrency?.enabled??false,this.isHighConcurrency?(this.storage=null,this.hcStorage=new O(this.options)):(this.storage=new $(this.options),this.hcStorage=null);}getStorage(){if(this.storage===null)throw new Error("Storage is not available in high-concurrency mode");return this.storage}getHCStorage(){if(this.hcStorage===null)throw new Error("HighConcurrencyStorage is not available in standard mode");return this.hcStorage}async connect(){if(!this.connected){if(this.isHighConcurrency){let t=await this.getHCStorage().listCollections();for(let e of t)this.getOrCreateCollection(e);}else {let t=await this.getStorage().list();for(let e of t)this.getOrCreateCollection(e);}this.connected=true;}}async close(){if(this.connected){for(let t of this.collections.values())await t.flush();this.isHighConcurrency&&this.hcStorage&&await this.hcStorage.shutdown(),this.collections.clear(),this.storage&&this.storage.clearCache(),this.hcStorage&&this.hcStorage.clearCache(),this.connected=false;}}collection(t,e){if(!t||typeof t!="string")throw new v("Collection name must be a non-empty string");if(!/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(t))throw new v("Collection name must start with a letter or underscore and contain only letters, numbers, underscores, and hyphens");return this.getOrCreateCollection(t,e)}getOrCreateCollection(t,e){if(this.collections.has(t))return this.collections.get(t);if(this.isHighConcurrency){let i=new b(t,this.getHCStorage(),{schema:e?.schema});this.collections.set(t,i);}else {let i=new C(t,this.getStorage(),{autoSave:this.options.autoSave,saveDebounce:this.options.saveDebounce,schema:e?.schema});this.collections.set(t,i);}return this.collections.get(t)}async hasCollection(t){return this.isHighConcurrency?this.getHCStorage().exists(t):this.getStorage().exists(t)}async listCollections(){return this.isHighConcurrency?this.getHCStorage().listCollections():this.getStorage().list()}async dropCollection(t){let e=this.collections.get(t);e?(await e.drop(),this.collections.delete(t)):this.isHighConcurrency?await this.getHCStorage().deleteCollection(t):await this.getStorage().delete(t);}async drop(){if(this.isHighConcurrency){let t=this.getHCStorage(),e=await t.listCollections();for(let i of e)await t.deleteCollection(i);}else {let t=this.getStorage(),e=await t.list();for(let i of e)await t.delete(i);}this.collections.clear(),this.storage?.clearCache(),this.hcStorage?.clearCache();}getDataDir(){return this.isHighConcurrency?this.options.dataDir:this.getStorage().getDataDir()}isConnected(){return this.connected}isHighConcurrencyMode(){return this.isHighConcurrency}getStats(){return !this.isHighConcurrency||!this.hcStorage?null:this.hcStorage.getStats()}async flush(){for(let t of this.collections.values())await t.flush();}};export{C as Collection,v as CollectionError,A as DocumentNotFoundError,w as DuplicateKeyError,b as HighConcurrencyCollection,O as HighConcurrencyStorage,M as JsonDB,y as JsonDBError,S as PartitionManager,f as StorageError,T as ValidationError,k as WorkerPool,x as WriteQueue,D as generateId,j as isValidId,Q as parallelLimit};
package/package.json ADDED
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "nodejs-json-db",
3
+ "version": "0.0.1",
4
+ "description": "A production-ready, lightweight JSON-based database for Node.js and Electron applications",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "module": "./dist/index.mjs",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "require": "./dist/index.js",
12
+ "import": "./dist/index.mjs",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "sideEffects": false,
17
+ "scripts": {
18
+ "build": "tsup",
19
+ "test": "vitest run",
20
+ "test:watch": "vitest",
21
+ "test:coverage": "vitest run --coverage",
22
+ "dev": "tsup --watch",
23
+ "lint": "eslint src --ext .ts",
24
+ "format": "prettier --write \"src/**/*.ts\"",
25
+ "typecheck": "tsc --noEmit",
26
+ "prepublishOnly": "npm run build && npm run test",
27
+ "benchmark": "tsx examples/benchmark.ts"
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "README.md",
32
+ "LICENSE"
33
+ ],
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/iqbal-rashed/nodejs-json-db.git"
37
+ },
38
+ "keywords": [
39
+ "json",
40
+ "database",
41
+ "db",
42
+ "nosql",
43
+ "document",
44
+ "storage",
45
+ "electron",
46
+ "nodejs",
47
+ "typescript",
48
+ "esm",
49
+ "cjs",
50
+ "lightweight",
51
+ "file-based",
52
+ "local-storage"
53
+ ],
54
+ "author": "Rashed Iqbal",
55
+ "license": "MIT",
56
+ "bugs": {
57
+ "url": "https://github.com/iqbal-rashed/nodejs-json-db/issues"
58
+ },
59
+ "homepage": "https://github.com/iqbal-rashed/nodejs-json-db#readme",
60
+ "engines": {
61
+ "node": ">=18.0.0"
62
+ },
63
+ "devDependencies": {
64
+ "@eslint/js": "^9.18.0",
65
+ "@types/node": "^22.10.5",
66
+ "@vitest/coverage-v8": "^4.0.16",
67
+ "eslint": "^9.18.0",
68
+ "globals": "^15.14.0",
69
+ "prettier": "^3.4.2",
70
+ "tsup": "^8.5.1",
71
+ "tsx": "^4.21.0",
72
+ "typescript": "^5.9.3",
73
+ "typescript-eslint": "^8.19.1",
74
+ "vitest": "^4.0.16",
75
+ "zod": "^4.2.1"
76
+ },
77
+ "dependencies": {
78
+ "bson": "^7.0.0"
79
+ },
80
+ "peerDependencies": {
81
+ "zod": "^3.0.0 || ^4.0.0"
82
+ },
83
+ "peerDependenciesMeta": {
84
+ "zod": {
85
+ "optional": true
86
+ }
87
+ }
88
+ }