@joystick.js/db-canary 0.0.0-canary.2293 → 0.0.0-canary.2294
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
import{get_database as b}from"../query_engine.js";import{get_settings as q}from"../load_settings.js";import{get_write_queue as z}from"../write_queue.js";import{get_auth_stats as M}from"../auth_manager.js";import{get_query_statistics as P,get_auto_index_statistics as N,force_index_evaluation as O,remove_automatic_indexes as A}from"../auto_index_manager.js";import{create_index as R,drop_index as U,get_indexes as j}from"../index_manager.js";import{test_s3_connection as T,create_backup as W,list_backups as J,restore_backup as B,cleanup_old_backups as I}from"../backup_manager.js";import{get_simple_sync_manager as x}from"../simple_sync_manager.js";import{get_sync_receiver as v}from"../sync_receiver.js";import L from"../logger.js";import{performance_monitor as w}from"../performance_monitor.js";const{create_context_logger:y}=L("admin"),K=()=>{try{return q()}catch{return{port:1983}}},Y=t=>{try{const e=t.getStats?t.getStats():{};return{pageSize:e.pageSize||0,treeDepth:e.treeDepth||0,treeBranchPages:e.treeBranchPages||0,treeLeafPages:e.treeLeafPages||0,entryCount:e.entryCount||0,mapSize:e.mapSize||0,lastPageNumber:e.lastPageNumber||0}}catch{return{error:"Could not retrieve database stats"}}},G=(t,e)=>{const o={};let r=0;try{for(const{key:n}of t.getRange())if(typeof n=="string"&&n.includes(":")&&!n.startsWith("_")){const a=n.split(":")[0];o[a]=(o[a]||0)+1,r++}}catch(n){e.warn("Could not iterate database range for stats",{error:n.message})}return{collections:o,total_documents:r}},S=()=>{const t=process.memoryUsage();return{rss:Math.round(t.rss/1024/1024),heapTotal:Math.round(t.heapTotal/1024/1024),heapUsed:Math.round(t.heapUsed/1024/1024),external:Math.round(t.external/1024/1024)}},H=t=>t.mapSize>0?Math.round(t.lastPageNumber*t.pageSize/t.mapSize*100):0,Q=t=>({uptime:Math.floor(process.uptime()),uptime_formatted:E(process.uptime()),memory_usage:t,memory_usage_raw:process.memoryUsage(),node_version:process.version,platform:process.platform,arch:process.arch,pid:process.pid,cpu_usage:process.cpuUsage()}),V=(t,e,o,r)=>({total_documents:t,total_collections:Object.keys(e).length,collections:e,stats:o,map_size_usage_percent:r,disk_usage:{map_size_mb:Math.round((o.mapSize||0)/1024/1024),used_space_mb:Math.round((o.lastPageNumber||0)*(o.pageSize||0)/1024/1024)}}),X=()=>{const t=y();try{const e=b(),o=K(),r=Y(e),{collections:n,total_documents:a}=G(e,t),s=S(),i=H(r);return{server:Q(s),database:V(a,n,r,i),performance:{ops_per_second:C(),avg_response_time_ms:D()}}}catch(e){throw t.error("Failed to get enhanced stats",{error:e.message}),e}},E=t=>{const e=Math.floor(t/86400),o=Math.floor(t%86400/3600),r=Math.floor(t%3600/60),n=Math.floor(t%60);return e>0?`${e}d ${o}h ${r}m ${n}s`:o>0?`${o}h ${r}m ${n}s`:r>0?`${r}m ${n}s`:`${n}s`};let $=0,F=0,Z=Date.now();const C=()=>{const t=(Date.now()-Z)/1e3;return t>0?Math.round($/t):0},D=()=>$>0?Math.round(F/$):0,ee=t=>{$++,F+=t},te=t=>({name:t,document_count:0,indexes:[],estimated_size_bytes:0}),re=(t,e,o)=>{const r={};let n=0;try{for(const{key:a}of t.getRange())if(typeof a=="string"&&a.includes(":")&&!a.startsWith("_")){const s=a.split(":");if(s.length>=3){const i=s[0],c=s[1];i===e&&(r[c]||(r[c]=te(c)),r[c].document_count++,n++)}}}catch(a){o.warn("Could not iterate database range for collections",{error:a.message})}return{collections_map:r,total_documents:n}},se=(t,e,o)=>{const r=["admin_test","test_collection","queue_test","users","products","orders","sessions","logs","analytics","settings","another_collection","list_test","pagination_test","get_test","query_test","admin_insert_test","admin_update_test","admin_delete_test"];let n=0;for(const a of r)try{const s=`${e}:${a}:`,i=t.getRange({start:s,end:s+"\xFF"});let c=0;for(const _ of i)c++,n++;c>0&&(o[a]={name:a,document_count:c,indexes:[],estimated_size_bytes:c*100})}catch{continue}return n},oe=(t,e,o,r)=>{try{const n=`index:${e}:`,a=t.getRange({start:n,end:n+"\xFF"});for(const{key:s,value:i}of a)if(typeof s=="string"&&s.startsWith(n)){const c=s.substring(n.length),_=c.split(":")[0],u=c.split(":")[1];o[_]&&u&&(o[_].indexes.includes(u)||o[_].indexes.push(u))}}catch(n){r.warn("Could not iterate index range",{error:n.message})}},ne=(t="default")=>{const e=y();try{const o=b();let{collections_map:r,total_documents:n}=re(o,t,e);Object.keys(r).length===0&&(n+=se(o,t,r)),oe(o,t,r,e);const a=Object.values(r);return{collections:a,total_collections:a.length,total_documents:n}}catch(o){throw e.error("Failed to list collections",{error:o.message}),o}},ae=(t,e={})=>{const o=y();if(!t)throw new Error("Collection name is required");try{const r=b(),{limit:n=50,skip:a=0,sort_field:s,sort_order:i="asc",database:c="default"}=e,_=[],u=`${c}:${t}:`;let m=0,p=0;for(const{key:d,value:g}of r.getRange({start:u,end:u+"\xFF"}))if(typeof d=="string"&&d.startsWith(u)){if(p<a){p++;continue}if(m>=n)break;try{const l=JSON.parse(g),f=d.substring(u.length);_.push({_id:f,...l}),m++}catch(l){o.warn("Could not parse document",{collection:t,key:d,error:l.message})}}return s&&_.length>0&&_.sort((d,g)=>{const l=d[s],f=g[s];return i==="desc"?f>l?1:f<l?-1:0:l>f?1:l<f?-1:0}),{collection:t,documents:_,count:_.length,skip:a,limit:n,has_more:m===n}}catch(r){throw o.error("Failed to list documents",{collection:t,error:r.message}),r}},ce=(t,e,o="default")=>{const r=y();if(!t||!e)throw new Error("Collection name and document ID are required");try{const n=b(),a=`${o}:${t}:${e}`,s=n.get(a);if(!s)return{found:!1,collection:t,document_id:e};const i=JSON.parse(s);return{found:!0,collection:t,document_id:e,document:{_id:e,...i}}}catch(n){throw r.error("Failed to get document",{collection:t,document_id:e,error:n.message}),n}},ie=(t,e,o,r)=>{switch(t){case"$gt":return o>e;case"$gte":return o>=e;case"$lt":return o<e;case"$lte":return o<=e;case"$ne":return o!==e;case"$in":return Array.isArray(e)&&e.includes(o);case"$regex":const n=r.$options||"";return new RegExp(e,n).test(String(o));default:return o===r}},_e=(t,e)=>Object.keys(e).every(o=>{const r=e[o],n=t[o];return typeof r=="object"&&r!==null?Object.keys(r).every(a=>{const s=r[a];return ie(a,s,n,r)}):n===r}),ue=(t,e,o,r,n)=>{try{const a=JSON.parse(e),i={_id:t.substring(o.length),...a};return _e(i,r)?i:null}catch(a){return n.warn("Could not parse document during query",{key:t,error:a.message}),null}},le=(t,e={},o={})=>{const r=y();if(!t)throw new Error("Collection name is required");try{const n=b(),{limit:a=100,skip:s=0,database:i="default"}=o,c=[],_=`${i}:${t}:`;let u=0,m=0,p=0;for(const{key:d,value:g}of n.getRange({start:_,end:_+"\xFF"}))if(typeof d=="string"&&d.startsWith(_)){p++;const l=ue(d,g,_,e,r);if(l){if(m<s){m++;continue}if(u>=a)break;c.push(l),u++}}return{collection:t,filter:e,documents:c,count:c.length,total_examined:p,skip:s,limit:a,has_more:u===a}}catch(n){throw r.error("Failed to query documents",{collection:t,filter:e,error:n.message}),n}},de=async(t,e,o,r={})=>await(await import("./insert_one.js")).default(t,e,o,r),me=async(t,e,o,r,n={})=>await(await import("./update_one.js")).default(t,e,o,r,n),pe=async(t,e,o,r={})=>await(await import("./delete_one.js")).default(t,e,o,r);var ze=async(t,e={},o,r)=>{const n=y(),a=Date.now();try{let s;switch(t){case"stats":const c=S();s={server:{uptime:Math.floor(process.uptime()),uptime_formatted:E(process.uptime()),node_version:process.version,platform:process.platform,arch:process.arch,pid:process.pid},memory:{heap_used_mb:c.heapUsed,heap_total_mb:c.heapTotal,rss_mb:c.rss,external_mb:c.external,heap_used_percent:c.heapTotal>0?Math.round(c.heapUsed/c.heapTotal*100):0},database:{...w.get_database_stats(),map_size_mb:Math.round((w.get_database_stats()?.map_size||0)/1024/1024),used_space_mb:Math.round((w.get_database_stats()?.used_space||0)/1024/1024),usage_percent:w.get_database_stats()?.usage_percent||0},performance:{ops_per_second:C(),avg_response_time_ms:D()},system:w.get_system_stats(),connections:o?.get_stats()||{active:r?.size||0,total:r?.size||0},write_queue:z()?.get_stats()||{},authentication:{authenticated_clients:r?.size||0,...M()},settings:(()=>{try{return{port:q().port||1983}}catch{return{port:1983}}})()};break;case"list_collections":s=ne();break;case"list_documents":s=ae(e.collection,{limit:e.limit,skip:e.skip,sort_field:e.sort_field,sort_order:e.sort_order});break;case"get_document":s=ce(e.collection,e.document_id);break;case"query_documents":s=le(e.collection,e.filter,{limit:e.limit,skip:e.skip});break;case"insert_document":s=await de(e.database||"default",e.collection,e.document,e.options);break;case"update_document":const _=e.document_id?{_id:e.document_id}:e.filter;s=await me(e.database||"default",e.collection,_,e.update,e.options);break;case"delete_document":const u=e.document_id?{_id:e.document_id}:e.filter;s=await pe(e.database||"default",e.collection,u,e.options);break;case"test_s3_connection":s=await T();break;case"backup_now":s=await W();break;case"list_backups":s=await J();break;case"restore_backup":if(!e.backup_filename)throw new Error("backup_filename is required for restore operation");s=await B(e.backup_filename);break;case"cleanup_backups":s=await I();break;case"get_auto_index_stats":s=N();break;case"get_query_stats":s=P(e.collection);break;case"evaluate_auto_indexes":s=await O(e.collection);break;case"remove_auto_indexes":if(!e.collection)throw new Error("collection is required for remove_auto_indexes operation");s=await A(e.collection,e.field_names);break;case"create_index":if(!e.collection||!e.field)throw new Error("collection and field are required for create_index operation");s=await R(e.database||"default",e.collection,e.field,e.options);break;case"drop_index":if(!e.collection||!e.field)throw new Error("collection and field are required for drop_index operation");s=await U(e.database||"default",e.collection,e.field);break;case"get_indexes":if(!e.collection)throw new Error("collection is required for get_indexes operation");s={indexes:j(e.database||"default",e.collection)};break;case"get_sync_status":const m=x(),p=v();s={sync_manager:m.get_sync_status(),sync_receiver:p.get_sync_status()};break;case"update_secondary_nodes":if(!Array.isArray(e.secondary_nodes))throw new Error("secondary_nodes array is required for update_secondary_nodes operation");x().update_secondary_nodes(e.secondary_nodes),s={success:!0,message:"Secondary nodes updated successfully",secondary_nodes:e.secondary_nodes};break;case"force_sync":s=await x().force_sync();break;case"set_primary_role":if(typeof e.primary!="boolean")throw new Error("primary boolean value is required for set_primary_role operation");e.primary?(v().promote_to_primary(),s={success:!0,message:"Node promoted to primary successfully",role:"primary"}):s={success:!1,message:"Demoting primary to secondary requires server restart with updated configuration",role:"primary"};break;case"reload_sync_key":const l=v();if(!l.is_secondary)throw new Error("reload_sync_key can only be used on secondary nodes");await l.reload_api_key(),s={success:!0,message:"API_KEY reloaded successfully"};break;case"get_secondary_auth_status":const h=x().get_sync_status();s={secondary_count:h.secondary_count,auth_failures:h.stats.auth_failures,successful_syncs:h.stats.successful_syncs,failed_syncs:h.stats.failed_syncs,secondaries:h.secondaries};break;default:s={...X(),connections:o?.get_stats()||{},write_queue:z()?.get_stats()||{},authentication:{authenticated_clients:r?.size||0,...M()},settings:(()=>{try{return{port:q().port||1983}}catch{return{port:1983}}})()}}const i=Date.now()-a;return ee(i),n.info("Admin operation completed",{admin_action:t||"default",duration_ms:i,status:"success"}),s}catch(s){const i=Date.now()-a;throw n.error("Admin operation failed",{admin_action:t||"default",duration_ms:i,status:"error",error:s.message}),s}};export{ze as default,ee as track_operation};
|
|
1
|
+
import{get_database as h}from"../query_engine.js";import{get_settings as q}from"../load_settings.js";import{get_write_queue as z}from"../write_queue.js";import{get_auth_stats as M}from"../auth_manager.js";import{get_query_statistics as P,get_auto_index_statistics as N,force_index_evaluation as R,remove_automatic_indexes as j}from"../auto_index_manager.js";import{create_index as A,drop_index as U,get_indexes as W}from"../index_manager.js";import{test_s3_connection as T,create_backup as J,list_backups as B,restore_backup as I,cleanup_old_backups as L}from"../backup_manager.js";import{get_simple_sync_manager as x}from"../simple_sync_manager.js";import{get_sync_receiver as v}from"../sync_receiver.js";import K from"../logger.js";import{performance_monitor as w}from"../performance_monitor.js";const{create_context_logger:g}=K("admin"),Y=()=>{try{return q()}catch{return{port:1983}}},G=t=>{try{const e=t.getStats?t.getStats():{};return{pageSize:e.pageSize||0,treeDepth:e.treeDepth||0,treeBranchPages:e.treeBranchPages||0,treeLeafPages:e.treeLeafPages||0,entryCount:e.entryCount||0,mapSize:e.mapSize||0,lastPageNumber:e.lastPageNumber||0}}catch{return{error:"Could not retrieve database stats"}}},H=(t,e)=>{const r={};let n=0;try{for(const{key:s}of t.getRange())if(typeof s=="string"&&s.includes(":")&&!s.startsWith("_")){const a=s.split(":")[0];r[a]=(r[a]||0)+1,n++}}catch(s){e.warn("Could not iterate database range for stats",{error:s.message})}return{collections:r,total_documents:n}},S=()=>{const t=process.memoryUsage();return{rss:Math.round(t.rss/1024/1024),heapTotal:Math.round(t.heapTotal/1024/1024),heapUsed:Math.round(t.heapUsed/1024/1024),external:Math.round(t.external/1024/1024)}},Q=t=>t.mapSize>0?Math.round(t.lastPageNumber*t.pageSize/t.mapSize*100):0,V=t=>({uptime:Math.floor(process.uptime()),uptime_formatted:F(process.uptime()),memory_usage:t,memory_usage_raw:process.memoryUsage(),node_version:process.version,platform:process.platform,arch:process.arch,pid:process.pid,cpu_usage:process.cpuUsage()}),X=(t,e,r,n)=>({total_documents:t,total_collections:Object.keys(e).length,collections:e,stats:r,map_size_usage_percent:n,disk_usage:{map_size_mb:Math.round((r.mapSize||0)/1024/1024),used_space_mb:Math.round((r.lastPageNumber||0)*(r.pageSize||0)/1024/1024)}}),Z=()=>{const t=g();try{const e=h(),r=Y(),n=G(e),{collections:s,total_documents:a}=H(e,t),o=S(),i=Q(n);return{server:V(o),database:X(a,s,n,i),performance:{ops_per_second:C(),avg_response_time_ms:D()}}}catch(e){throw t.error("Failed to get enhanced stats",{error:e.message}),e}},F=t=>{const e=Math.floor(t/86400),r=Math.floor(t%86400/3600),n=Math.floor(t%3600/60),s=Math.floor(t%60);return e>0?`${e}d ${r}h ${n}m ${s}s`:r>0?`${r}h ${n}m ${s}s`:n>0?`${n}m ${s}s`:`${s}s`};let $=0,E=0,ee=Date.now();const C=()=>{const t=(Date.now()-ee)/1e3;return t>0?Math.round($/t):0},D=()=>$>0?Math.round(E/$):0,te=t=>{$++,E+=t},se=t=>({name:t,document_count:0,indexes:[],estimated_size_bytes:0}),re=(t,e,r)=>{const n={};let s=0;try{for(const{key:a}of t.getRange())if(typeof a=="string"&&a.includes(":")&&!a.startsWith("_")){const o=a.split(":");if(o.length>=3){const i=o[0],c=o[1];i===e&&(n[c]||(n[c]=se(c)),n[c].document_count++,s++)}}}catch(a){r.warn("Could not iterate database range for collections",{error:a.message})}return{collections_map:n,total_documents:s}},oe=(t,e,r)=>{const n=["admin_test","test_collection","queue_test","users","products","orders","sessions","logs","analytics","settings","another_collection","list_test","pagination_test","get_test","query_test","admin_insert_test","admin_update_test","admin_delete_test","test_data"];let s=0;for(const a of n)try{const o=`${e}:${a}:`,i=t.getRange({start:o,end:o+"\xFF"});let c=0;for(const _ of i)c++,s++;c>0&&(r[a]={name:a,document_count:c,indexes:[],estimated_size_bytes:c*100})}catch{continue}return s},ne=(t,e,r,n)=>{try{const s=`index:${e}:`,a=t.getRange({start:s,end:s+"\xFF"});for(const{key:o,value:i}of a)if(typeof o=="string"&&o.startsWith(s)){const c=o.substring(s.length),_=c.split(":")[0],u=c.split(":")[1];r[_]&&u&&(r[_].indexes.includes(u)||r[_].indexes.push(u))}}catch(s){n.warn("Could not iterate index range",{error:s.message})}},ae=(t,e)=>{const r={},n=["default","test","admin"];for(const s of n)try{const a=O(s);(a.total_documents>0||a.total_collections>0)&&(r[s]={name:s,collections:new Set,documents_count:a.total_documents},a.collections.forEach(o=>{r[s].collections.add(o.name)}))}catch(a){e.warn("Failed to scan database in fallback",{database:s,error:a.message})}return r},ce=()=>{const t=g();try{const e=h();let r={};try{for(const{key:s}of e.getRange())if(typeof s=="string"&&s.includes(":")&&!s.startsWith("_")){const a=s.split(":");if(a.length>=3){const o=a[0],i=a[1];r[o]||(r[o]={name:o,collections:new Set,documents_count:0}),r[o].documents_count++,r[o].collections.add(i)}}}catch(s){t.warn("Could not iterate database range for databases",{error:s.message}),r=ae(e,t)}const n=Object.values(r).map(s=>({name:s.name,collections_count:s.collections.size,documents_count:s.documents_count,size_bytes:s.documents_count*100}));return r.default||n.push({name:"default",collections_count:0,documents_count:0,size_bytes:0}),{databases:n,total_databases:n.length}}catch(e){throw t.error("Failed to list databases",{error:e.message}),e}},O=(t="default")=>{const e=g();try{const r=h();let{collections_map:n,total_documents:s}=re(r,t,e);Object.keys(n).length===0&&(s+=oe(r,t,n)),ne(r,t,n,e);const a=Object.values(n);return{collections:a,total_collections:a.length,total_documents:s}}catch(r){throw e.error("Failed to list collections",{error:r.message}),r}},ie=(t,e={})=>{const r=g();if(!t)throw new Error("Collection name is required");try{const n=h(),{limit:s=50,skip:a=0,sort_field:o,sort_order:i="asc",database:c="default"}=e,_=[],u=`${c}:${t}:`;let m=0,p=0;for(const{key:d,value:y}of n.getRange({start:u,end:u+"\xFF"}))if(typeof d=="string"&&d.startsWith(u)){if(p<a){p++;continue}if(m>=s)break;try{const l=JSON.parse(y),f=d.substring(u.length);_.push({_id:f,...l}),m++}catch(l){r.warn("Could not parse document",{collection:t,key:d,error:l.message})}}return o&&_.length>0&&_.sort((d,y)=>{const l=d[o],f=y[o];return i==="desc"?f>l?1:f<l?-1:0:l>f?1:l<f?-1:0}),{collection:t,documents:_,count:_.length,skip:a,limit:s,has_more:m===s}}catch(n){throw r.error("Failed to list documents",{collection:t,error:n.message}),n}},_e=(t,e,r="default")=>{const n=g();if(!t||!e)throw new Error("Collection name and document ID are required");try{const s=h(),a=`${r}:${t}:${e}`,o=s.get(a);if(!o)return{found:!1,collection:t,document_id:e};const i=JSON.parse(o);return{found:!0,collection:t,document_id:e,document:{_id:e,...i}}}catch(s){throw n.error("Failed to get document",{collection:t,document_id:e,error:s.message}),s}},ue=(t,e,r,n)=>{switch(t){case"$gt":return r>e;case"$gte":return r>=e;case"$lt":return r<e;case"$lte":return r<=e;case"$ne":return r!==e;case"$in":return Array.isArray(e)&&e.includes(r);case"$regex":const s=n.$options||"";return new RegExp(e,s).test(String(r));default:return r===n}},le=(t,e)=>Object.keys(e).every(r=>{const n=e[r],s=t[r];return typeof n=="object"&&n!==null?Object.keys(n).every(a=>{const o=n[a];return ue(a,o,s,n)}):s===n}),de=(t,e,r,n,s)=>{try{const a=JSON.parse(e),i={_id:t.substring(r.length),...a};return le(i,n)?i:null}catch(a){return s.warn("Could not parse document during query",{key:t,error:a.message}),null}},me=(t,e={},r={})=>{const n=g();if(!t)throw new Error("Collection name is required");try{const s=h(),{limit:a=100,skip:o=0,database:i="default"}=r,c=[],_=`${i}:${t}:`;let u=0,m=0,p=0;for(const{key:d,value:y}of s.getRange({start:_,end:_+"\xFF"}))if(typeof d=="string"&&d.startsWith(_)){p++;const l=de(d,y,_,e,n);if(l){if(m<o){m++;continue}if(u>=a)break;c.push(l),u++}}return{collection:t,filter:e,documents:c,count:c.length,total_examined:p,skip:o,limit:a,has_more:u===a}}catch(s){throw n.error("Failed to query documents",{collection:t,filter:e,error:s.message}),s}},pe=async(t,e,r,n={})=>await(await import("./insert_one.js")).default(t,e,r,n),fe=async(t,e,r,n,s={})=>await(await import("./update_one.js")).default(t,e,r,n,s),ge=async(t,e,r,n={})=>await(await import("./delete_one.js")).default(t,e,r,n);var Se=async(t,e={},r,n)=>{const s=g(),a=Date.now();try{let o;switch(t){case"stats":const c=S();o={server:{uptime:Math.floor(process.uptime()),uptime_formatted:F(process.uptime()),node_version:process.version,platform:process.platform,arch:process.arch,pid:process.pid},memory:{heap_used_mb:c.heapUsed,heap_total_mb:c.heapTotal,rss_mb:c.rss,external_mb:c.external,heap_used_percent:c.heapTotal>0?Math.round(c.heapUsed/c.heapTotal*100):0},database:{...w.get_database_stats(),map_size_mb:Math.round((w.get_database_stats()?.map_size||0)/1024/1024),used_space_mb:Math.round((w.get_database_stats()?.used_space||0)/1024/1024),usage_percent:w.get_database_stats()?.usage_percent||0},performance:{ops_per_second:C(),avg_response_time_ms:D()},system:w.get_system_stats(),connections:r?.get_stats()||{active:n?.size||0,total:n?.size||0},write_queue:z()?.get_stats()||{},authentication:{authenticated_clients:n?.size||0,...M()},settings:(()=>{try{return{port:q().port||1983}}catch{return{port:1983}}})()};break;case"list_collections":o=O();break;case"list_documents":o=ie(e.collection,{limit:e.limit,skip:e.skip,sort_field:e.sort_field,sort_order:e.sort_order});break;case"get_document":o=_e(e.collection,e.document_id);break;case"query_documents":o=me(e.collection,e.filter,{limit:e.limit,skip:e.skip});break;case"insert_document":o=await pe(e.database||"default",e.collection,e.document,e.options);break;case"update_document":const _=e.document_id?{_id:e.document_id}:e.filter;o=await fe(e.database||"default",e.collection,_,e.update,e.options);break;case"delete_document":const u=e.document_id?{_id:e.document_id}:e.filter;o=await ge(e.database||"default",e.collection,u,e.options);break;case"test_s3_connection":o=await T();break;case"backup_now":o=await J();break;case"list_backups":o=await B();break;case"restore_backup":if(!e.backup_filename)throw new Error("backup_filename is required for restore operation");o=await I(e.backup_filename);break;case"cleanup_backups":o=await L();break;case"get_auto_index_stats":o=N();break;case"get_query_stats":o=P(e.collection);break;case"evaluate_auto_indexes":o=await R(e.collection);break;case"remove_auto_indexes":if(!e.collection)throw new Error("collection is required for remove_auto_indexes operation");o=await j(e.collection,e.field_names);break;case"create_index":if(!e.collection||!e.field)throw new Error("collection and field are required for create_index operation");o=await A(e.database||"default",e.collection,e.field,e.options);break;case"drop_index":if(!e.collection||!e.field)throw new Error("collection and field are required for drop_index operation");o=await U(e.database||"default",e.collection,e.field);break;case"get_indexes":if(!e.collection)throw new Error("collection is required for get_indexes operation");o={indexes:W(e.database||"default",e.collection)};break;case"list_databases":o=ce();break;case"get_sync_status":const m=x(),p=v();o={sync_manager:m.get_sync_status(),sync_receiver:p.get_sync_status()};break;case"update_secondary_nodes":if(!Array.isArray(e.secondary_nodes))throw new Error("secondary_nodes array is required for update_secondary_nodes operation");x().update_secondary_nodes(e.secondary_nodes),o={success:!0,message:"Secondary nodes updated successfully",secondary_nodes:e.secondary_nodes};break;case"force_sync":o=await x().force_sync();break;case"set_primary_role":if(typeof e.primary!="boolean")throw new Error("primary boolean value is required for set_primary_role operation");e.primary?(v().promote_to_primary(),o={success:!0,message:"Node promoted to primary successfully",role:"primary"}):o={success:!1,message:"Demoting primary to secondary requires server restart with updated configuration",role:"primary"};break;case"reload_sync_key":const l=v();if(!l.is_secondary)throw new Error("reload_sync_key can only be used on secondary nodes");await l.reload_api_key(),o={success:!0,message:"API_KEY reloaded successfully"};break;case"get_secondary_auth_status":const b=x().get_sync_status();o={secondary_count:b.secondary_count,auth_failures:b.stats.auth_failures,successful_syncs:b.stats.successful_syncs,failed_syncs:b.stats.failed_syncs,secondaries:b.secondaries};break;default:o={...Z(),connections:r?.get_stats()||{},write_queue:z()?.get_stats()||{},authentication:{authenticated_clients:n?.size||0,...M()},settings:(()=>{try{return{port:q().port||1983}}catch{return{port:1983}}})()}}const i=Date.now()-a;return te(i),s.info("Admin operation completed",{admin_action:t||"default",duration_ms:i,status:"success"}),o}catch(o){const i=Date.now()-a;throw s.error("Admin operation failed",{admin_action:t||"default",duration_ms:i,status:"error",error:o.message}),o}};export{Se as default,te as track_operation};
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@joystick.js/db-canary",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.0-canary.
|
|
5
|
-
"canary_version": "0.0.0-canary.
|
|
4
|
+
"version": "0.0.0-canary.2294",
|
|
5
|
+
"canary_version": "0.0.0-canary.2293",
|
|
6
6
|
"description": "JoystickDB - A minimalist database server for the Joystick framework",
|
|
7
7
|
"main": "./dist/server/index.js",
|
|
8
8
|
"scripts": {
|
|
@@ -305,7 +305,7 @@ const fallback_collection_scan = (db, database_name, collections_map) => {
|
|
|
305
305
|
'admin_test', 'test_collection', 'queue_test', 'users', 'products',
|
|
306
306
|
'orders', 'sessions', 'logs', 'analytics', 'settings', 'another_collection',
|
|
307
307
|
'list_test', 'pagination_test', 'get_test', 'query_test', 'admin_insert_test',
|
|
308
|
-
'admin_update_test', 'admin_delete_test'
|
|
308
|
+
'admin_update_test', 'admin_delete_test', 'test_data'
|
|
309
309
|
];
|
|
310
310
|
|
|
311
311
|
let additional_documents = 0;
|
|
@@ -367,6 +367,109 @@ const add_index_information = (db, database_name, collections_map, log) => {
|
|
|
367
367
|
}
|
|
368
368
|
};
|
|
369
369
|
|
|
370
|
+
/**
|
|
371
|
+
* Fallback method to find databases using known patterns.
|
|
372
|
+
* @param {Object} db - Database instance
|
|
373
|
+
* @param {Object} log - Logger instance
|
|
374
|
+
* @returns {Object} Databases map
|
|
375
|
+
*/
|
|
376
|
+
const fallback_database_scan = (db, log) => {
|
|
377
|
+
const databases_map = {};
|
|
378
|
+
const known_databases = ['default', 'test', 'admin'];
|
|
379
|
+
|
|
380
|
+
for (const database_name of known_databases) {
|
|
381
|
+
try {
|
|
382
|
+
const collections_result = list_collections(database_name);
|
|
383
|
+
|
|
384
|
+
if (collections_result.total_documents > 0 || collections_result.total_collections > 0) {
|
|
385
|
+
databases_map[database_name] = {
|
|
386
|
+
name: database_name,
|
|
387
|
+
collections: new Set(),
|
|
388
|
+
documents_count: collections_result.total_documents
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// Add collection names to the set
|
|
392
|
+
collections_result.collections.forEach(col => {
|
|
393
|
+
databases_map[database_name].collections.add(col.name);
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
} catch (error) {
|
|
397
|
+
log.warn('Failed to scan database in fallback', { database: database_name, error: error.message });
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return databases_map;
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Lists all databases in the system with metadata.
|
|
406
|
+
* @returns {Object} Object containing databases array and total counts
|
|
407
|
+
* @throws {Error} When database listing fails
|
|
408
|
+
*/
|
|
409
|
+
const list_databases = () => {
|
|
410
|
+
const log = create_context_logger();
|
|
411
|
+
|
|
412
|
+
try {
|
|
413
|
+
const db = get_database();
|
|
414
|
+
let databases_map = {};
|
|
415
|
+
|
|
416
|
+
// Try to scan the database for unique database names
|
|
417
|
+
try {
|
|
418
|
+
for (const { key } of db.getRange()) {
|
|
419
|
+
if (typeof key === 'string' && key.includes(':') && !key.startsWith('_')) {
|
|
420
|
+
const parts = key.split(':');
|
|
421
|
+
if (parts.length >= 3) {
|
|
422
|
+
const database_name = parts[0];
|
|
423
|
+
const collection_name = parts[1];
|
|
424
|
+
|
|
425
|
+
if (!databases_map[database_name]) {
|
|
426
|
+
databases_map[database_name] = {
|
|
427
|
+
name: database_name,
|
|
428
|
+
collections: new Set(),
|
|
429
|
+
documents_count: 0
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
databases_map[database_name].documents_count++;
|
|
434
|
+
databases_map[database_name].collections.add(collection_name);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
} catch (range_error) {
|
|
439
|
+
log.warn('Could not iterate database range for databases', { error: range_error.message });
|
|
440
|
+
|
|
441
|
+
// Use fallback method
|
|
442
|
+
databases_map = fallback_database_scan(db, log);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Convert collections sets to counts and create final database objects
|
|
446
|
+
const databases_array = Object.values(databases_map).map(db_info => ({
|
|
447
|
+
name: db_info.name,
|
|
448
|
+
collections_count: db_info.collections.size,
|
|
449
|
+
documents_count: db_info.documents_count,
|
|
450
|
+
size_bytes: db_info.documents_count * 100 // Rough estimate
|
|
451
|
+
}));
|
|
452
|
+
|
|
453
|
+
// Ensure 'default' database is always included, even if empty
|
|
454
|
+
if (!databases_map.default) {
|
|
455
|
+
databases_array.push({
|
|
456
|
+
name: 'default',
|
|
457
|
+
collections_count: 0,
|
|
458
|
+
documents_count: 0,
|
|
459
|
+
size_bytes: 0
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return {
|
|
464
|
+
databases: databases_array,
|
|
465
|
+
total_databases: databases_array.length
|
|
466
|
+
};
|
|
467
|
+
} catch (error) {
|
|
468
|
+
log.error('Failed to list databases', { error: error.message });
|
|
469
|
+
throw error;
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
|
|
370
473
|
/**
|
|
371
474
|
* Lists all collections in the database with metadata.
|
|
372
475
|
* @param {string} [database_name='default'] - Database name to list collections from
|
|
@@ -869,6 +972,10 @@ export default async (admin_action, data = {}, connection_manager, authenticated
|
|
|
869
972
|
result = { indexes: get_indexes(data.database || 'default', data.collection) };
|
|
870
973
|
break;
|
|
871
974
|
|
|
975
|
+
case 'list_databases':
|
|
976
|
+
result = list_databases();
|
|
977
|
+
break;
|
|
978
|
+
|
|
872
979
|
case 'get_sync_status':
|
|
873
980
|
const sync_manager = get_simple_sync_manager();
|
|
874
981
|
const sync_receiver = get_sync_receiver();
|
|
@@ -1240,6 +1240,154 @@ test('collection chaining API handles errors properly', async (t) => {
|
|
|
1240
1240
|
t.truthy(error);
|
|
1241
1241
|
});
|
|
1242
1242
|
|
|
1243
|
+
// list_databases operation tests
|
|
1244
|
+
test('client has list_databases method available', (t) => {
|
|
1245
|
+
client = joystickdb.client({
|
|
1246
|
+
host: 'localhost',
|
|
1247
|
+
port: 9999,
|
|
1248
|
+
reconnect: false,
|
|
1249
|
+
auto_connect: false
|
|
1250
|
+
});
|
|
1251
|
+
|
|
1252
|
+
t.is(typeof client.list_databases, 'function');
|
|
1253
|
+
t.truthy(client.list_databases);
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
test('client can perform list_databases operation', async (t) => {
|
|
1257
|
+
const test_port = get_next_port();
|
|
1258
|
+
server = await create_server();
|
|
1259
|
+
|
|
1260
|
+
await new Promise((resolve) => {
|
|
1261
|
+
server.listen(test_port, () => {
|
|
1262
|
+
setTimeout(resolve, 100);
|
|
1263
|
+
});
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
const setup_client = joystickdb.client({
|
|
1267
|
+
port: test_port,
|
|
1268
|
+
reconnect: false
|
|
1269
|
+
});
|
|
1270
|
+
|
|
1271
|
+
await new Promise((resolve) => {
|
|
1272
|
+
setup_client.on('connect', resolve);
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
const setup_result = await setup_client.setup();
|
|
1276
|
+
setup_client.disconnect();
|
|
1277
|
+
|
|
1278
|
+
client = joystickdb.client({
|
|
1279
|
+
port: test_port,
|
|
1280
|
+
reconnect: false,
|
|
1281
|
+
password: setup_result.password
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1284
|
+
await new Promise((resolve) => {
|
|
1285
|
+
client.on('authenticated', resolve);
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
const result = await client.list_databases();
|
|
1289
|
+
|
|
1290
|
+
t.is(result.ok, 1);
|
|
1291
|
+
t.truthy(result.databases);
|
|
1292
|
+
t.true(Array.isArray(result.databases));
|
|
1293
|
+
t.truthy(result.databases.some(db => db.name === 'default'));
|
|
1294
|
+
});
|
|
1295
|
+
|
|
1296
|
+
test('client list_databases returns default database after data insertion', async (t) => {
|
|
1297
|
+
const test_port = get_next_port();
|
|
1298
|
+
server = await create_server();
|
|
1299
|
+
|
|
1300
|
+
await new Promise((resolve) => {
|
|
1301
|
+
server.listen(test_port, () => {
|
|
1302
|
+
setTimeout(resolve, 100);
|
|
1303
|
+
});
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1306
|
+
const setup_client = joystickdb.client({
|
|
1307
|
+
port: test_port,
|
|
1308
|
+
reconnect: false
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1311
|
+
await new Promise((resolve) => {
|
|
1312
|
+
setup_client.on('connect', resolve);
|
|
1313
|
+
});
|
|
1314
|
+
|
|
1315
|
+
const setup_result = await setup_client.setup();
|
|
1316
|
+
setup_client.disconnect();
|
|
1317
|
+
|
|
1318
|
+
client = joystickdb.client({
|
|
1319
|
+
port: test_port,
|
|
1320
|
+
reconnect: false,
|
|
1321
|
+
password: setup_result.password
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1324
|
+
await new Promise((resolve) => {
|
|
1325
|
+
client.on('authenticated', resolve);
|
|
1326
|
+
});
|
|
1327
|
+
|
|
1328
|
+
// Insert some data to ensure default database exists
|
|
1329
|
+
await client.db('default').collection('test_data').insert_one({ test: 'value' });
|
|
1330
|
+
|
|
1331
|
+
const result = await client.list_databases();
|
|
1332
|
+
|
|
1333
|
+
t.is(result.ok, 1);
|
|
1334
|
+
t.truthy(result.databases);
|
|
1335
|
+
t.true(Array.isArray(result.databases));
|
|
1336
|
+
|
|
1337
|
+
const default_db = result.databases.find(db => db.name === 'default');
|
|
1338
|
+
t.truthy(default_db);
|
|
1339
|
+
t.is(default_db.name, 'default');
|
|
1340
|
+
t.truthy(default_db.collections_count >= 1);
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
test('client list_databases method is properly bound to instance', async (t) => {
|
|
1344
|
+
const test_port = get_next_port();
|
|
1345
|
+
server = await create_server();
|
|
1346
|
+
|
|
1347
|
+
await new Promise((resolve) => {
|
|
1348
|
+
server.listen(test_port, () => {
|
|
1349
|
+
setTimeout(resolve, 100);
|
|
1350
|
+
});
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
const setup_client = joystickdb.client({
|
|
1354
|
+
port: test_port,
|
|
1355
|
+
reconnect: false
|
|
1356
|
+
});
|
|
1357
|
+
|
|
1358
|
+
await new Promise((resolve) => {
|
|
1359
|
+
setup_client.on('connect', resolve);
|
|
1360
|
+
});
|
|
1361
|
+
|
|
1362
|
+
const setup_result = await setup_client.setup();
|
|
1363
|
+
setup_client.disconnect();
|
|
1364
|
+
|
|
1365
|
+
client = joystickdb.client({
|
|
1366
|
+
port: test_port,
|
|
1367
|
+
reconnect: false,
|
|
1368
|
+
password: setup_result.password
|
|
1369
|
+
});
|
|
1370
|
+
|
|
1371
|
+
await new Promise((resolve) => {
|
|
1372
|
+
client.on('authenticated', resolve);
|
|
1373
|
+
});
|
|
1374
|
+
|
|
1375
|
+
// Test method exists and is callable
|
|
1376
|
+
t.is(typeof client.list_databases, 'function');
|
|
1377
|
+
|
|
1378
|
+
// Test method binding by destructuring
|
|
1379
|
+
const { list_databases } = client;
|
|
1380
|
+
t.is(typeof list_databases, 'function');
|
|
1381
|
+
|
|
1382
|
+
// Test calling the method directly
|
|
1383
|
+
const result1 = await client.list_databases();
|
|
1384
|
+
t.is(result1.ok, 1);
|
|
1385
|
+
|
|
1386
|
+
// Test calling the destructured method (this would fail if binding is wrong)
|
|
1387
|
+
const result2 = await list_databases.call(client);
|
|
1388
|
+
t.is(result2.ok, 1);
|
|
1389
|
+
});
|
|
1390
|
+
|
|
1243
1391
|
// delete_many operation tests
|
|
1244
1392
|
test('client can perform delete_many operation via main client method', async (t) => {
|
|
1245
1393
|
const test_port = get_next_port();
|