@joystick.js/db-canary 0.0.0-canary.2227 → 0.0.0-canary.2229

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 u from"net";import{EventEmitter as h}from"events";import{encode as d,decode as l}from"msgpackr";import m from"./database.js";const p=n=>{const e=d(n,{useFloat32:!1,int64AsType:"number",mapsAsObjects:!0}),t=Buffer.allocUnsafe(4);return t.writeUInt32BE(e.length,0),Buffer.concat([t,e])},f=()=>{let n=Buffer.alloc(0),e=null;return{parse_messages:i=>{n=Buffer.concat([n,i]);const r=[];for(;n.length>0;){if(e===null){if(n.length<4)break;e=n.readUInt32BE(0),n=n.slice(4)}if(n.length<e)break;const a=n.slice(0,e);n=n.slice(e),e=null;try{const c=l(a,{useFloat32:!1,int64AsType:"number",mapsAsObjects:!0});r.push(c)}catch(c){throw new Error(`Invalid message format: ${c.message}`)}}return r},reset:()=>{n=Buffer.alloc(0),e=null}}};class _ extends h{constructor(e={}){super(),this.host=e.host||"localhost",this.port=e.port||1983,this.password=e.password||null,this.timeout=e.timeout||5e3,this.reconnect=e.reconnect!==!1,this.max_reconnect_attempts=e.max_reconnect_attempts||10,this.reconnect_delay=e.reconnect_delay||1e3,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=[],e.auto_connect!==!1&&this.connect()}connect(){if(this.is_connecting||this.is_connected)return;this.is_connecting=!0,this.socket=new u.Socket,this.message_parser=f();const e=setTimeout(()=>{this.socket&&!this.is_connected&&(this.socket.destroy(),this.handle_connection_error(new Error("Connection timeout")))},this.timeout);this.socket.connect(this.port,this.host,()=>{clearTimeout(e),this.is_connected=!0,this.is_connecting=!1,this.reconnect_attempts=0,this.emit("connect"),this.password?this.authenticate():(this.is_authenticated=!0,this.emit("authenticated"),this.process_request_queue())}),this.socket.on("data",t=>{try{const s=this.message_parser.parse_messages(t);for(const i of s)this.handle_message(i)}catch(s){this.emit("error",new Error(`Message parsing failed: ${s.message}`))}}),this.socket.on("error",t=>{clearTimeout(e),this.handle_connection_error(t)}),this.socket.on("close",()=>{clearTimeout(e),this.handle_disconnect()})}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.is_authenticated=!0,this.emit("authenticated"),this.process_request_queue();else throw new Error("Authentication failed")}catch(e){this.emit("error",new Error(`Authentication error: ${e.message}`)),this.disconnect()}}handle_message(e){if(this.pending_requests.size>0){const[t,{resolve:s,reject:i,timeout:r}]=this.pending_requests.entries().next().value;if(clearTimeout(r),this.pending_requests.delete(t),e.ok===1||e.ok===!0)s(e);else if(e.ok===0||e.ok===!1){const a=typeof e.error=="string"?e.error:JSON.stringify(e.error)||"Operation failed";i(new Error(a))}else s(e)}else this.emit("response",e)}handle_connection_error(e){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();for(const[t,{reject:s,timeout:i}]of this.pending_requests)clearTimeout(i),s(new Error("Connection lost"));this.pending_requests.clear(),this.emit("error",e),this.reconnect&&this.reconnect_attempts<this.max_reconnect_attempts?this.schedule_reconnect():this.emit("disconnect")}handle_disconnect(){this.is_connected=!1,this.is_authenticated=!1,this.is_connecting=!1,this.socket&&(this.socket.removeAllListeners(),this.socket=null),this.message_parser&&this.message_parser.reset();for(const[e,{reject:t,timeout:s}]of this.pending_requests)clearTimeout(s),t(new Error("Connection closed"));this.pending_requests.clear(),this.reconnect&&this.reconnect_attempts<this.max_reconnect_attempts?this.schedule_reconnect():this.emit("disconnect")}schedule_reconnect(){this.reconnect_attempts++;const e=Math.min(this.reconnect_delay*Math.pow(2,this.reconnect_attempts-1),3e4);this.emit("reconnecting",{attempt:this.reconnect_attempts,delay:e}),this.reconnect_timeout=setTimeout(()=>{this.connect()},e)}send_request(e,t={},s=!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.is_connected||e!=="authentication"&&e!=="setup"&&e!=="ping"&&!this.is_authenticated)if(s){this.request_queue.push(o);return}else{r(new Error("Not connected or authenticated"));return}this.send_request_now(o)})}send_request_now(e){const{message:t,resolve:s,reject:i,request_id:r}=e,a=setTimeout(()=>{this.pending_requests.delete(r),i(new Error("Request timeout"))},this.timeout);this.pending_requests.set(r,{resolve:s,reject:i,timeout:a});try{const c=p(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}db(e){return new m(this,e)}async list_databases(){return this.send_request("admin",{admin_action:"list_databases"})}}class g{constructor(e,t,s){this.client=e,this.database_name=t,this.collection_name=s}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})).result}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 update_one(e,t,s={}){return this.client.send_request("update_one",{database:this.database_name,collection:this.collection_name,filter:e,update:t,options:s})}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 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})}}_.Collection=g;const q={client:n=>new _(n)};var x=q;export{x as default};
1
+ import u from"net";import{EventEmitter as h}from"events";import{encode as d,decode as l}from"msgpackr";import m from"./database.js";const p=n=>{const e=d(n,{useFloat32:!1,int64AsType:"number",mapsAsObjects:!0}),t=Buffer.allocUnsafe(4);return t.writeUInt32BE(e.length,0),Buffer.concat([t,e])},f=()=>{let n=Buffer.alloc(0),e=null;return{parse_messages:i=>{n=Buffer.concat([n,i]);const r=[];for(;n.length>0;){if(e===null){if(n.length<4)break;e=n.readUInt32BE(0),n=n.slice(4)}if(n.length<e)break;const a=n.slice(0,e);n=n.slice(e),e=null;try{const c=l(a,{useFloat32:!1,int64AsType:"number",mapsAsObjects:!0});r.push(c)}catch(c){throw new Error(`Invalid message format: ${c.message}`)}}return r},reset:()=>{n=Buffer.alloc(0),e=null}}};class _ extends h{constructor(e={}){super(),this.host=e.host||"localhost",this.port=e.port||1983,this.password=e.password||null,this.timeout=e.timeout||5e3,this.reconnect=e.reconnect!==!1,this.max_reconnect_attempts=e.max_reconnect_attempts||10,this.reconnect_delay=e.reconnect_delay||1e3,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=[],e.auto_connect!==!1&&this.connect()}connect(){if(this.is_connecting||this.is_connected)return;this.is_connecting=!0,this.socket=new u.Socket,this.message_parser=f();const e=setTimeout(()=>{this.socket&&!this.is_connected&&(this.socket.destroy(),this.handle_connection_error(new Error("Connection timeout")))},this.timeout);this.socket.connect(this.port,this.host,()=>{clearTimeout(e),this.is_connected=!0,this.is_connecting=!1,this.reconnect_attempts=0,this.emit("connect"),this.password?this.authenticate():(this.is_authenticated=!0,this.emit("authenticated"),this.process_request_queue())}),this.socket.on("data",t=>{try{const s=this.message_parser.parse_messages(t);for(const i of s)this.handle_message(i)}catch(s){this.emit("error",new Error(`Message parsing failed: ${s.message}`))}}),this.socket.on("error",t=>{clearTimeout(e),this.handle_connection_error(t)}),this.socket.on("close",()=>{clearTimeout(e),this.handle_disconnect()})}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.is_authenticated=!0,this.emit("authenticated"),this.process_request_queue();else throw new Error("Authentication failed")}catch(e){this.emit("error",new Error(`Authentication error: ${e.message}`)),this.disconnect()}}handle_message(e){if(this.pending_requests.size>0){const[t,{resolve:s,reject:i,timeout:r}]=this.pending_requests.entries().next().value;if(clearTimeout(r),this.pending_requests.delete(t),e.ok===1||e.ok===!0)s(e);else if(e.ok===0||e.ok===!1){const a=typeof e.error=="string"?e.error:JSON.stringify(e.error)||"Operation failed";i(new Error(a))}else s(e)}else this.emit("response",e)}handle_connection_error(e){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();for(const[t,{reject:s,timeout:i}]of this.pending_requests)clearTimeout(i),s(new Error("Connection lost"));this.pending_requests.clear(),this.emit("error",e),this.reconnect&&this.reconnect_attempts<this.max_reconnect_attempts?this.schedule_reconnect():this.emit("disconnect")}handle_disconnect(){this.is_connected=!1,this.is_authenticated=!1,this.is_connecting=!1,this.socket&&(this.socket.removeAllListeners(),this.socket=null),this.message_parser&&this.message_parser.reset();for(const[e,{reject:t,timeout:s}]of this.pending_requests)clearTimeout(s),t(new Error("Connection closed"));this.pending_requests.clear(),this.reconnect&&this.reconnect_attempts<this.max_reconnect_attempts?this.schedule_reconnect():this.emit("disconnect")}schedule_reconnect(){this.reconnect_attempts++;const e=Math.min(this.reconnect_delay*Math.pow(2,this.reconnect_attempts-1),3e4);this.emit("reconnecting",{attempt:this.reconnect_attempts,delay:e}),this.reconnect_timeout=setTimeout(()=>{this.connect()},e)}send_request(e,t={},s=!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.is_connected||e!=="authentication"&&e!=="setup"&&e!=="ping"&&!this.is_authenticated)if(s){this.request_queue.push(o);return}else{r(new Error("Not connected or authenticated"));return}this.send_request_now(o)})}send_request_now(e){const{message:t,resolve:s,reject:i,request_id:r}=e,a=setTimeout(()=>{this.pending_requests.delete(r),i(new Error("Request timeout"))},this.timeout);this.pending_requests.set(r,{resolve:s,reject:i,timeout:a});try{const c=p(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}db(e){return new m(this,e)}async list_databases(){return this.send_request("admin",{admin_action:"list_databases"})}}class g{constructor(e,t,s){this.client=e,this.database_name=t,this.collection_name=s}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 update_one(e,t,s={}){return this.client.send_request("update_one",{database:this.database_name,collection:this.collection_name,filter:e,update:t,options:s})}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 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})}}_.Collection=g;const q={client:n=>new _(n)};var x=q;export{x as default};
@@ -1 +1 @@
1
- import h from"net";import p from"../lib/op_types.js";import{send_success as u,send_error as d,send_message as l}from"../lib/send_response.js";import{shutdown_write_queue as g}from"../lib/write_queue.js";import{create_message_parser as w,encode_message as c}from"../lib/tcp_protocol.js";import m from"../lib/logger.js";import{initialize_database as f,cleanup_database as y}from"../lib/query_engine.js";import{handle_admin_operation as b,handle_ping_operation as v}from"../lib/operation_dispatcher.js";import{get_settings as k}from"../lib/load_settings.js";import{is_development_mode as $}from"../lib/development_mode.js";class S{constructor(){this.server=null,this.connections=new Map,this.connection_count=0,this.settings=null,this.port=null,this.write_id_counter=0,this.pending_writes=new Map,this.authenticated_clients=new Set,this.heartbeat_interval=null;const{create_context_logger:e}=m("worker");this.log=e({worker_pid:process.pid}),this.setup_worker()}setup_worker(){process.on("message",e=>{this.handle_master_message(e)}),process.on("SIGTERM",()=>{this.shutdown()}),process.on("SIGINT",()=>{this.shutdown()}),this.send_heartbeat(),this.heartbeat_interval=setInterval(()=>{this.send_heartbeat()},5e3),process.connected&&process.send({type:"worker_ready"})}handle_master_message(e){switch(e.type){case"config":this.handle_config(e);break;case"write_response":this.handle_write_response(e);break;case"auth_response":this.handle_auth_response(e);break;case"setup_response":this.handle_setup_response(e);break;case"write_notification":this.handle_write_notification(e);break;case"shutdown":this.shutdown();break;default:this.log.warn("Unknown message type received from master",{message_type:e.type})}}handle_config(e){const t=e.data.master_id;if(this.master_id&&this.master_id!==t){this.log.info("Worker already configured by different master, ignoring config message",{current_master_id:this.master_id,incoming_master_id:t,current_port:this.port,new_port:e.data.port});return}if(this.port!==null&&this.master_id===t){this.log.info("Worker already configured by same master, ignoring duplicate config message",{master_id:t,current_port:this.port,new_port:e.data.port});return}this.log.info("Received config message",{port:e.data.port,master_id:t}),this.port=e.data.port,this.settings=e.data.settings,this.master_id=t;try{let s="./data";try{const r=k();r?.data_path&&(s=r.data_path)}catch{}f(s),this.log.info("Database initialized in worker process",{database_path:s})}catch(s){this.log.error("Failed to initialize database in worker process",{error:s.message})}this.log.info("Starting server",{port:this.port}),this.start_server()}start_server(){this.server=h.createServer(e=>{this.handle_connection(e)}),this.server.listen(this.port,()=>{this.log.info("Server listening",{port:this.port}),process.connected&&process.send({type:"server_ready"})}),this.server.on("error",e=>{this.log.error("Server error",{error:e.message})})}handle_connection(e){const t=`${process.pid}_${Date.now()}_${Math.random()}`;e.id=t,e.message_parser=w(),this.connections.set(t,e),this.connection_count++,this.update_connection_count(),e.on("data",s=>{this.handle_socket_data(e,s)}),e.on("end",()=>{this.handle_socket_end(e)}),e.on("error",s=>{this.log.error("Socket error",{socket_id:t,error_message:s.message}),this.handle_socket_end(e)})}handle_socket_data(e,t){try{const s=e.message_parser.parse_messages(t);for(const r of s){const a=r,i=a?.op||null;if(!i){d(e,{message:"Missing operation type"});continue}if(!this.check_op_type(i)){d(e,{message:"Invalid operation type"});continue}this.route_operation(e,i,a?.data||{})}}catch(s){this.log.error("Data parsing error",{socket_id:e.id,error_message:s.message}),d(e,{message:"Invalid data format"})}}handle_socket_end(e){e.id&&(this.connections.delete(e.id),this.authenticated_clients.delete(e.id),this.connection_count--,this.update_connection_count()),this.log.info("Client disconnected",{socket_id:e.id})}check_op_type(e=""){return e?p.includes(e):!1}route_operation(e,t,s){switch(t){case"authentication":this.handle_authentication(e,s);break;case"setup":this.handle_setup(e,s);break;case"find_one":case"find":case"get_indexes":this.handle_read_operation(e,t,s);break;case"create_index":case"drop_index":this.handle_write_operation(e,t,s);break;case"insert_one":case"update_one":case"delete_one":case"bulk_write":this.handle_write_operation(e,t,s);break;case"ping":v(e);break;case"admin":b(e,s,this.is_authenticated.bind(this));break;default:d(e,{message:`Unsupported operation: ${t}`})}}handle_authentication(e,t){if(this.is_authenticated(e))l(e,"Already authenticated");else{const s=`${e.id}_${Date.now()}`;process.send({type:"auth_request",data:{auth_id:s,socket_id:e.id,password:t.password}}),this.pending_writes.set(s,{socket:e,type:"auth"})}}handle_setup(e,t){const s=`${e.id}_${Date.now()}`;process.send({type:"setup_request",data:{setup_id:s,socket_id:e.id}}),this.pending_writes.set(s,{socket:e,type:"setup"})}handle_read_operation(e,t,s){if(!this.is_authenticated(e)){d(e,{message:"Authentication required"});return}const r=`${e.id}_${++this.write_id_counter}`;process.send({type:"write_request",data:{write_id:r,op_type:t,data:s,socket_id:e.id}}),this.pending_writes.set(r,{socket:e,type:"read"})}handle_write_operation(e,t,s){if(!this.is_authenticated(e)){d(e,{message:"Authentication required"});return}const r=`${e.id}_${++this.write_id_counter}`;process.send({type:"write_request",data:{write_id:r,op_type:t,data:s,socket_id:e.id}}),this.pending_writes.set(r,{socket:e,type:"write"})}handle_write_response(e){const{write_id:t,success:s,result:r,error:a}=e.data,i=this.pending_writes.get(t);if(!i){this.log.warn("No pending write found",{write_id:t});return}const{socket:n}=i;if(this.pending_writes.delete(t),n.destroyed||!n.writable){this.log.warn("Socket disconnected before response could be sent",{write_id:t});return}try{if(s){let o;Array.isArray(r)?o={ok:1,documents:r}:o={ok:1,result:r};const _=c(o);n.write(_)}else{const _=c({ok:0,error:a});n.write(_)}}catch(o){this.log.error("Error sending response to client",{write_id:t,error:o.message})}}handle_auth_response(e){const{auth_id:t,success:s,message:r}=e.data,a=this.pending_writes.get(t);if(!a){this.log.warn("No pending auth found",{auth_id:t});return}const{socket:i}=a;if(this.pending_writes.delete(t),i.destroyed||!i.writable){this.log.warn("Socket disconnected before auth response could be sent",{auth_id:t});return}try{if(s){this.authenticated_clients.add(i.id);const o=c({ok:1,version:"1.0.0",message:r});i.write(o)}else d(i,{message:r}),i.end()}catch(n){this.log.error("Error sending auth response to client",{auth_id:t,error:n.message})}}handle_setup_response(e){const{setup_id:t,success:s,password:r,message:a,error:i}=e.data,n=this.pending_writes.get(t);if(!n){this.log.warn("No pending setup found",{setup_id:t});return}const{socket:o}=n;this.pending_writes.delete(t),s?u(o,{password:r,message:a}):d(o,{message:i})}handle_write_notification(e){this.log.info("Received write notification",{op_type:e.data.op_type,timestamp:e.data.timestamp})}is_authenticated(e){return $()?!0:this.authenticated_clients.has(e.id)}update_connection_count(){process.connected&&process.send({type:"connection_count",data:{count:this.connection_count}})}send_heartbeat(){if(process.connected)try{process.send({type:"heartbeat",data:{timestamp:Date.now()}})}catch{clearInterval(this.heartbeat_interval)}}async shutdown(){const e=Date.now();this.log.info("Initiating graceful shutdown");try{await g(),this.log.info("Write queue shutdown complete")}catch(s){this.log.error("Error shutting down write queue",{error:s.message})}try{await y(),this.log.info("Database cleanup complete")}catch(s){this.log.error("Error cleaning up database",{error:s.message})}this.server&&this.server.close(()=>{this.log.info("Server closed")});for(const[s,r]of this.connections)r.end();const t=process.env.NODE_ENV==="test"?100:5e3;setTimeout(()=>{const s=Date.now()-e;this.log.info("Worker shutdown complete",{shutdown_duration_ms:s}),process.exit(0)},t)}}const T=new S;
1
+ import p from"net";import u from"../lib/op_types.js";import{send_success as l,send_error as a,send_message as g}from"../lib/send_response.js";import{shutdown_write_queue as w}from"../lib/write_queue.js";import{create_message_parser as m,encode_message as h}from"../lib/tcp_protocol.js";import f from"../lib/logger.js";import{initialize_database as y,cleanup_database as b}from"../lib/query_engine.js";import{handle_admin_operation as v,handle_ping_operation as k}from"../lib/operation_dispatcher.js";import{get_settings as $}from"../lib/load_settings.js";import{is_development_mode as S}from"../lib/development_mode.js";class D{constructor(){this.server=null,this.connections=new Map,this.connection_count=0,this.settings=null,this.port=null,this.write_id_counter=0,this.pending_writes=new Map,this.authenticated_clients=new Set,this.heartbeat_interval=null;const{create_context_logger:e}=f("worker");this.log=e({worker_pid:process.pid}),this.setup_worker()}setup_worker(){process.on("message",e=>{this.handle_master_message(e)}),process.on("SIGTERM",()=>{this.shutdown()}),process.on("SIGINT",()=>{this.shutdown()}),this.send_heartbeat(),this.heartbeat_interval=setInterval(()=>{this.send_heartbeat()},5e3),process.connected&&process.send({type:"worker_ready"})}handle_master_message(e){switch(e.type){case"config":this.handle_config(e);break;case"write_response":this.handle_write_response(e);break;case"auth_response":this.handle_auth_response(e);break;case"setup_response":this.handle_setup_response(e);break;case"write_notification":this.handle_write_notification(e);break;case"shutdown":this.shutdown();break;default:this.log.warn("Unknown message type received from master",{message_type:e.type})}}handle_config(e){const t=e.data.master_id;if(this.master_id&&this.master_id!==t){this.log.info("Worker already configured by different master, ignoring config message",{current_master_id:this.master_id,incoming_master_id:t,current_port:this.port,new_port:e.data.port});return}if(this.port!==null&&this.master_id===t){this.log.info("Worker already configured by same master, ignoring duplicate config message",{master_id:t,current_port:this.port,new_port:e.data.port});return}this.log.info("Received config message",{port:e.data.port,master_id:t}),this.port=e.data.port,this.settings=e.data.settings,this.master_id=t;try{let s="./data";try{const r=$();r?.data_path&&(s=r.data_path)}catch{}y(s),this.log.info("Database initialized in worker process",{database_path:s})}catch(s){this.log.error("Failed to initialize database in worker process",{error:s.message})}this.log.info("Starting server",{port:this.port}),this.start_server()}start_server(){this.server=p.createServer(e=>{this.handle_connection(e)}),this.server.listen(this.port,()=>{this.log.info("Server listening",{port:this.port}),process.connected&&process.send({type:"server_ready"})}),this.server.on("error",e=>{this.log.error("Server error",{error:e.message})})}handle_connection(e){const t=`${process.pid}_${Date.now()}_${Math.random()}`;e.id=t,e.message_parser=m(),this.connections.set(t,e),this.connection_count++,this.update_connection_count(),e.on("data",s=>{this.handle_socket_data(e,s)}),e.on("end",()=>{this.handle_socket_end(e)}),e.on("error",s=>{this.log.error("Socket error",{socket_id:t,error_message:s.message}),this.handle_socket_end(e)})}handle_socket_data(e,t){try{const s=e.message_parser.parse_messages(t);for(const r of s){const o=r,i=o?.op||null;if(!i){a(e,{message:"Missing operation type"});continue}if(!this.check_op_type(i)){a(e,{message:"Invalid operation type"});continue}this.route_operation(e,i,o?.data||{})}}catch(s){this.log.error("Data parsing error",{socket_id:e.id,error_message:s.message}),a(e,{message:"Invalid data format"})}}handle_socket_end(e){e.id&&(this.connections.delete(e.id),this.authenticated_clients.delete(e.id),this.connection_count--,this.update_connection_count()),this.log.info("Client disconnected",{socket_id:e.id})}check_op_type(e=""){return e?u.includes(e):!1}route_operation(e,t,s){switch(t){case"authentication":this.handle_authentication(e,s);break;case"setup":this.handle_setup(e,s);break;case"find_one":case"find":case"get_indexes":this.handle_read_operation(e,t,s);break;case"create_index":case"drop_index":this.handle_write_operation(e,t,s);break;case"insert_one":case"update_one":case"delete_one":case"bulk_write":this.handle_write_operation(e,t,s);break;case"ping":k(e);break;case"admin":v(e,s,this.is_authenticated.bind(this));break;default:a(e,{message:`Unsupported operation: ${t}`})}}handle_authentication(e,t){if(this.is_authenticated(e))g(e,"Already authenticated");else{const s=`${e.id}_${Date.now()}`;process.send({type:"auth_request",data:{auth_id:s,socket_id:e.id,password:t.password}}),this.pending_writes.set(s,{socket:e,type:"auth"})}}handle_setup(e,t){const s=`${e.id}_${Date.now()}`;process.send({type:"setup_request",data:{setup_id:s,socket_id:e.id}}),this.pending_writes.set(s,{socket:e,type:"setup"})}handle_read_operation(e,t,s){if(!this.is_authenticated(e)){a(e,{message:"Authentication required"});return}const r=`${e.id}_${++this.write_id_counter}`;process.send({type:"write_request",data:{write_id:r,op_type:t,data:s,socket_id:e.id}}),this.pending_writes.set(r,{socket:e,type:"read",op_type:t})}handle_write_operation(e,t,s){if(!this.is_authenticated(e)){a(e,{message:"Authentication required"});return}const r=`${e.id}_${++this.write_id_counter}`;process.send({type:"write_request",data:{write_id:r,op_type:t,data:s,socket_id:e.id}}),this.pending_writes.set(r,{socket:e,type:"write",op_type:t})}handle_write_response(e){const{write_id:t,success:s,result:r,error:o}=e.data,i=this.pending_writes.get(t);if(!i){this.log.warn("No pending write found",{write_id:t});return}const{socket:n,op_type:d}=i;if(this.pending_writes.delete(t),n.destroyed||!n.writable){this.log.warn("Socket disconnected before response could be sent",{write_id:t});return}try{if(s){let _;d==="find"?_={ok:1,documents:r}:d==="find_one"?_={ok:1,document:r}:_={ok:1,result:r};const c=h(_);n.write(c)}else{const c=h({ok:0,error:o});n.write(c)}}catch(_){this.log.error("Error sending response to client",{write_id:t,error:_.message})}}handle_auth_response(e){const{auth_id:t,success:s,message:r}=e.data,o=this.pending_writes.get(t);if(!o){this.log.warn("No pending auth found",{auth_id:t});return}const{socket:i}=o;if(this.pending_writes.delete(t),i.destroyed||!i.writable){this.log.warn("Socket disconnected before auth response could be sent",{auth_id:t});return}try{if(s){this.authenticated_clients.add(i.id);const d=h({ok:1,version:"1.0.0",message:r});i.write(d)}else a(i,{message:r}),i.end()}catch(n){this.log.error("Error sending auth response to client",{auth_id:t,error:n.message})}}handle_setup_response(e){const{setup_id:t,success:s,password:r,message:o,error:i}=e.data,n=this.pending_writes.get(t);if(!n){this.log.warn("No pending setup found",{setup_id:t});return}const{socket:d}=n;this.pending_writes.delete(t),s?l(d,{password:r,message:o}):a(d,{message:i})}handle_write_notification(e){this.log.info("Received write notification",{op_type:e.data.op_type,timestamp:e.data.timestamp})}is_authenticated(e){return S()?!0:this.authenticated_clients.has(e.id)}update_connection_count(){process.connected&&process.send({type:"connection_count",data:{count:this.connection_count}})}send_heartbeat(){if(process.connected)try{process.send({type:"heartbeat",data:{timestamp:Date.now()}})}catch{clearInterval(this.heartbeat_interval)}}async shutdown(){const e=Date.now();this.log.info("Initiating graceful shutdown");try{await w(),this.log.info("Write queue shutdown complete")}catch(s){this.log.error("Error shutting down write queue",{error:s.message})}try{await b(),this.log.info("Database cleanup complete")}catch(s){this.log.error("Error cleaning up database",{error:s.message})}this.server&&this.server.close(()=>{this.log.info("Server closed")});for(const[s,r]of this.connections)r.end();const t=process.env.NODE_ENV==="test"?100:5e3;setTimeout(()=>{const s=Date.now()-e;this.log.info("Worker shutdown complete",{shutdown_duration_ms:s}),process.exit(0)},t)}}const C=new D;
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.2227",
5
- "canary_version": "0.0.0-canary.2226",
4
+ "version": "0.0.0-canary.2229",
5
+ "canary_version": "0.0.0-canary.2228",
6
6
  "description": "JoystickDB - A minimalist database server for the Joystick framework",
7
7
  "main": "./dist/server/index.js",
8
8
  "scripts": {
@@ -617,7 +617,7 @@ class Collection {
617
617
  filter,
618
618
  options
619
619
  });
620
- return result.result;
620
+ return result.document;
621
621
  }
622
622
 
623
623
  /**
@@ -338,7 +338,7 @@ class ClusterWorker {
338
338
  }
339
339
  });
340
340
 
341
- this.pending_writes.set(read_id, { socket, type: 'read' });
341
+ this.pending_writes.set(read_id, { socket, type: 'read', op_type });
342
342
  }
343
343
 
344
344
  handle_write_operation(socket, op_type, data) {
@@ -359,7 +359,7 @@ class ClusterWorker {
359
359
  }
360
360
  });
361
361
 
362
- this.pending_writes.set(write_id, { socket, type: 'write' });
362
+ this.pending_writes.set(write_id, { socket, type: 'write', op_type });
363
363
  }
364
364
 
365
365
 
@@ -374,7 +374,7 @@ class ClusterWorker {
374
374
  return;
375
375
  }
376
376
 
377
- const { socket } = pending_write;
377
+ const { socket, op_type } = pending_write;
378
378
  this.pending_writes.delete(write_id);
379
379
 
380
380
  // NOTE: Check if socket is still connected before sending response.
@@ -385,13 +385,13 @@ class ClusterWorker {
385
385
 
386
386
  try {
387
387
  if (success) {
388
- // NOTE: Handle different result types properly
388
+ // NOTE: Handle different result types properly based on operation type
389
389
  let response;
390
- if (Array.isArray(result)) {
391
- // For find operations that return arrays directly
390
+ if (op_type === 'find') {
392
391
  response = { ok: 1, documents: result };
392
+ } else if (op_type === 'find_one') {
393
+ response = { ok: 1, document: result };
393
394
  } else {
394
- // For all other operations (including find_one), wrap result properly
395
395
  response = { ok: 1, result };
396
396
  }
397
397
  const encoded_response = encode_message(response);