@joystick.js/db-canary 0.0.0-canary.2294 → 0.0.0-canary.2296

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 h from"net";import{EventEmitter as l}from"events";import{encode as m,decode as p}from"msgpackr";import f from"./database.js";const _=()=>({useFloat32:!1,int64AsType:"number",mapsAsObjects:!0}),g=s=>{const e=m(s,_()),t=Buffer.allocUnsafe(4);return t.writeUInt32BE(e.length,0),Buffer.concat([t,e])},q=(s,e)=>{const t=s.slice(0,e),n=s.slice(e);try{return{message:p(t,_()),buffer:n}}catch(i){throw new Error(`Invalid message format: ${i.message}`)}},y=s=>{if(s.length<4)return{expected_length:null,buffer:s};const e=s.readUInt32BE(0),t=s.slice(4);return{expected_length:e,buffer:t}},b=()=>{let s=Buffer.alloc(0),e=null;return{parse_messages:i=>{s=Buffer.concat([s,i]);const r=[];for(;s.length>0;){if(e===null){const c=y(s);if(e=c.expected_length,s=c.buffer,e===null)break}if(s.length<e)break;const a=q(s,e);r.push(a.message),s=a.buffer,e=null}return r},reset:()=>{s=Buffer.alloc(0),e=null}}},w=(s,e)=>Math.min(e*Math.pow(2,s-1),3e4),k=(s={})=>({host:s.host||"localhost",port:s.port||1983,password:s.password||null,timeout:s.timeout||5e3,reconnect:s.reconnect!==!1,max_reconnect_attempts:s.max_reconnect_attempts||10,reconnect_delay:s.reconnect_delay||1e3,auto_connect:s.auto_connect!==!1}),x=(s,e,t)=>setTimeout(()=>{s&&!s.destroyed&&(s.destroy(),e(new Error("Connection timeout")))},t),E=(s,e,t)=>setTimeout(()=>{const n=s.get(e);n&&(s.delete(e),n.reject(new Error("Request timeout")))},t),u=(s,e)=>{for(const[t,{reject:n,timeout:i}]of s)clearTimeout(i),n(new Error(e));s.clear()},T=s=>s.ok===1||s.ok===!0,v=s=>s.ok===0||s.ok===!1,B=s=>typeof s.error=="string"?s.error:JSON.stringify(s.error)||"Operation failed";class d extends l{constructor(e={}){super();const t=k(e);this.host=t.host,this.port=t.port,this.password=t.password,this.timeout=t.timeout,this.reconnect=t.reconnect,this.max_reconnect_attempts=t.max_reconnect_attempts,this.reconnect_delay=t.reconnect_delay,this.socket=null,this.message_parser=null,this.is_connected=!1,this.is_authenticated=!1,this.is_connecting=!1,this.reconnect_attempts=0,this.reconnect_timeout=null,this.pending_requests=new Map,this.request_id_counter=0,this.request_queue=[],t.auto_connect&&this.connect()}connect(){if(this.is_connecting||this.is_connected)return;this.is_connecting=!0,this.socket=new h.Socket,this.message_parser=b();const e=x(this.socket,this.handle_connection_error.bind(this),this.timeout);this.setup_socket_handlers(e),this.socket.connect(this.port,this.host,()=>{this.handle_successful_connection(e)})}setup_socket_handlers(e){this.socket.on("data",t=>{this.handle_incoming_data(t)}),this.socket.on("error",t=>{clearTimeout(e),this.handle_connection_error(t)}),this.socket.on("close",()=>{clearTimeout(e),this.handle_disconnect()})}handle_successful_connection(e){clearTimeout(e),this.is_connected=!0,this.is_connecting=!1,this.reconnect_attempts=0,this.emit("connect"),this.password?this.authenticate():this.handle_authentication_complete()}handle_authentication_complete(){this.is_authenticated=!0,this.emit("authenticated"),this.process_request_queue()}handle_incoming_data(e){try{const t=this.message_parser.parse_messages(e);for(const n of t)this.handle_message(n)}catch(t){this.emit("error",new Error(`Message parsing failed: ${t.message}`))}}async authenticate(){if(!this.password){this.emit("error",new Error('Password required for authentication. Provide password in client options: joystickdb.client({ password: "your_password" })')),this.disconnect();return}try{if((await this.send_request("authentication",{password:this.password})).ok===1)this.handle_authentication_complete();else throw new Error("Authentication failed")}catch(e){this.emit("error",new Error(`Authentication error: ${e.message}`)),this.disconnect()}}handle_message(e){this.pending_requests.size>0?this.handle_pending_request_response(e):this.emit("response",e)}handle_pending_request_response(e){const[t,{resolve:n,reject:i,timeout:r}]=this.pending_requests.entries().next().value;if(clearTimeout(r),this.pending_requests.delete(t),T(e))n(e);else if(v(e)){const a=B(e);i(new Error(a))}else n(e)}handle_connection_error(e){this.reset_connection_state(),u(this.pending_requests,"Connection lost"),this.emit("error",e),this.should_attempt_reconnect()?this.schedule_reconnect():this.emit("disconnect")}handle_disconnect(){this.reset_connection_state(),u(this.pending_requests,"Connection closed"),this.should_attempt_reconnect()?this.schedule_reconnect():this.emit("disconnect")}reset_connection_state(){this.is_connecting=!1,this.is_connected=!1,this.is_authenticated=!1,this.socket&&(this.socket.removeAllListeners(),this.socket.destroy(),this.socket=null),this.message_parser&&this.message_parser.reset()}should_attempt_reconnect(){return this.reconnect&&this.reconnect_attempts<this.max_reconnect_attempts}schedule_reconnect(){this.reconnect_attempts++;const e=w(this.reconnect_attempts,this.reconnect_delay);this.emit("reconnecting",{attempt:this.reconnect_attempts,delay:e}),this.reconnect_timeout=setTimeout(()=>{this.connect()},e)}send_request(e,t={},n=!0){return new Promise((i,r)=>{const a=++this.request_id_counter,o={message:{op:e,data:t},resolve:i,reject:r,request_id:a};if(this.should_queue_request(e,n)){this.request_queue.push(o);return}this.send_request_now(o)})}should_queue_request(e,t){const i=!["authentication","setup","ping"].includes(e);return(!this.is_connected||i&&!this.is_authenticated)&&t}send_request_now(e){const{message:t,resolve:n,reject:i,request_id:r}=e,a=E(this.pending_requests,r,this.timeout);this.pending_requests.set(r,{resolve:n,reject:i,timeout:a});try{const c=g(t);this.socket.write(c)}catch(c){clearTimeout(a),this.pending_requests.delete(r),i(c)}}process_request_queue(){for(;this.request_queue.length>0&&this.is_connected&&this.is_authenticated;){const e=this.request_queue.shift();this.send_request_now(e)}}disconnect(){this.reconnect=!1,this.reconnect_timeout&&(clearTimeout(this.reconnect_timeout),this.reconnect_timeout=null),this.socket&&this.socket.end()}async backup_now(){return this.send_request("admin",{admin_action:"backup_now"})}async list_backups(){return this.send_request("admin",{admin_action:"list_backups"})}async restore_backup(e){return this.send_request("admin",{admin_action:"restore_backup",backup_name:e})}async get_replication_status(){return this.send_request("admin",{admin_action:"get_replication_status"})}async add_secondary(e){return this.send_request("admin",{admin_action:"add_secondary",...e})}async remove_secondary(e){return this.send_request("admin",{admin_action:"remove_secondary",secondary_id:e})}async sync_secondaries(){return this.send_request("admin",{admin_action:"sync_secondaries"})}async get_secondary_health(){return this.send_request("admin",{admin_action:"get_secondary_health"})}async get_forwarder_status(){return this.send_request("admin",{admin_action:"get_forwarder_status"})}async ping(){return this.send_request("ping",{},!1)}async reload(){return this.send_request("reload")}async get_auto_index_stats(){return this.send_request("admin",{admin_action:"get_auto_index_stats"})}async setup(){const e=await this.send_request("setup",{},!1);return e.data&&e.data.instructions&&console.log(e.data.instructions),e}async delete_many(e,t={},n={}){return this.send_request("delete_many",{database:"default",collection:e,filter:t,options:n})}db(e){return new f(this,e)}async list_databases(){return this.send_request("admin",{admin_action:"list_databases"})}async get_stats(){return this.send_request("admin",{admin_action:"stats"})}}class j{constructor(e,t,n){this.client=e,this.database_name=t,this.collection_name=n}async insert_one(e,t={}){return this.client.send_request("insert_one",{database:this.database_name,collection:this.collection_name,document:e,options:t})}async find_one(e={},t={}){return(await this.client.send_request("find_one",{database:this.database_name,collection:this.collection_name,filter:e,options:t})).document}async find(e={},t={}){return(await this.client.send_request("find",{database:this.database_name,collection:this.collection_name,filter:e,options:t})).documents||[]}async count_documents(e={},t={}){return(await this.client.send_request("count_documents",{database:this.database_name,collection:this.collection_name,filter:e,options:t})).count}async update_one(e,t,n={}){return this.client.send_request("update_one",{database:this.database_name,collection:this.collection_name,filter:e,update:t,options:n})}async delete_one(e,t={}){return this.client.send_request("delete_one",{database:this.database_name,collection:this.collection_name,filter:e,options:t})}async delete_many(e={},t={}){return this.client.send_request("delete_many",{database:this.database_name,collection:this.collection_name,filter:e,options:t})}async bulk_write(e,t={}){return this.client.send_request("bulk_write",{database:this.database_name,collection:this.collection_name,operations:e,options:t})}async create_index(e,t={}){return this.client.send_request("create_index",{database:this.database_name,collection:this.collection_name,field:e,options:t})}async upsert_index(e,t={}){return this.client.send_request("create_index",{database:this.database_name,collection:this.collection_name,field:e,options:{...t,upsert:!0}})}async drop_index(e){return this.client.send_request("drop_index",{database:this.database_name,collection:this.collection_name,field:e})}async get_indexes(){return this.client.send_request("get_indexes",{database:this.database_name,collection:this.collection_name})}}d.Collection=j;const C={client:s=>new d(s)};var P=C;export{P as default};
1
+ import d from"net";import{EventEmitter as l}from"events";import{encode as m,decode as p}from"msgpackr";import f from"./database.js";const _=()=>({useFloat32:!1,int64AsType:"number",mapsAsObjects:!0}),g=s=>{const e=m(s,_()),t=Buffer.allocUnsafe(4);return t.writeUInt32BE(e.length,0),Buffer.concat([t,e])},q=(s,e)=>{const t=s.slice(0,e),n=s.slice(e);try{return{message:p(t,_()),buffer:n}}catch(i){throw new Error(`Invalid message format: ${i.message}`)}},y=s=>{if(s.length<4)return{expected_length:null,buffer:s};const e=s.readUInt32BE(0),t=s.slice(4);return{expected_length:e,buffer:t}},b=()=>{let s=Buffer.alloc(0),e=null;return{parse_messages:i=>{s=Buffer.concat([s,i]);const a=[];for(;s.length>0;){if(e===null){const c=y(s);if(e=c.expected_length,s=c.buffer,e===null)break}if(s.length<e)break;const r=q(s,e);a.push(r.message),s=r.buffer,e=null}return a},reset:()=>{s=Buffer.alloc(0),e=null}}},w=(s,e)=>Math.min(e*Math.pow(2,s-1),3e4),k=(s={})=>({host:s.host||"localhost",port:s.port||1983,authentication:s.authentication||null,timeout:s.timeout||5e3,reconnect:s.reconnect!==!1,max_reconnect_attempts:s.max_reconnect_attempts||10,reconnect_delay:s.reconnect_delay||1e3,auto_connect:s.auto_connect!==!1}),x=(s,e,t)=>setTimeout(()=>{s&&!s.destroyed&&(s.destroy(),e(new Error("Connection timeout")))},t),E=(s,e,t)=>setTimeout(()=>{const n=s.get(e);n&&(s.delete(e),n.reject(new Error("Request timeout")))},t),u=(s,e)=>{for(const[t,{reject:n,timeout:i}]of s)clearTimeout(i),n(new Error(e));s.clear()},T=s=>s.ok===1||s.ok===!0,v=s=>s.ok===0||s.ok===!1,j=s=>typeof s.error=="string"?s.error:JSON.stringify(s.error)||"Operation failed";class h extends l{constructor(e={}){if(super(),e.password&&typeof e.password=="string"&&!e.authentication)throw new Error('Authentication must be provided as an object with username and password. Use: { authentication: { username: "your_username", password: "your_password" } }');const t=k(e);this.host=t.host,this.port=t.port,this.authentication=t.authentication,this.timeout=t.timeout,this.reconnect=t.reconnect,this.max_reconnect_attempts=t.max_reconnect_attempts,this.reconnect_delay=t.reconnect_delay,this.socket=null,this.message_parser=null,this.is_connected=!1,this.is_authenticated=!1,this.is_connecting=!1,this.reconnect_attempts=0,this.reconnect_timeout=null,this.pending_requests=new Map,this.request_id_counter=0,this.request_queue=[],t.auto_connect&&this.connect()}connect(){if(this.is_connecting||this.is_connected)return;this.is_connecting=!0,this.socket=new d.Socket,this.message_parser=b();const e=x(this.socket,this.handle_connection_error.bind(this),this.timeout);this.setup_socket_handlers(e),this.socket.connect(this.port,this.host,()=>{this.handle_successful_connection(e)})}setup_socket_handlers(e){this.socket.on("data",t=>{this.handle_incoming_data(t)}),this.socket.on("error",t=>{clearTimeout(e),this.handle_connection_error(t)}),this.socket.on("close",()=>{clearTimeout(e),this.handle_disconnect()})}handle_successful_connection(e){clearTimeout(e),this.is_connected=!0,this.is_connecting=!1,this.reconnect_attempts=0,this.emit("connect"),this.authentication?this.authenticate():this.handle_authentication_complete()}handle_authentication_complete(){this.is_authenticated=!0,this.emit("authenticated"),this.process_request_queue()}handle_incoming_data(e){try{const t=this.message_parser.parse_messages(e);for(const n of t)this.handle_message(n)}catch(t){this.emit("error",new Error(`Message parsing failed: ${t.message}`))}}async authenticate(){if(!this.authentication||!this.authentication.username||!this.authentication.password){this.emit("error",new Error('Authentication required. Provide authentication object in client options: joystickdb.client({ authentication: { username: "your_username", password: "your_password" } })')),this.disconnect();return}try{if((await this.send_request("authentication",{username:this.authentication.username,password:this.authentication.password})).ok===1)this.handle_authentication_complete();else throw new Error("Authentication failed")}catch(e){this.emit("error",new Error(`Authentication error: ${e.message}`)),this.disconnect()}}handle_message(e){this.pending_requests.size>0?this.handle_pending_request_response(e):this.emit("response",e)}handle_pending_request_response(e){const[t,{resolve:n,reject:i,timeout:a}]=this.pending_requests.entries().next().value;if(clearTimeout(a),this.pending_requests.delete(t),T(e))n(e);else if(v(e)){const r=j(e);i(new Error(r))}else n(e)}handle_connection_error(e){this.reset_connection_state(),u(this.pending_requests,"Connection lost"),this.emit("error",e),this.should_attempt_reconnect()?this.schedule_reconnect():this.emit("disconnect")}handle_disconnect(){this.reset_connection_state(),u(this.pending_requests,"Connection closed"),this.should_attempt_reconnect()?this.schedule_reconnect():this.emit("disconnect")}reset_connection_state(){this.is_connecting=!1,this.is_connected=!1,this.is_authenticated=!1,this.socket&&(this.socket.removeAllListeners(),this.socket.destroy(),this.socket=null),this.message_parser&&this.message_parser.reset()}should_attempt_reconnect(){return this.reconnect&&this.reconnect_attempts<this.max_reconnect_attempts}schedule_reconnect(){this.reconnect_attempts++;const e=w(this.reconnect_attempts,this.reconnect_delay);this.emit("reconnecting",{attempt:this.reconnect_attempts,delay:e}),this.reconnect_timeout=setTimeout(()=>{this.connect()},e)}send_request(e,t={},n=!0){return new Promise((i,a)=>{const r=++this.request_id_counter,o={message:{op:e,data:t},resolve:i,reject:a,request_id:r};if(this.should_queue_request(e,n)){this.request_queue.push(o);return}this.send_request_now(o)})}should_queue_request(e,t){const i=!["authentication","setup","ping"].includes(e);return(!this.is_connected||i&&!this.is_authenticated)&&t}send_request_now(e){const{message:t,resolve:n,reject:i,request_id:a}=e,r=E(this.pending_requests,a,this.timeout);this.pending_requests.set(a,{resolve:n,reject:i,timeout:r});try{const c=g(t);this.socket.write(c)}catch(c){clearTimeout(r),this.pending_requests.delete(a),i(c)}}process_request_queue(){for(;this.request_queue.length>0&&this.is_connected&&this.is_authenticated;){const e=this.request_queue.shift();this.send_request_now(e)}}disconnect(){this.reconnect=!1,this.reconnect_timeout&&(clearTimeout(this.reconnect_timeout),this.reconnect_timeout=null),this.socket&&this.socket.end()}async backup_now(){return this.send_request("admin",{admin_action:"backup_now"})}async list_backups(){return this.send_request("admin",{admin_action:"list_backups"})}async restore_backup(e){return this.send_request("admin",{admin_action:"restore_backup",backup_name:e})}async get_replication_status(){return this.send_request("admin",{admin_action:"get_replication_status"})}async add_secondary(e){return this.send_request("admin",{admin_action:"add_secondary",...e})}async remove_secondary(e){return this.send_request("admin",{admin_action:"remove_secondary",secondary_id:e})}async sync_secondaries(){return this.send_request("admin",{admin_action:"sync_secondaries"})}async get_secondary_health(){return this.send_request("admin",{admin_action:"get_secondary_health"})}async get_forwarder_status(){return this.send_request("admin",{admin_action:"get_forwarder_status"})}async ping(){return this.send_request("ping",{},!1)}async reload(){return this.send_request("reload")}async get_auto_index_stats(){return this.send_request("admin",{admin_action:"get_auto_index_stats"})}async setup(){const e=await this.send_request("setup",{},!1);return e.data&&e.data.instructions&&console.log(e.data.instructions),e}async delete_many(e,t={},n={}){return this.send_request("delete_many",{database:"default",collection:e,filter:t,options:n})}db(e){return new f(this,e)}async list_databases(){return this.send_request("admin",{admin_action:"list_databases"})}async get_stats(){return this.send_request("admin",{admin_action:"stats"})}async admin(e,t={}){return this.send_request("admin",{admin_action:e,...t})}}class B{constructor(e,t,n){this.client=e,this.database_name=t,this.collection_name=n}async insert_one(e,t={}){return this.client.send_request("insert_one",{database:this.database_name,collection:this.collection_name,document:e,options:t})}async find_one(e={},t={}){return(await this.client.send_request("find_one",{database:this.database_name,collection:this.collection_name,filter:e,options:t})).document}async find(e={},t={}){return(await this.client.send_request("find",{database:this.database_name,collection:this.collection_name,filter:e,options:t})).documents||[]}async count_documents(e={},t={}){return(await this.client.send_request("count_documents",{database:this.database_name,collection:this.collection_name,filter:e,options:t})).count}async update_one(e,t,n={}){return this.client.send_request("update_one",{database:this.database_name,collection:this.collection_name,filter:e,update:t,options:n})}async delete_one(e,t={}){return this.client.send_request("delete_one",{database:this.database_name,collection:this.collection_name,filter:e,options:t})}async delete_many(e={},t={}){return this.client.send_request("delete_many",{database:this.database_name,collection:this.collection_name,filter:e,options:t})}async bulk_write(e,t={}){return this.client.send_request("bulk_write",{database:this.database_name,collection:this.collection_name,operations:e,options:t})}async create_index(e,t={}){return this.client.send_request("create_index",{database:this.database_name,collection:this.collection_name,field:e,options:t})}async upsert_index(e,t={}){return this.client.send_request("create_index",{database:this.database_name,collection:this.collection_name,field:e,options:{...t,upsert:!0}})}async drop_index(e){return this.client.send_request("drop_index",{database:this.database_name,collection:this.collection_name,field:e})}async get_indexes(){return this.client.send_request("get_indexes",{database:this.database_name,collection:this.collection_name})}}h.Collection=B;const A={client:s=>new h(s)};var O=A;export{O as default};
@@ -1 +1 @@
1
- import S from"net";import{decode as k}from"msgpackr";import x from"./lib/op_types.js";import g from"./lib/safe_json_parse.js";import{load_settings as m,get_settings as l,get_port_configuration as c}from"./lib/load_settings.js";import{send_error as _}from"./lib/send_response.js";import{start_cluster as T}from"./cluster/index.js";import h from"./lib/logger.js";import{initialize_database as z,cleanup_database as O}from"./lib/query_engine.js";import{create_message_parser as I,encode_message as y}from"./lib/tcp_protocol.js";import{create_connection_manager as R}from"./lib/connection_manager.js";import{shutdown_write_queue as C}from"./lib/write_queue.js";import{setup_authentication as E,verify_password as q,get_client_ip as N,is_rate_limited as $,initialize_auth_manager as A,reset_auth_state as B}from"./lib/auth_manager.js";import{initialize_api_key_manager as D}from"./lib/api_key_manager.js";import{is_development_mode as v,display_development_startup_message as F,warn_undefined_node_env as J}from"./lib/development_mode.js";import{restore_backup as K,start_backup_schedule as P,stop_backup_schedule as j}from"./lib/backup_manager.js";import{initialize_simple_sync_manager as G,shutdown_simple_sync_manager as M}from"./lib/simple_sync_manager.js";import{initialize_sync_receiver as H,shutdown_sync_receiver as U}from"./lib/sync_receiver.js";import{handle_database_operation as V,handle_admin_operation as W,handle_ping_operation as Y}from"./lib/operation_dispatcher.js";import{start_http_server as L,stop_http_server as Q}from"./lib/http_server.js";import{create_recovery_token as X,initialize_recovery_manager as w,reset_recovery_state as Z}from"./lib/recovery_manager.js";import{has_settings as ee}from"./lib/load_settings.js";const i=new Set;let a=null;const re=e=>e&&e.password,d=e=>({ok:0,error:e}),te=()=>({ok:1,version:"1.0.0",message:"Authentication successful"}),u=(e,r)=>{const t=y(r);e.write(t),e.end()},p=(e,r)=>{const t=y(r);e.write(t)},ne=async(e,r={})=>{if(!re(r)){const t=d("Authentication operation requires password to be set in data.");u(e,t);return}try{const t=N(e);if($(t)){const o=d("Too many failed attempts. Please try again later.");u(e,o);return}if(!await q(r.password,t)){const o=d("Authentication failed");u(e,o);return}i.add(e.id);const s=te();p(e,s)}catch(t){const n=d(`Authentication error: ${t.message}`);u(e,n)}},se=e=>({ok:1,password:e,message:"Authentication setup completed successfully. Save this password - it will not be shown again."}),oe=e=>({ok:0,error:`Setup error: ${e}`}),ae=async(e,r={})=>{try{const t=E(),n=se(t);p(e,n)}catch(t){const n=oe(t.message);p(e,n)}},ie=(e="")=>{if(!e)throw new Error("Must pass an op type for operation.");return x.includes(e)},ce=e=>g(e),_e=e=>{try{const r=k(e);return typeof r=="string"?g(r):r}catch{return null}},or=e=>{try{return typeof e=="string"?ce(e):Buffer.isBuffer(e)?_e(e):e}catch{return null}},f=e=>v()?!0:i.has(e.id),pe=async(e,r)=>{if(e?.restore_from)try{r.info("Startup restore requested",{backup_filename:e.restore_from});const t=await K(e.restore_from);r.info("Startup restore completed",{backup_filename:e.restore_from,duration_ms:t.duration_ms});const n={...e};delete n.restore_from,process.env.JOYSTICK_DB_SETTINGS=JSON.stringify(n),m(),r.info("Removed restore_from from settings after successful restore")}catch(t){r.error("Startup restore failed",{backup_filename:e.restore_from,error:t.message}),r.info("Continuing with fresh database after restore failure")}},de=()=>{try{return m(),l()}catch{return null}},ue=async e=>{const{tcp_port:r}=c(),t=e?.data_path||`./.joystick/data/joystickdb_${r}`;z(t),A(),await D(),w()},me=e=>{try{G(),e.info("Simple sync manager initialized")}catch(r){e.warn("Failed to initialize simple sync manager",{error:r.message})}},le=e=>{H().then(()=>{e.info("Sync receiver initialized")}).catch(r=>{e.warn("Failed to initialize sync receiver",{error:r.message})})},fe=(e,r)=>{if(e?.s3)try{P(),r.info("Backup scheduling started")}catch(t){r.warn("Failed to start backup scheduling",{error:t.message})}},ge=async(e,r)=>{try{const t=await L(e);return t&&r.info("HTTP server started",{http_port:e}),t}catch(t){return r.warn("Failed to start HTTP server",{error:t.message}),null}},he=()=>{if(v()){const{tcp_port:e,http_port:r}=c();F(e,r)}else J()},ye=()=>R({max_connections:1e3,idle_timeout:600*1e3,request_timeout:5*1e3}),ve=async(e,r,t,n)=>{a.update_activity(e.id);try{const s=t.parse_messages(r);for(const o of s)await we(e,o,r.length,n)}catch(s){n.error("Message parsing failed",{client_id:e.id,error:s.message}),_(e,{message:"Invalid message format"}),e.end()}},we=async(e,r,t,n)=>{const s=r,o=s?.op||null;if(!o){_(e,{message:"Missing operation type"});return}if(!ie(o)){_(e,{message:"Invalid operation type"});return}const b=a.create_request_timeout(e.id,o);try{await be(e,o,s,t)}finally{clearTimeout(b)}},be=async(e,r,t,n)=>{const s=t?.data||{};switch(r){case"authentication":await ne(e,s);break;case"setup":await ae(e,s);break;case"insert_one":case"update_one":case"delete_one":case"delete_many":case"bulk_write":case"find_one":case"find":case"count_documents":case"create_index":case"drop_index":case"get_indexes":await V(e,r,s,f,n,a,i);break;case"ping":Y(e);break;case"admin":await W(e,s,f,a,i);break;case"reload":await Se(e);break;default:_(e,{message:`Operation ${r} not implemented`})}},Se=async e=>{if(!f(e)){_(e,{message:"Authentication required"});return}try{const r=ke(),t=await xe(),n=Te(r,t);p(e,n)}catch(r){const t={ok:0,error:`Reload operation failed: ${r.message}`};p(e,t)}},ke=()=>{try{return l()}catch{return null}},xe=async()=>{try{return await m(),l()}catch{return{port:1983,authentication:{}}}},Te=(e,r)=>({ok:1,status:"success",message:"Configuration reloaded successfully",changes:{port_changed:e?e.port!==r.port:!1,authentication_changed:e?e.authentication?.password_hash!==r.authentication?.password_hash:!1},timestamp:new Date().toISOString()}),ze=(e,r)=>{r.info("Client disconnected",{socket_id:e.id}),i.delete(e.id),a.remove_connection(e.id)},Oe=(e,r,t)=>{t.error("Socket error",{socket_id:e.id,error:r.message}),i.delete(e.id),a.remove_connection(e.id)},Ie=(e,r,t)=>{e.on("data",async n=>{await ve(e,n,r,t)}),e.on("end",()=>{ze(e,t)}),e.on("error",n=>{Oe(e,n,t)})},Re=(e,r)=>{if(!a.add_connection(e))return;const t=I();Ie(e,t,r)},Ce=()=>async()=>{try{await Q(),j(),await M(),await U(),a&&a.shutdown(),i.clear(),await C(),await new Promise(e=>setTimeout(e,100)),await O(),B(),Z()}catch{}},ar=async()=>{const{create_context_logger:e}=h("server"),r=e(),t=de();await pe(t,r),await ue(t),me(r),le(r),fe(t,r),a=ye();const{http_port:n}=c();await ge(n,r),he();const s=S.createServer((o={})=>{Re(o,r)});return s.cleanup=Ce(),s},Ee=e=>{try{w();const r=X();console.log("Emergency Recovery Token Generated"),console.log(`Visit: ${r.url}`),console.log("Token expires in 10 minutes"),e.info("Recovery token generated via CLI",{expires_at:new Date(r.expires_at).toISOString()}),process.exit(0)}catch(r){console.error("Failed to generate recovery token:",r.message),e.error("Recovery token generation failed",{error:r.message}),process.exit(1)}},qe=()=>{const{tcp_port:e}=c();return{worker_count:process.env.WORKER_COUNT?parseInt(process.env.WORKER_COUNT):void 0,port:e,environment:process.env.NODE_ENV||"development"}},Ne=(e,r)=>{const{tcp_port:t,http_port:n}=c(),s=ee();r.info("Starting JoystickDB server...",{workers:e.worker_count||"auto",tcp_port:t,http_port:n,environment:e.environment,has_settings:s,port_source:s?"JOYSTICK_DB_SETTINGS":"default"})};if(import.meta.url===`file://${process.argv[1]}`){const{create_context_logger:e}=h("main"),r=e();process.argv.includes("--generate-recovery-token")&&Ee(r);const t=qe();Ne(t,r),T(t)}export{ne as authentication,ie as check_op_type,ar as create_server,or as parse_data,ae as setup};
1
+ import k from"net";import T from"crypto";import{decode as z}from"msgpackr";import O from"./lib/op_types.js";import y from"./lib/safe_json_parse.js";import{load_settings as l,get_settings as f,get_port_configuration as _}from"./lib/load_settings.js";import{send_error as p}from"./lib/send_response.js";import{start_cluster as I}from"./cluster/index.js";import v from"./lib/logger.js";import{initialize_database as R,cleanup_database as C}from"./lib/query_engine.js";import{create_message_parser as E,encode_message as w}from"./lib/tcp_protocol.js";import{create_connection_manager as q}from"./lib/connection_manager.js";import{shutdown_write_queue as A}from"./lib/write_queue.js";import{get_client_ip as B,is_rate_limited as N,initialize_auth_manager as D,reset_auth_state as $}from"./lib/auth_manager.js";import{set_storage_engine as F,verify_credentials as J,setup_initial_admin as K,initialize_user_auth_manager as P,reset_auth_state as j,get_auth_stats as G}from"./lib/user_auth_manager.js";import{initialize_api_key_manager as M}from"./lib/api_key_manager.js";import{is_development_mode as b,display_development_startup_message as H,warn_undefined_node_env as U}from"./lib/development_mode.js";import{restore_backup as V,start_backup_schedule as W,stop_backup_schedule as Y}from"./lib/backup_manager.js";import{initialize_simple_sync_manager as L,shutdown_simple_sync_manager as Q}from"./lib/simple_sync_manager.js";import{initialize_sync_receiver as X,shutdown_sync_receiver as Z}from"./lib/sync_receiver.js";import{handle_database_operation as ee,handle_admin_operation as re,handle_ping_operation as te}from"./lib/operation_dispatcher.js";import{start_http_server as se,stop_http_server as ne}from"./lib/http_server.js";import{create_recovery_token as oe,initialize_recovery_manager as S,reset_recovery_state as ae}from"./lib/recovery_manager.js";import{has_settings as ie}from"./lib/load_settings.js";const i=new Set;let a=null;const ce=()=>T.randomBytes(16).toString("hex"),_e=e=>e&&e.username&&e.password,d=e=>({ok:0,error:e}),pe=e=>({ok:1,version:"1.0.0",message:"Authentication successful",role:e?.role||"user"}),m=(e,r)=>{const t=w(r);e.write(t),e.end()},c=(e,r)=>{const t=w(r);e.write(t)},ue=async(e,r={})=>{if(!_e(r)){const t=d("Authentication requires username and password");m(e,t);return}try{const t=B(e);if(N(t)){const n=d("Too many failed attempts. Please try again later.");m(e,n);return}const s=await J(r.username,r.password,t);if(s.success){i.add(e.id);const n=pe(s.user);c(e,n)}else{const n=d(s.error||"Authentication failed");m(e,n)}}catch{const s=d("Authentication failed");m(e,s)}},de=e=>({ok:1,password:e,message:"Authentication setup completed successfully. Save this password - it will not be shown again."}),g=e=>({ok:0,error:`Setup error: ${e}`}),me=async(e,r={})=>{try{if((await G()).configured){const u=g("Authentication already configured");c(e,u);return}const s=ce(),n=await K("admin",s,"admin@localhost.local");if(!n.success){const u=g(n.error);c(e,u);return}const o=de(s);c(e,o)}catch(t){const s=g(t.message);c(e,s)}},le=(e="")=>{if(!e)throw new Error("Must pass an op type for operation.");return O.includes(e)},fe=e=>y(e),ge=e=>{try{const r=z(e);return typeof r=="string"?y(r):r}catch{return null}},fr=e=>{try{return typeof e=="string"?fe(e):Buffer.isBuffer(e)?ge(e):e}catch{return null}},h=e=>b()?!0:i.has(e.id),he=async(e,r)=>{if(e?.restore_from)try{r.info("Startup restore requested",{backup_filename:e.restore_from});const t=await V(e.restore_from);r.info("Startup restore completed",{backup_filename:e.restore_from,duration_ms:t.duration_ms});const s={...e};delete s.restore_from,process.env.JOYSTICK_DB_SETTINGS=JSON.stringify(s),l(),r.info("Removed restore_from from settings after successful restore")}catch(t){r.error("Startup restore failed",{backup_filename:e.restore_from,error:t.message}),r.info("Continuing with fresh database after restore failure")}},ye=()=>{try{return l(),f()}catch{return null}},ve=async e=>{const{tcp_port:r}=_(),t=e?.data_path||`./.joystick/data/joystickdb_${r}`,s=R(t);D(),P(),F(s),await M(),S()},we=e=>{try{L(),e.info("Simple sync manager initialized")}catch(r){e.warn("Failed to initialize simple sync manager",{error:r.message})}},be=e=>{X().then(()=>{e.info("Sync receiver initialized")}).catch(r=>{e.warn("Failed to initialize sync receiver",{error:r.message})})},Se=(e,r)=>{if(e?.s3)try{W(),r.info("Backup scheduling started")}catch(t){r.warn("Failed to start backup scheduling",{error:t.message})}},xe=async(e,r)=>{try{const t=await se(e);return t&&r.info("HTTP server started",{http_port:e}),t}catch(t){return r.warn("Failed to start HTTP server",{error:t.message}),null}},ke=()=>{if(b()){const{tcp_port:e,http_port:r}=_();H(e,r)}else U()},Te=()=>q({max_connections:1e3,idle_timeout:600*1e3,request_timeout:5*1e3}),ze=async(e,r,t,s)=>{a.update_activity(e.id);try{const n=t.parse_messages(r);for(const o of n)await Oe(e,o,r.length,s)}catch(n){s.error("Message parsing failed",{client_id:e.id,error:n.message}),p(e,{message:"Invalid message format"}),e.end()}},Oe=async(e,r,t,s)=>{const n=r,o=n?.op||null;if(!o){p(e,{message:"Missing operation type"});return}if(!le(o)){p(e,{message:"Invalid operation type"});return}const x=a.create_request_timeout(e.id,o);try{await Ie(e,o,n,t)}finally{clearTimeout(x)}},Ie=async(e,r,t,s)=>{const n=t?.data||{};switch(r){case"authentication":await ue(e,n);break;case"setup":await me(e,n);break;case"insert_one":case"update_one":case"delete_one":case"delete_many":case"bulk_write":case"find_one":case"find":case"count_documents":case"create_index":case"drop_index":case"get_indexes":await ee(e,r,n,h,s,a,i);break;case"ping":te(e);break;case"admin":await re(e,n,h,a,i);break;case"reload":await Re(e);break;default:p(e,{message:`Operation ${r} not implemented`})}},Re=async e=>{if(!h(e)){p(e,{message:"Authentication required"});return}try{const r=Ce(),t=await Ee(),s=qe(r,t);c(e,s)}catch(r){const t={ok:0,error:`Reload operation failed: ${r.message}`};c(e,t)}},Ce=()=>{try{return f()}catch{return null}},Ee=async()=>{try{return await l(),f()}catch{return{port:1983,authentication:{}}}},qe=(e,r)=>({ok:1,status:"success",message:"Configuration reloaded successfully",changes:{port_changed:e?e.port!==r.port:!1,authentication_changed:e?e.authentication?.password_hash!==r.authentication?.password_hash:!1},timestamp:new Date().toISOString()}),Ae=(e,r)=>{r.info("Client disconnected",{socket_id:e.id}),i.delete(e.id),a.remove_connection(e.id)},Be=(e,r,t)=>{t.error("Socket error",{socket_id:e.id,error:r.message}),i.delete(e.id),a.remove_connection(e.id)},Ne=(e,r,t)=>{e.on("data",async s=>{await ze(e,s,r,t)}),e.on("end",()=>{Ae(e,t)}),e.on("error",s=>{Be(e,s,t)})},De=(e,r)=>{if(!a.add_connection(e))return;const t=E();Ne(e,t,r)},$e=()=>async()=>{try{await ne(),Y(),await Q(),await Z(),a&&a.shutdown(),i.clear(),await A(),await new Promise(e=>setTimeout(e,100)),await C(),$(),j(),ae()}catch{}},gr=async()=>{const{create_context_logger:e}=v("server"),r=e(),t=ye();await he(t,r),await ve(t),we(r),be(r),Se(t,r),a=Te();const{http_port:s}=_();await xe(s,r),ke();const n=k.createServer((o={})=>{De(o,r)});return n.cleanup=$e(),n},Fe=e=>{try{S();const r=oe();console.log("Emergency Recovery Token Generated"),console.log(`Visit: ${r.url}`),console.log("Token expires in 10 minutes"),e.info("Recovery token generated via CLI",{expires_at:new Date(r.expires_at).toISOString()}),process.exit(0)}catch(r){console.error("Failed to generate recovery token:",r.message),e.error("Recovery token generation failed",{error:r.message}),process.exit(1)}},Je=()=>{const{tcp_port:e}=_();return{worker_count:process.env.WORKER_COUNT?parseInt(process.env.WORKER_COUNT):void 0,port:e,environment:process.env.NODE_ENV||"development"}},Ke=(e,r)=>{const{tcp_port:t,http_port:s}=_(),n=ie();r.info("Starting JoystickDB server...",{workers:e.worker_count||"auto",tcp_port:t,http_port:s,environment:e.environment,has_settings:n,port_source:n?"JOYSTICK_DB_SETTINGS":"default"})};if(import.meta.url===`file://${process.argv[1]}`){const{create_context_logger:e}=v("main"),r=e();process.argv.includes("--generate-recovery-token")&&Fe(r);const t=Je();Ke(t,r),I(t)}export{ue as authentication,le as check_op_type,gr as create_server,fr as parse_data,me as setup};
@@ -1 +1 @@
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};
1
+ import{get_database as y}from"../query_engine.js";import{get_settings as v}from"../load_settings.js";import{get_write_queue as M}from"../write_queue.js";import{get_auth_stats as F}from"../auth_manager.js";import{create_user as W,get_user as T,update_user as J,reset_user_password as B,delete_user as I,list_users as L,setup_initial_admin as K,get_auth_stats as C,set_storage_engine as Y}from"../user_auth_manager.js";import{get_query_statistics as G,get_auto_index_statistics as H,force_index_evaluation as Q,remove_automatic_indexes as V}from"../auto_index_manager.js";import{create_index as X,drop_index as Z,get_indexes as ee}from"../index_manager.js";import{test_s3_connection as te,create_backup as se,list_backups as re,restore_backup as oe,cleanup_old_backups as ne}from"../backup_manager.js";import{get_simple_sync_manager as q}from"../simple_sync_manager.js";import{get_sync_receiver as z}from"../sync_receiver.js";import ae from"../logger.js";import{performance_monitor as k}from"../performance_monitor.js";const{create_context_logger:h}=ae("admin"),ce=()=>{try{return v()}catch{return{port:1983}}},ie=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"}}},ue=(t,e)=>{const o={};let n=0;try{for(const{key:s}of t.getRange())if(typeof s=="string"&&s.includes(":")&&!s.startsWith("_")){const a=s.split(":")[0];o[a]=(o[a]||0)+1,n++}}catch(s){e.warn("Could not iterate database range for stats",{error:s.message})}return{collections:o,total_documents:n}},D=()=>{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)}},_e=t=>t.mapSize>0?Math.round(t.lastPageNumber*t.pageSize/t.mapSize*100):0,le=t=>({uptime:Math.floor(process.uptime()),uptime_formatted:O(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()}),de=(t,e,o,n)=>({total_documents:t,total_collections:Object.keys(e).length,collections:e,stats:o,map_size_usage_percent:n,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)}}),me=()=>{const t=h();try{const e=y(),o=ce(),n=ie(e),{collections:s,total_documents:a}=ue(e,t),r=D(),i=_e(n);return{server:le(r),database:de(a,s,n,i),performance:{ops_per_second:N(),avg_response_time_ms:R()}}}catch(e){throw t.error("Failed to get enhanced stats",{error:e.message}),e}},O=t=>{const e=Math.floor(t/86400),o=Math.floor(t%86400/3600),n=Math.floor(t%3600/60),s=Math.floor(t%60);return e>0?`${e}d ${o}h ${n}m ${s}s`:o>0?`${o}h ${n}m ${s}s`:n>0?`${n}m ${s}s`:`${s}s`};let $=0,P=0,pe=Date.now();const N=()=>{const t=(Date.now()-pe)/1e3;return t>0?Math.round($/t):0},R=()=>$>0?Math.round(P/$):0,fe=t=>{$++,P+=t},ge=t=>({name:t,document_count:0,indexes:[],estimated_size_bytes:0}),ye=(t,e,o)=>{const n={};let s=0;try{for(const{key:a}of t.getRange())if(typeof a=="string"&&a.includes(":")&&!a.startsWith("_")){const r=a.split(":");if(r.length>=3){const i=r[0],c=r[1];i===e&&(n[c]||(n[c]=ge(c)),n[c].document_count++,s++)}}}catch(a){o.warn("Could not iterate database range for collections",{error:a.message})}return{collections_map:n,total_documents:s}},he=(t,e,o)=>{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 r=`${e}:${a}:`,i=t.getRange({start:r,end:r+"\xFF"});let c=0;for(const u of i)c++,s++;c>0&&(o[a]={name:a,document_count:c,indexes:[],estimated_size_bytes:c*100})}catch{continue}return s},be=(t,e,o,n)=>{try{const s=`index:${e}:`,a=t.getRange({start:s,end:s+"\xFF"});for(const{key:r,value:i}of a)if(typeof r=="string"&&r.startsWith(s)){const c=r.substring(s.length),u=c.split(":")[0],_=c.split(":")[1];o[u]&&_&&(o[u].indexes.includes(_)||o[u].indexes.push(_))}}catch(s){n.warn("Could not iterate index range",{error:s.message})}},we=(t,e)=>{const o={},n=["default","test","admin"];for(const s of n)try{const a=j(s);(a.total_documents>0||a.total_collections>0)&&(o[s]={name:s,collections:new Set,documents_count:a.total_documents},a.collections.forEach(r=>{o[s].collections.add(r.name)}))}catch(a){e.warn("Failed to scan database in fallback",{database:s,error:a.message})}return o},ke=()=>{const t=h();try{const e=y();let o={};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 r=a[0],i=a[1];o[r]||(o[r]={name:r,collections:new Set,documents_count:0}),o[r].documents_count++,o[r].collections.add(i)}}}catch(s){t.warn("Could not iterate database range for databases",{error:s.message}),o=we(e,t)}const n=Object.values(o).map(s=>({name:s.name,collections_count:s.collections.size,documents_count:s.documents_count,size_bytes:s.documents_count*100}));return o.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}},j=(t="default")=>{const e=h();try{const o=y();let{collections_map:n,total_documents:s}=ye(o,t,e);Object.keys(n).length===0&&(s+=he(o,t,n)),be(o,t,n,e);const a=Object.values(n);return{collections:a,total_collections:a.length,total_documents:s}}catch(o){throw e.error("Failed to list collections",{error:o.message}),o}},xe=(t,e={})=>{const o=h();if(!t)throw new Error("Collection name is required");try{const n=y(),{limit:s=50,skip:a=0,sort_field:r,sort_order:i="asc",database:c="default"}=e,u=[],_=`${c}:${t}:`;let m=0,f=0;for(const{key:d,value:b}of n.getRange({start:_,end:_+"\xFF"}))if(typeof d=="string"&&d.startsWith(_)){if(f<a){f++;continue}if(m>=s)break;try{const l=JSON.parse(b),g=d.substring(_.length);u.push({_id:g,...l}),m++}catch(l){o.warn("Could not parse document",{collection:t,key:d,error:l.message})}}return r&&u.length>0&&u.sort((d,b)=>{const l=d[r],g=b[r];return i==="desc"?g>l?1:g<l?-1:0:l>g?1:l<g?-1:0}),{collection:t,documents:u,count:u.length,skip:a,limit:s,has_more:m===s}}catch(n){throw o.error("Failed to list documents",{collection:t,error:n.message}),n}},qe=(t,e,o="default")=>{const n=h();if(!t||!e)throw new Error("Collection name and document ID are required");try{const s=y(),a=`${o}:${t}:${e}`,r=s.get(a);if(!r)return{found:!1,collection:t,document_id:e};const i=JSON.parse(r);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}},$e=(t,e,o,n)=>{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 s=n.$options||"";return new RegExp(e,s).test(String(o));default:return o===n}},ve=(t,e)=>Object.keys(e).every(o=>{const n=e[o],s=t[o];return typeof n=="object"&&n!==null?Object.keys(n).every(a=>{const r=n[a];return $e(a,r,s,n)}):s===n}),ze=(t,e,o,n,s)=>{try{const a=JSON.parse(e),i={_id:t.substring(o.length),...a};return ve(i,n)?i:null}catch(a){return s.warn("Could not parse document during query",{key:t,error:a.message}),null}},Ee=(t,e={},o={})=>{const n=h();if(!t)throw new Error("Collection name is required");try{const s=y(),{limit:a=100,skip:r=0,database:i="default"}=o,c=[],u=`${i}:${t}:`;let _=0,m=0,f=0;for(const{key:d,value:b}of s.getRange({start:u,end:u+"\xFF"}))if(typeof d=="string"&&d.startsWith(u)){f++;const l=ze(d,b,u,e,n);if(l){if(m<r){m++;continue}if(_>=a)break;c.push(l),_++}}return{collection:t,filter:e,documents:c,count:c.length,total_examined:f,skip:r,limit:a,has_more:_===a}}catch(s){throw n.error("Failed to query documents",{collection:t,filter:e,error:s.message}),s}},Se=async(t,e,o,n={})=>await(await import("./insert_one.js")).default(t,e,o,n),Me=async(t,e,o,n,s={})=>await(await import("./update_one.js")).default(t,e,o,n,s),Fe=async(t,e,o,n={})=>await(await import("./delete_one.js")).default(t,e,o,n);var Be=async(t,e={},o,n)=>{const s=h(),a=Date.now();try{let r;switch(t){case"stats":const c=D();r={server:{uptime:Math.floor(process.uptime()),uptime_formatted:O(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:{...k.get_database_stats(),map_size_mb:Math.round((k.get_database_stats()?.map_size||0)/1024/1024),used_space_mb:Math.round((k.get_database_stats()?.used_space||0)/1024/1024),usage_percent:k.get_database_stats()?.usage_percent||0},performance:{ops_per_second:N(),avg_response_time_ms:R()},system:k.get_system_stats(),connections:o?.get_stats()||{active:n?.size||0,total:n?.size||0},write_queue:M()?.get_stats()||{},authentication:{authenticated_clients:n?.size||0,...F()},settings:(()=>{try{return{port:v().port||1983}}catch{return{port:1983}}})()};break;case"list_collections":r=j();break;case"list_documents":r=xe(e.collection,{limit:e.limit,skip:e.skip,sort_field:e.sort_field,sort_order:e.sort_order});break;case"get_document":r=qe(e.collection,e.document_id);break;case"query_documents":r=Ee(e.collection,e.filter,{limit:e.limit,skip:e.skip});break;case"insert_document":r=await Se(e.database||"default",e.collection,e.document,e.options);break;case"update_document":const u=e.document_id?{_id:e.document_id}:e.filter;r=await Me(e.database||"default",e.collection,u,e.update,e.options);break;case"delete_document":const _=e.document_id?{_id:e.document_id}:e.filter;r=await Fe(e.database||"default",e.collection,_,e.options);break;case"test_s3_connection":r=await te();break;case"backup_now":r=await se();break;case"list_backups":r=await re();break;case"restore_backup":if(!e.backup_filename)throw new Error("backup_filename is required for restore operation");r=await oe(e.backup_filename);break;case"cleanup_backups":r=await ne();break;case"get_auto_index_stats":r=H();break;case"get_query_stats":r=G(e.collection);break;case"evaluate_auto_indexes":r=await Q(e.collection);break;case"remove_auto_indexes":if(!e.collection)throw new Error("collection is required for remove_auto_indexes operation");r=await V(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");r=await X(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");r=await Z(e.database||"default",e.collection,e.field);break;case"get_indexes":if(!e.collection)throw new Error("collection is required for get_indexes operation");r={indexes:ee(e.database||"default",e.collection)};break;case"list_databases":r=ke();break;case"get_sync_status":const m=q(),f=z();r={sync_manager:m.get_sync_status(),sync_receiver:f.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");q().update_secondary_nodes(e.secondary_nodes),r={success:!0,message:"Secondary nodes updated successfully",secondary_nodes:e.secondary_nodes};break;case"force_sync":r=await q().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?(z().promote_to_primary(),r={success:!0,message:"Node promoted to primary successfully",role:"primary"}):r={success:!1,message:"Demoting primary to secondary requires server restart with updated configuration",role:"primary"};break;case"reload_sync_key":const l=z();if(!l.is_secondary)throw new Error("reload_sync_key can only be used on secondary nodes");await l.reload_api_key(),r={success:!0,message:"API_KEY reloaded successfully"};break;case"get_secondary_auth_status":const w=q().get_sync_status();r={secondary_count:w.secondary_count,auth_failures:w.stats.auth_failures,successful_syncs:w.stats.successful_syncs,failed_syncs:w.stats.failed_syncs,secondaries:w.secondaries};break;case"setup_initial_admin":if(!e.username||!e.password)throw new Error("username and password are required for setup_initial_admin operation");const A=y();Y(A);const p=await K(e.username,e.password,e.email);s.info("Setup initial admin result",{setup_result:p,success:p.success,ok:p.ok,error:p.error}),r={success:p.success,message:p.message,error:p.error,admin_user:p.admin_user};break;case"create_user":if(!e.username||!e.password)throw new Error("username and password are required for create_user operation");r=await W(e.username,e.password,e.email,e.role);break;case"get_user":if(!e.username)throw new Error("username is required for get_user operation");const E=await T(e.username);r=E?{found:!0,user:E}:{found:!1};break;case"update_user":if(!e.username)throw new Error("username is required for update_user operation");r=await J(e.username,e.updates||{});break;case"reset_user_password":if(!e.username||!e.new_password)throw new Error("username and new_password are required for reset_user_password operation");r=await B(e.username,e.new_password);break;case"delete_user":if(!e.username)throw new Error("username is required for delete_user operation");r=await I(e.username);break;case"list_users":const S=await L();r={users:S,total_users:S.length};break;case"get_user_auth_stats":r=await C();break;default:const U=await C();r={...me(),connections:o?.get_stats()||{},write_queue:M()?.get_stats()||{},authentication:{authenticated_clients:n?.size||0,...F(),...U},settings:(()=>{try{return{port:v().port||1983}}catch{return{port:1983}}})()}}const i=Date.now()-a;return fe(i),s.info("Admin operation completed",{admin_action:t||"default",duration_ms:i,status:"success"}),r}catch(r){const i=Date.now()-a;throw s.error("Admin operation failed",{admin_action:t||"default",duration_ms:i,status:"error",error:r.message}),r}};export{Be as default,fe as track_operation};
@@ -0,0 +1 @@
1
+ import"fs";import p from"bcrypt";import k from"crypto";import C from"./logger.js";const{create_context_logger:F}=C("user_auth_manager"),c=F();import{load_settings as x,has_settings as L}from"./load_settings.js";const S=12,b=60*1e3,v=5,M=1e3,N=2,I="_admin",U="_users";let i=null,l=new Map,d=new Map,n=null;const $=e=>{n=e},ne=()=>k.randomBytes(16).toString("hex"),O=()=>{try{if(!L())return null;const e=x();return i=e,e.authentication&&e.authentication.failed_attempts&&(l=new Map(Object.entries(e.authentication.failed_attempts))),e.authentication&&e.authentication.rate_limits&&(d=new Map(Object.entries(e.authentication.rate_limits))),c.info("Settings data loaded successfully from environment variable"),i}catch(e){throw c.error("Failed to load settings data",{error:e.message}),new Error(`Failed to load settings data: ${e.message}`)}},h=()=>{try{i||(i={port:1983,authentication:{}}),i.authentication||(i.authentication={}),i.authentication.failed_attempts=Object.fromEntries(l),i.authentication.rate_limits=Object.fromEntries(d),process.env.JOYSTICK_DB_SETTINGS=JSON.stringify(i),c.info("Settings data saved successfully to environment variable")}catch(e){if(c.error("Failed to save settings data",{error:e.message}),process.env.NODE_ENV!=="test")throw new Error(`Failed to save settings data: ${e.message}`)}},T=e=>process.env.NODE_ENV==="test"?!1:["127.0.0.1","::1","localhost"].includes(e),z=e=>e.remoteAddress||"127.0.0.1",A=e=>{if(T(e))return!1;const t=Date.now(),r=(l.get(e)||[]).filter(a=>t-a<b);if(r.length>=v){const a=d.get(e);if(a&&t<a.expires_at)return!0;const o=Math.min(M*Math.pow(N,Math.floor(r.length/v)),1800*1e3);return d.set(e,{expires_at:t+o,attempts:r.length}),h(),c.warn("IP rate limited",{ip:e,attempts:r.length,backoff_duration_ms:o}),!0}return!1},w=e=>{if(T(e))return;const t=Date.now(),s=l.get(e)||[];s.push(t);const r=s.filter(a=>t-a<b);l.set(e,r),c.warn("Failed authentication attempt recorded",{ip:e,total_recent_attempts:r.length}),h()},j=e=>{l.delete(e),d.delete(e),h()},g=e=>`${I}:${U}:${e}`,q=async(e,t,s="user")=>{const r=await p.hash(t,S),a=new Date().toISOString();return{username:e,password_hash:r,role:s,active:!0,created_at:a,last_login:null}},E=async(e,t,s,r="user")=>{try{if(!n)return{success:!1,error:"Storage engine not initialized"};if(!e||!t)return{success:!1,error:"Username and password are required"};if(typeof e!="string"||e.trim().length===0)return{success:!1,error:"Username is required and must be a non-empty string"};if(typeof t!="string"||t.length<6)return{success:!1,error:"Password is required and must be at least 6 characters"};if(r&&!["admin","user"].includes(r))return{success:!1,error:'Role must be either "admin" or "user"'};const a=g(e.trim().toLowerCase());if(await n.get(a))return{success:!1,error:"User already exists"};const u=await q(e.trim().toLowerCase(),t,r);await n.put(a,u),c.info("User created successfully",{username:u.username,role:u.role});const{password_hash:f,..._}=u;return{success:!0,message:"User created successfully",ok:1,user:_}}catch(a){return{success:!1,error:a.message}}},B=async e=>{try{if(!n)return{success:!1,error:"Storage engine not initialized"};if(!e)return{success:!1,error:"Username is required"};const t=g(e.trim().toLowerCase()),s=await n.get(t);if(!s)return{success:!1,error:"User not found"};const{password_hash:r,...a}=s;return{success:!0,user:a}}catch(t){return{success:!1,error:t.message}}},P=async(e,t)=>{try{if(!n)return{success:!1,error:"Storage engine not initialized"};if(!e)return{success:!1,error:"Username is required"};const s=g(e.trim().toLowerCase()),r=await n.get(s);if(!r)return{success:!1,error:`User '${e}' not found`};if(t.email&&!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t.email))return{success:!1,error:"Invalid email format"};if(t.role&&!["admin","user"].includes(t.role))return{success:!1,error:'Role must be either "admin" or "user"'};const a=["role","active","email"],o={};for(const[f,_]of Object.entries(t))a.includes(f)&&(o[f]=_);const u={...r,...o};return await n.put(s,u),c.info("User updated successfully",{username:e,updates:o}),{success:!0,message:"User updated successfully"}}catch(s){return{success:!1,error:s.message}}},R=async(e,t)=>{try{if(!n)return{success:!1,error:"Storage engine not initialized"};if(!t||typeof t!="string"||t.length<6)return{success:!1,error:"Password must be at least 6 characters long"};const s=g(e.trim().toLowerCase()),r=await n.get(s);if(!r)return{success:!1,error:`User '${e}' not found`};const a=await p.hash(t,S),o={...r,password_hash:a};return await n.put(s,o),c.info("User password reset successfully",{username:e}),{success:!0,message:"Password reset successfully"}}catch(s){return{success:!1,error:s.message}}},K=async e=>{try{if(!n)return{success:!1,error:"Storage engine not initialized"};const t=g(e.trim().toLowerCase());return await n.get(t)?(await n.del(t),c.info("User deleted successfully",{username:e}),{success:!0,message:"User deleted successfully"}):{success:!1,error:"User not found"}}catch(t){return{success:!1,error:t.message}}},y=async()=>{try{if(!n)return{success:!1,error:"Storage engine not initialized"};const e=[],t=`${I}:${U}:`;try{for(const{key:s,value:r}of n.getRange({start:t,end:t+"\xFF"}))if(r&&typeof r=="object"){const{password_hash:a,...o}=r;e.push(o)}}catch(s){return c.error("Failed to list users",{error:s.message}),{success:!1,error:s.message}}return{success:!0,users:e.sort((s,r)=>s.username.localeCompare(r.username))}}catch(e){return{success:!1,error:e.message}}},J=async(e,t,s)=>{try{if(!n)return{success:!1,error:"Storage engine not initialized"};if(!e||!t)return{success:!1,error:"Username and password are required"};if(A(s))return{success:!1,error:"Too many failed attempts"};const r=Date.now();try{const a=g(e.trim().toLowerCase()),o=await n.get(a);if(!o)return w(s),c.warn("Authentication failed - user not found",{username:e,ip:s}),{success:!1,error:"Invalid credentials"};if(!o.active)return w(s),c.warn("Authentication failed - user inactive",{username:e,ip:s}),{success:!1,error:"Account is disabled"};const u=await p.compare(t,o.password_hash),f=Date.now()-r,_=100;if(f<_&&await new Promise(m=>setTimeout(m,_-f)),u){j(s);const m={...o,last_login:new Date().toISOString()};await n.put(a,m),c.info("Authentication successful",{username:e,ip:s});const{password_hash:X,...D}=m;return{success:!0,user:D}}else return w(s),c.warn("Authentication failed - invalid password",{username:e,ip:s}),{success:!1,error:"Invalid credentials"}}catch(a){return w(s),c.error("Authentication error",{username:e,ip:s,error:a.message}),{success:!1,error:a.message}}}catch(r){return{success:!1,error:r.message}}},G=async(e,t,s=null)=>{try{if(!n)return{success:!1,error:"Storage engine not initialized"};if(!e||!t||s!==null&&!s)return{success:!1,error:"Username, password, and email are required"};if(t.length<6)return{success:!1,error:"Password must be at least 6 characters long"};if(s&&!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s))return{success:!1,error:"Invalid email format"};const r=await y();if(r.success&&r.users.length>0)return{success:!1,error:"Initial admin user already exists"};const a=await E(e,t,s||`${e}@localhost`,"admin");if(!a.success)return a;const o=new Date().toISOString();return i||O(),i||(i={port:1983,cluster:!0,worker_count:2,authentication:{},backup:{enabled:!1},replication:{enabled:!1,role:"primary"},auto_indexing:{enabled:!0,threshold:100},performance:{monitoring_enabled:!0,log_slow_queries:!0,slow_query_threshold_ms:1e3},logging:{level:"info",structured:!0}}),i.authentication={user_based:!0,created_at:o,last_updated:o,failed_attempts:{},rate_limits:{}},h(),c.info("Initial admin setup completed",{admin_username:a.username,created_at:o}),{success:!0,admin_user:a,message:"Initial admin user created successfully",ok:1}}catch(r){return{success:!1,error:r.message}}},Y=async()=>{const e=!!(i&&i.authentication&&i.authentication.user_based);let t=0;if(e){const s=await y();t=s.success?s.users.length:0}return{configured:e,user_based:!0,user_count:t,failed_attempts_count:l.size,rate_limited_ips:d.size,created_at:i?.authentication?.created_at||null,last_updated:i?.authentication?.last_updated||null}},V=()=>{try{O()}catch(e){c.warn("Could not load settings data on startup",{error:e.message})}},W=()=>{i=null,l=new Map,d=new Map,n=null,process.env.JOYSTICK_DB_SETTINGS&&delete process.env.JOYSTICK_DB_SETTINGS};export{E as create_user,K as delete_user,Y as get_auth_stats,z as get_client_ip,B as get_user,V as initialize_user_auth_manager,A as is_rate_limited,y as list_users,W as reset_auth_state,R as reset_user_password,$ as set_storage_engine,G as setup_initial_admin,P as update_user,J as verify_credentials};
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.2294",
5
- "canary_version": "0.0.0-canary.2293",
4
+ "version": "0.0.0-canary.2296",
5
+ "canary_version": "0.0.0-canary.2295",
6
6
  "description": "JoystickDB - A minimalist database server for the Joystick framework",
7
7
  "main": "./dist/server/index.js",
8
8
  "scripts": {
@@ -120,7 +120,7 @@ const calculate_reconnect_delay = (attempt_number, base_delay) => {
120
120
  const create_client_options = (options = {}) => ({
121
121
  host: options.host || 'localhost',
122
122
  port: options.port || 1983,
123
- password: options.password || null,
123
+ authentication: options.authentication || null,
124
124
  timeout: options.timeout || 5000,
125
125
  reconnect: options.reconnect !== false,
126
126
  max_reconnect_attempts: options.max_reconnect_attempts || 10,
@@ -208,7 +208,10 @@ const extract_error_message = (message) => {
208
208
  * @typedef {Object} ClientOptions
209
209
  * @property {string} [host='localhost'] - Server hostname
210
210
  * @property {number} [port=1983] - Server port
211
- * @property {string} [password] - Authentication password (required for authenticated connections)
211
+ * @property {string} [password] - Authentication password (deprecated, use authentication object)
212
+ * @property {Object} [authentication] - Authentication object with username and password
213
+ * @property {string} [authentication.username] - Username for authentication
214
+ * @property {string} [authentication.password] - Password for authentication
212
215
  * @property {number} [timeout=5000] - Request timeout in milliseconds
213
216
  * @property {boolean} [reconnect=true] - Enable automatic reconnection
214
217
  * @property {number} [max_reconnect_attempts=10] - Maximum reconnection attempts
@@ -230,10 +233,15 @@ class JoystickDBClient extends EventEmitter {
230
233
  constructor(options = {}) {
231
234
  super();
232
235
 
236
+ // Reject old password-only authentication format
237
+ if (options.password && typeof options.password === 'string' && !options.authentication) {
238
+ throw new Error('Authentication must be provided as an object with username and password. Use: { authentication: { username: "your_username", password: "your_password" } }');
239
+ }
240
+
233
241
  const client_options = create_client_options(options);
234
242
  this.host = client_options.host;
235
243
  this.port = client_options.port;
236
- this.password = client_options.password;
244
+ this.authentication = client_options.authentication;
237
245
  this.timeout = client_options.timeout;
238
246
  this.reconnect = client_options.reconnect;
239
247
  this.max_reconnect_attempts = client_options.max_reconnect_attempts;
@@ -309,7 +317,7 @@ class JoystickDBClient extends EventEmitter {
309
317
 
310
318
  this.emit('connect');
311
319
 
312
- if (this.password) {
320
+ if (this.authentication) {
313
321
  this.authenticate();
314
322
  } else {
315
323
  this.handle_authentication_complete();
@@ -342,15 +350,16 @@ class JoystickDBClient extends EventEmitter {
342
350
  }
343
351
 
344
352
  async authenticate() {
345
- if (!this.password) {
346
- this.emit('error', new Error('Password required for authentication. Provide password in client options: joystickdb.client({ password: "your_password" })'));
353
+ if (!this.authentication || !this.authentication.username || !this.authentication.password) {
354
+ this.emit('error', new Error('Authentication required. Provide authentication object in client options: joystickdb.client({ authentication: { username: "your_username", password: "your_password" } })'));
347
355
  this.disconnect();
348
356
  return;
349
357
  }
350
358
 
351
359
  try {
352
360
  const result = await this.send_request('authentication', {
353
- password: this.password
361
+ username: this.authentication.username,
362
+ password: this.authentication.password
354
363
  });
355
364
 
356
365
  if (result.ok === 1) {
@@ -608,6 +617,11 @@ class JoystickDBClient extends EventEmitter {
608
617
  async get_stats() {
609
618
  return this.send_request('admin', { admin_action: 'stats' });
610
619
  }
620
+
621
+ // NOTE: Generic Admin Operations.
622
+ async admin(action, data = {}) {
623
+ return this.send_request('admin', { admin_action: action, ...data });
624
+ }
611
625
  }
612
626
 
613
627
  /**
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import net from 'net';
10
+ import crypto from 'crypto';
10
11
  import { decode as decode_messagepack } from 'msgpackr';
11
12
  import op_types from './lib/op_types.js';
12
13
  import safe_json_parse from './lib/safe_json_parse.js';
@@ -26,6 +27,14 @@ import {
26
27
  initialize_auth_manager,
27
28
  reset_auth_state
28
29
  } from './lib/auth_manager.js';
30
+ import {
31
+ set_storage_engine,
32
+ verify_credentials,
33
+ setup_initial_admin,
34
+ initialize_user_auth_manager,
35
+ reset_auth_state as reset_user_auth_state,
36
+ get_auth_stats
37
+ } from './lib/user_auth_manager.js';
29
38
  import {
30
39
  initialize_api_key_manager,
31
40
  verify_user_password,
@@ -62,12 +71,20 @@ const authenticated_clients = new Set();
62
71
  let connection_manager = null;
63
72
 
64
73
  /**
65
- * Validates authentication request data.
74
+ * Generates a cryptographically secure random password.
75
+ * @returns {string} 32-character hexadecimal password
76
+ */
77
+ const generate_secure_password = () => {
78
+ return crypto.randomBytes(16).toString('hex');
79
+ };
80
+
81
+ /**
82
+ * Validates authentication request data structure.
66
83
  * @param {Object} data - Authentication data
67
- * @returns {boolean} True if data is valid
84
+ * @returns {boolean} True if data structure is valid
68
85
  */
69
86
  const validate_authentication_data = (data) => {
70
- return data && data.password;
87
+ return data && data.username && data.password;
71
88
  };
72
89
 
73
90
  /**
@@ -82,12 +99,14 @@ const create_authentication_error_response = (error_message) => ({
82
99
 
83
100
  /**
84
101
  * Creates authentication success response.
102
+ * @param {Object} user - User object with role information
85
103
  * @returns {Object} Success response object
86
104
  */
87
- const create_authentication_success_response = () => ({
105
+ const create_authentication_success_response = (user) => ({
88
106
  ok: 1,
89
107
  version: "1.0.0",
90
- message: 'Authentication successful'
108
+ message: 'Authentication successful',
109
+ role: user?.role || 'user'
91
110
  });
92
111
 
93
112
  /**
@@ -114,11 +133,11 @@ const send_response_to_socket = (socket, response) => {
114
133
  /**
115
134
  * Handles client authentication requests.
116
135
  * @param {net.Socket} socket - Client socket connection
117
- * @param {Object} data - Authentication data containing password
136
+ * @param {Object} data - Authentication data containing username and password
118
137
  */
119
138
  export const authentication = async (socket, data = {}) => {
120
139
  if (!validate_authentication_data(data)) {
121
- const response = create_authentication_error_response('Authentication operation requires password to be set in data.');
140
+ const response = create_authentication_error_response('Authentication requires username and password');
122
141
  send_response_and_close_socket(socket, response);
123
142
  return;
124
143
  }
@@ -132,19 +151,18 @@ export const authentication = async (socket, data = {}) => {
132
151
  return;
133
152
  }
134
153
 
135
- const is_valid = await verify_password(data.password, client_ip);
154
+ const result = await verify_credentials(data.username, data.password, client_ip);
136
155
 
137
- if (!is_valid) {
138
- const response = create_authentication_error_response('Authentication failed');
156
+ if (result.success) {
157
+ authenticated_clients.add(socket.id);
158
+ const auth_response = create_authentication_success_response(result.user);
159
+ send_response_to_socket(socket, auth_response);
160
+ } else {
161
+ const response = create_authentication_error_response(result.error || 'Authentication failed');
139
162
  send_response_and_close_socket(socket, response);
140
- return;
141
163
  }
142
-
143
- authenticated_clients.add(socket.id);
144
- const auth_response = create_authentication_success_response();
145
- send_response_to_socket(socket, auth_response);
146
164
  } catch (error) {
147
- const response = create_authentication_error_response(`Authentication error: ${error.message}`);
165
+ const response = create_authentication_error_response('Authentication failed');
148
166
  send_response_and_close_socket(socket, response);
149
167
  }
150
168
  };
@@ -171,13 +189,30 @@ const create_setup_error_response = (error_message) => ({
171
189
  });
172
190
 
173
191
  /**
174
- * Handles initial server setup, generating authentication password.
192
+ * Handles initial server setup, creating initial admin user.
175
193
  * @param {net.Socket} socket - Client socket connection
176
194
  * @param {Object} data - Setup data (currently unused)
177
195
  */
178
196
  export const setup = async (socket, data = {}) => {
179
197
  try {
180
- const password = setup_authentication();
198
+ // Check if authentication already configured
199
+ const auth_stats = await get_auth_stats();
200
+ if (auth_stats.configured) {
201
+ const response = create_setup_error_response('Authentication already configured');
202
+ send_response_to_socket(socket, response);
203
+ return;
204
+ }
205
+
206
+ // Setup initial admin user
207
+ const password = generate_secure_password();
208
+ const setup_result = await setup_initial_admin('admin', password, 'admin@localhost.local');
209
+
210
+ if (!setup_result.success) {
211
+ const response = create_setup_error_response(setup_result.error);
212
+ send_response_to_socket(socket, response);
213
+ return;
214
+ }
215
+
181
216
  const response = create_setup_success_response(password);
182
217
  send_response_to_socket(socket, response);
183
218
  } catch (error) {
@@ -319,8 +354,13 @@ const initialize_server_components = async (settings) => {
319
354
  const { tcp_port } = get_port_configuration();
320
355
  const database_path = settings?.data_path || `./.joystick/data/joystickdb_${tcp_port}`;
321
356
 
322
- initialize_database(database_path);
357
+ const db = initialize_database(database_path);
323
358
  initialize_auth_manager();
359
+
360
+ // NOTE: Initialize user authentication manager and set storage engine reference
361
+ initialize_user_auth_manager();
362
+ set_storage_engine(db);
363
+
324
364
  await initialize_api_key_manager();
325
365
  initialize_recovery_manager();
326
366
  };
@@ -673,6 +713,7 @@ const create_server_cleanup_function = () => {
673
713
  await cleanup_database();
674
714
 
675
715
  reset_auth_state();
716
+ reset_user_auth_state();
676
717
  reset_recovery_state();
677
718
  } catch (error) {
678
719
  // NOTE: Ignore cleanup errors in tests.
@@ -10,6 +10,17 @@ import { get_database } from '../query_engine.js';
10
10
  import { get_settings } from '../load_settings.js';
11
11
  import { get_write_queue } from '../write_queue.js';
12
12
  import { get_auth_stats } from '../auth_manager.js';
13
+ import {
14
+ create_user,
15
+ get_user,
16
+ update_user,
17
+ reset_user_password,
18
+ delete_user,
19
+ list_users,
20
+ setup_initial_admin,
21
+ get_auth_stats as get_user_auth_stats,
22
+ set_storage_engine
23
+ } from '../user_auth_manager.js';
13
24
  import {
14
25
  get_query_statistics,
15
26
  get_auto_index_statistics,
@@ -1051,15 +1062,93 @@ export default async (admin_action, data = {}, connection_manager, authenticated
1051
1062
  };
1052
1063
  break;
1053
1064
 
1054
- default:
1065
+ // NOTE: User Management Operations
1066
+ case 'setup_initial_admin':
1067
+ if (!data.username || !data.password) {
1068
+ throw new Error('username and password are required for setup_initial_admin operation');
1069
+ }
1070
+ // Ensure storage engine is set before calling setup_initial_admin
1071
+ const db = get_database();
1072
+ set_storage_engine(db);
1073
+
1074
+ const setup_result = await setup_initial_admin(data.username, data.password, data.email);
1075
+
1076
+ // Log the actual result for debugging
1077
+ log.info('Setup initial admin result', {
1078
+ setup_result,
1079
+ success: setup_result.success,
1080
+ ok: setup_result.ok,
1081
+ error: setup_result.error
1082
+ });
1083
+
1084
+ // Return the result - let dispatcher handle success/failure
1085
+ result = {
1086
+ success: setup_result.success,
1087
+ message: setup_result.message,
1088
+ error: setup_result.error,
1089
+ admin_user: setup_result.admin_user
1090
+ };
1091
+ break;
1092
+
1093
+ case 'create_user':
1094
+ if (!data.username || !data.password) {
1095
+ throw new Error('username and password are required for create_user operation');
1096
+ }
1097
+ result = await create_user(data.username, data.password, data.email, data.role);
1098
+ break;
1099
+
1100
+ case 'get_user':
1101
+ if (!data.username) {
1102
+ throw new Error('username is required for get_user operation');
1103
+ }
1104
+ const user = await get_user(data.username);
1105
+ result = user ? { found: true, user } : { found: false };
1106
+ break;
1107
+
1108
+ case 'update_user':
1109
+ if (!data.username) {
1110
+ throw new Error('username is required for update_user operation');
1111
+ }
1112
+ result = await update_user(data.username, data.updates || {});
1113
+ break;
1114
+
1115
+ case 'reset_user_password':
1116
+ if (!data.username || !data.new_password) {
1117
+ throw new Error('username and new_password are required for reset_user_password operation');
1118
+ }
1119
+ result = await reset_user_password(data.username, data.new_password);
1120
+ break;
1121
+
1122
+ case 'delete_user':
1123
+ if (!data.username) {
1124
+ throw new Error('username is required for delete_user operation');
1125
+ }
1126
+ result = await delete_user(data.username);
1127
+ break;
1128
+
1129
+ case 'list_users':
1130
+ const users = await list_users();
1131
+ result = {
1132
+ users,
1133
+ total_users: users.length
1134
+ };
1135
+ break;
1136
+
1137
+ case 'get_user_auth_stats':
1138
+ result = await get_user_auth_stats();
1139
+ break;
1140
+
1141
+ default:
1055
1142
  // Default admin action (backward compatibility)
1143
+ const user_auth_stats = await get_user_auth_stats();
1056
1144
  result = {
1057
1145
  ...get_enhanced_stats(),
1058
1146
  connections: connection_manager?.get_stats() || {},
1059
1147
  write_queue: get_write_queue()?.get_stats() || {},
1060
1148
  authentication: {
1061
1149
  authenticated_clients: authenticated_clients?.size || 0,
1062
- ...get_auth_stats()
1150
+ ...get_auth_stats(),
1151
+ ...user_auth_stats
1063
1152
  },
1064
1153
  settings: (() => {
1065
1154
  try {