@joystick.js/db-canary 0.0.0-canary.2264 โ†’ 0.0.0-canary.2266

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 p from"net";import u from"../lib/op_types.js";import{send_success as l,send_error as o,send_message as g}from"../lib/send_response.js";import{shutdown_write_queue as m}from"../lib/write_queue.js";import{create_message_parser as w,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=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)})}process_single_message(e,t){const s=t?.op||null;return s?this.check_op_type(s)?(this.route_operation(e,s,t?.data||{}),!0):(o(e,{message:"Invalid operation type"}),!1):(o(e,{message:"Missing operation type"}),!1)}handle_socket_data(e,t){try{const s=e.message_parser.parse_messages(t);for(const r of s)this.process_single_message(e,r)}catch(s){this.log.error("Data parsing error",{socket_id:e.id,error_message:s.message}),o(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"delete_many":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:o(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)){o(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)){o(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:_}=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:a}=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;a==="find_one"?d={ok:1,document:r}:a==="find"?d={ok:1,documents:r}:d={ok:1,...r};const c=h(d);n.write(c)}else{const c=h({ok:0,error:_});n.write(c)}}catch(d){this.log.error("Error sending response to client",{write_id:t,error:d.message})}}handle_auth_response(e){const{auth_id:t,success:s,message:r}=e.data,_=this.pending_writes.get(t);if(!_){this.log.warn("No pending auth found",{auth_id:t});return}const{socket:i}=_;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 a=h({ok:1,version:"1.0.0",message:r});i.write(a)}else o(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:_,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:a}=n;this.pending_writes.delete(t),s?l(a,{password:r,message:_}):o(a,{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 m(),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;
1
+ import p from"net";import u from"../lib/op_types.js";import{send_success as l,send_error as o,send_message as g}from"../lib/send_response.js";import{shutdown_write_queue as m}from"../lib/write_queue.js";import{create_message_parser as w,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=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)})}process_single_message(e,t){const s=t?.op||null;return s?this.check_op_type(s)?(this.route_operation(e,s,t?.data||{}),!0):(o(e,{message:"Invalid operation type"}),!1):(o(e,{message:"Missing operation type"}),!1)}handle_socket_data(e,t){try{const s=e.message_parser.parse_messages(t);for(const r of s)this.process_single_message(e,r)}catch(s){this.log.error("Data parsing error",{socket_id:e.id,error_message:s.message}),o(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"count_documents":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"delete_many":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:o(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)){o(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)){o(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:_}=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:a}=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;a==="find_one"?d={ok:1,document:r}:a==="find"?d={ok:1,documents:r}:d={ok:1,...r};const c=h(d);n.write(c)}else{const c=h({ok:0,error:_});n.write(c)}}catch(d){this.log.error("Error sending response to client",{write_id:t,error:d.message})}}handle_auth_response(e){const{auth_id:t,success:s,message:r}=e.data,_=this.pending_writes.get(t);if(!_){this.log.warn("No pending auth found",{auth_id:t});return}const{socket:i}=_;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 a=h({ok:1,version:"1.0.0",message:r});i.write(a)}else o(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:_,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:a}=n;this.pending_writes.delete(t),s?l(a,{password:r,message:_}):o(a,{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 m(),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.2264",
5
- "canary_version": "0.0.0-canary.2263",
4
+ "version": "0.0.0-canary.2266",
5
+ "canary_version": "0.0.0-canary.2265",
6
6
  "description": "JoystickDB - A minimalist database server for the Joystick framework",
7
7
  "main": "./dist/server/index.js",
8
8
  "scripts": {
@@ -266,6 +266,7 @@ class ClusterWorker {
266
266
  break;
267
267
  case 'find_one':
268
268
  case 'find':
269
+ case 'count_documents':
269
270
  case 'get_indexes':
270
271
  // NOTE: Route read operations through master for consistency.
271
272
  this.handle_read_operation(socket, op_type, data);
@@ -1,81 +0,0 @@
1
- /**
2
- * Simple integration test for count_documents operation
3
- * This demonstrates the count_documents functionality working end-to-end
4
- */
5
-
6
- import joystickdb from './src/client/index.js';
7
-
8
- const test_count_documents = async () => {
9
- console.log('๐Ÿš€ Testing count_documents integration...\n');
10
-
11
- // Create client (in development mode, no password needed)
12
- const client = joystickdb.client({ host: 'localhost', port: 1983 });
13
-
14
- try {
15
- // Wait for connection
16
- await new Promise(resolve => {
17
- client.on('authenticated', resolve);
18
- client.on('connect', resolve); // In dev mode, no auth needed
19
- });
20
-
21
- console.log('โœ… Connected to JoystickDB');
22
-
23
- // Get collection reference
24
- const users = client.db('test').collection('users');
25
-
26
- // Clean up any existing data
27
- await users.delete_many({});
28
- console.log('๐Ÿงน Cleaned up existing data');
29
-
30
- // Insert test documents
31
- await users.insert_one({ name: 'Alice', age: 25, active: true });
32
- await users.insert_one({ name: 'Bob', age: 30, active: true });
33
- await users.insert_one({ name: 'Carol', age: 35, active: false });
34
- await users.insert_one({ name: 'Dave', age: 25, active: true });
35
- console.log('๐Ÿ“ Inserted 4 test documents');
36
-
37
- // Test 1: Count all documents
38
- const total_count = await users.count_documents();
39
- console.log(`๐Ÿ“Š Total documents: ${total_count}`);
40
- console.assert(total_count === 4, 'Expected 4 total documents');
41
-
42
- // Test 2: Count with filter
43
- const active_count = await users.count_documents({ active: true });
44
- console.log(`๐Ÿ“Š Active users: ${active_count}`);
45
- console.assert(active_count === 3, 'Expected 3 active users');
46
-
47
- // Test 3: Count with complex filter
48
- const young_active_count = await users.count_documents({
49
- age: { $lte: 30 },
50
- active: true
51
- });
52
- console.log(`๐Ÿ“Š Young active users (age <= 30): ${young_active_count}`);
53
- console.assert(young_active_count === 2, 'Expected 2 young active users');
54
-
55
- // Test 4: Count with limit
56
- const limited_count = await users.count_documents({}, { limit: 2 });
57
- console.log(`๐Ÿ“Š Limited count (max 2): ${limited_count}`);
58
- console.assert(limited_count === 2, 'Expected count to be limited to 2');
59
-
60
- // Test 5: Count with no matches
61
- const no_match_count = await users.count_documents({ age: 100 });
62
- console.log(`๐Ÿ“Š Users aged 100: ${no_match_count}`);
63
- console.assert(no_match_count === 0, 'Expected 0 users aged 100');
64
-
65
- // Clean up
66
- await users.delete_many({});
67
- console.log('๐Ÿงน Cleaned up test data');
68
-
69
- console.log('\nโœ… All count_documents tests passed!');
70
- console.log('๐ŸŽ‰ Integration test completed successfully');
71
-
72
- } catch (error) {
73
- console.error('โŒ Test failed:', error.message);
74
- process.exit(1);
75
- } finally {
76
- client.disconnect();
77
- }
78
- };
79
-
80
- // Run the test
81
- test_count_documents().catch(console.error);