@joystick.js/db-canary 0.0.0-canary.2220 → 0.0.0-canary.2222

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 k from"net";import{decode as C}from"msgpackr";import E from"./lib/op_types.js";import S from"./lib/safe_json_parse.js";import{load_settings as y,get_settings as g,get_port_configuration as v}from"./lib/load_settings.js";import{send_error as f}from"./lib/send_response.js";import{start_cluster as q}from"./cluster/index.js";import x from"./lib/logger.js";import{initialize_database as N,cleanup_database as A}from"./lib/query_engine.js";import{create_message_parser as B,encode_message as _}from"./lib/tcp_protocol.js";import{create_connection_manager as D}from"./lib/connection_manager.js";import{shutdown_write_queue as $}from"./lib/write_queue.js";import{setup_authentication as F,verify_password as J,get_client_ip as K,is_rate_limited as P,initialize_auth_manager as j,reset_auth_state as G}from"./lib/auth_manager.js";import{initialize_api_key_manager as M}from"./lib/api_key_manager.js";import{is_development_mode as W,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 Y,stop_backup_schedule as L}from"./lib/backup_manager.js";import{initialize_replication_manager as Q,shutdown_replication_manager as X}from"./lib/replication_manager.js";import{initialize_write_forwarder as Z,shutdown_write_forwarder as ee}from"./lib/write_forwarder.js";import{handle_database_operation as re,handle_admin_operation as te,handle_ping_operation as oe}from"./lib/operation_dispatcher.js";import{start_http_server as ne,stop_http_server as se}from"./lib/http_server.js";import{create_recovery_token as ae,initialize_recovery_manager as T,reset_recovery_state as ie}from"./lib/recovery_manager.js";const d=new Set;let c=null;const ce=async(t,r={})=>{if(!r?.password){const s=_({ok:0,error:"Authentication operation requires password to be set in data."});t.write(s),t.end();return}try{const o=K(t);if(P(o)){const n=_({ok:0,error:"Too many failed attempts. Please try again later."});t.write(n),t.end();return}if(!await J(r.password,o)){const n=_({ok:0,error:"Authentication failed"});t.write(n),t.end();return}d.add(t.id);const e=_({ok:1,version:"1.0.0",message:"Authentication successful"});t.write(e)}catch(o){const s={ok:0,error:`Authentication error: ${o.message}`},a=_(s);t.write(a),t.end()}},_e=async(t,r={})=>{try{const s={ok:1,password:F(),message:"Authentication setup completed successfully. Save this password - it will not be shown again."},a=_(s);t.write(a)}catch(o){const s={ok:0,error:`Setup error: ${o.message}`},a=_(s);t.write(a)}},pe=(t="")=>{if(!t)throw new Error("Must pass an op type for operation.");return E.includes(t)},Be=t=>{try{if(typeof t=="string")return S(t);if(Buffer.isBuffer(t)){const r=C(t);return typeof r=="string"?S(r):r}else return t}catch{return null}},b=t=>d.has(t.id),De=async()=>{const{create_context_logger:t}=x("server"),r=t();let o=null;try{y(),o=g()}catch{}if(o?.restore_from)try{r.info("Startup restore requested",{backup_filename:o.restore_from});const e=await V(o.restore_from);r.info("Startup restore completed",{backup_filename:o.restore_from,duration_ms:e.duration_ms});const i={...o};delete i.restore_from,process.env.JOYSTICK_DB_SETTINGS=JSON.stringify(i),y(),o=g(),r.info("Removed restore_from from settings after successful restore")}catch(e){r.error("Startup restore failed",{backup_filename:o.restore_from,error:e.message}),r.info("Continuing with fresh database after restore failure")}N(),j(),await M(),T();try{Q(),r.info("Replication manager initialized")}catch(e){r.warn("Failed to initialize replication manager",{error:e.message})}try{Z(),r.info("Write forwarder initialized")}catch(e){r.warn("Failed to initialize write forwarder",{error:e.message})}if(o?.s3)try{Y(),r.info("Backup scheduling started")}catch(e){r.warn("Failed to start backup scheduling",{error:e.message})}c=D({max_connections:1e3,idle_timeout:600*1e3,request_timeout:5*1e3});let s=null;try{const{http_port:e}=v();s=await ne(e),s&&r.info("HTTP server started",{http_port:e})}catch(e){r.warn("Failed to start HTTP server",{error:e.message})}if(W()){const{tcp_port:e,http_port:i}=v();H(e,i)}else U();const a=k.createServer((e={})=>{if(!c.add_connection(e))return;const i=B();e.on("data",async n=>{c.update_activity(e.id);try{const h=i.parse_messages(n);for(const O of h){const u=O,l=u?.op||null;if(!l){f(e,{message:"Missing operation type"});continue}if(!pe(l)){f(e,{message:"Invalid operation type"});continue}const z=c.create_request_timeout(e.id,l);try{switch(l){case"authentication":await ce(e,u?.data||{});break;case"setup":await _e(e,u?.data||{});break;case"insert_one":case"update_one":case"delete_one":case"bulk_write":case"find_one":case"find":case"create_index":case"drop_index":case"get_indexes":await re(e,l,u?.data||{},b,n.length,c,d);break;case"ping":oe(e);break;case"admin":await te(e,u?.data||{},b,c,d);break;case"reload":if(!b(e)){f(e,{message:"Authentication required"});break}try{let p=null;try{p=g()}catch{}let m=null;try{await y(),m=g()}catch{m={port:1983,authentication:{}}}const w={ok:1,status:"success",message:"Configuration reloaded successfully",changes:{port_changed:p?p.port!==m.port:!1,authentication_changed:p?p.authentication?.password_hash!==m.authentication?.password_hash:!1},timestamp:new Date().toISOString()},I=_(w);e.write(I)}catch(p){const m={ok:0,error:`Reload operation failed: ${p.message}`},w=_(m);e.write(w)}break;default:f(e,{message:`Operation ${l} not implemented`})}}finally{clearTimeout(z)}}}catch(h){r.error("Message parsing failed",{client_id:e.id,error:h.message}),f(e,{message:"Invalid message format"}),e.end()}}),e.on("end",()=>{r.info("Client disconnected",{socket_id:e.id}),d.delete(e.id),c.remove_connection(e.id)}),e.on("error",n=>{r.error("Socket error",{socket_id:e.id,error:n.message}),d.delete(e.id),c.remove_connection(e.id)})});return a.cleanup=async()=>{try{await se(),L(),await X(),await ee(),c&&c.shutdown(),d.clear(),await $(),await new Promise(e=>setTimeout(e,100)),await A(),G(),ie()}catch{}},a};if(import.meta.url===`file://${process.argv[1]}`){const{create_context_logger:t}=x("main"),r=t();if(process.argv.includes("--generate-recovery-token"))try{T();const n=ae();console.log("Emergency Recovery Token Generated"),console.log(`Visit: ${n.url}`),console.log("Token expires in 10 minutes"),r.info("Recovery token generated via CLI",{expires_at:new Date(n.expires_at).toISOString()}),process.exit(0)}catch(n){console.error("Failed to generate recovery token:",n.message),r.error("Recovery token generation failed",{error:n.message}),process.exit(1)}const{tcp_port:o,http_port:s}=v(),a={worker_count:process.env.WORKER_COUNT?parseInt(process.env.WORKER_COUNT):void 0,port:o,environment:process.env.NODE_ENV||"development"},{has_settings:e}=await import("./lib/load_settings.js"),i=e();r.info("Starting JoystickDB server...",{workers:a.worker_count||"auto",tcp_port:o,http_port:s,environment:a.environment,has_settings:i,port_source:i?"JOYSTICK_DB_SETTINGS":"default"}),q(a)}export{ce as authentication,pe as check_op_type,De as create_server,Be as parse_data,_e as setup};
1
+ import C from"net";import{decode as E}from"msgpackr";import q from"./lib/op_types.js";import S from"./lib/safe_json_parse.js";import{load_settings as y,get_settings as g,get_port_configuration as v}from"./lib/load_settings.js";import{send_error as f}from"./lib/send_response.js";import{start_cluster as N}from"./cluster/index.js";import x from"./lib/logger.js";import{initialize_database as A,cleanup_database as B}from"./lib/query_engine.js";import{create_message_parser as D,encode_message as _}from"./lib/tcp_protocol.js";import{create_connection_manager as $}from"./lib/connection_manager.js";import{shutdown_write_queue as F}from"./lib/write_queue.js";import{setup_authentication as J,verify_password as K,get_client_ip as P,is_rate_limited as j,initialize_auth_manager as G,reset_auth_state as M}from"./lib/auth_manager.js";import{initialize_api_key_manager as W}from"./lib/api_key_manager.js";import{is_development_mode as T,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 Y,stop_backup_schedule as L}from"./lib/backup_manager.js";import{initialize_replication_manager as Q,shutdown_replication_manager as X}from"./lib/replication_manager.js";import{initialize_write_forwarder as Z,shutdown_write_forwarder as ee}from"./lib/write_forwarder.js";import{handle_database_operation as re,handle_admin_operation as te,handle_ping_operation as oe}from"./lib/operation_dispatcher.js";import{start_http_server as ne,stop_http_server as se}from"./lib/http_server.js";import{create_recovery_token as ae,initialize_recovery_manager as O,reset_recovery_state as ie}from"./lib/recovery_manager.js";const d=new Set;let c=null;const ce=async(t,r={})=>{if(!r?.password){const s=_({ok:0,error:"Authentication operation requires password to be set in data."});t.write(s),t.end();return}try{const o=P(t);if(j(o)){const n=_({ok:0,error:"Too many failed attempts. Please try again later."});t.write(n),t.end();return}if(!await K(r.password,o)){const n=_({ok:0,error:"Authentication failed"});t.write(n),t.end();return}d.add(t.id);const e=_({ok:1,version:"1.0.0",message:"Authentication successful"});t.write(e)}catch(o){const s={ok:0,error:`Authentication error: ${o.message}`},a=_(s);t.write(a),t.end()}},_e=async(t,r={})=>{try{const s={ok:1,password:J(),message:"Authentication setup completed successfully. Save this password - it will not be shown again."},a=_(s);t.write(a)}catch(o){const s={ok:0,error:`Setup error: ${o.message}`},a=_(s);t.write(a)}},pe=(t="")=>{if(!t)throw new Error("Must pass an op type for operation.");return q.includes(t)},Be=t=>{try{if(typeof t=="string")return S(t);if(Buffer.isBuffer(t)){const r=E(t);return typeof r=="string"?S(r):r}else return t}catch{return null}},b=t=>T()?!0:d.has(t.id),De=async()=>{const{create_context_logger:t}=x("server"),r=t();let o=null;try{y(),o=g()}catch{}if(o?.restore_from)try{r.info("Startup restore requested",{backup_filename:o.restore_from});const e=await V(o.restore_from);r.info("Startup restore completed",{backup_filename:o.restore_from,duration_ms:e.duration_ms});const i={...o};delete i.restore_from,process.env.JOYSTICK_DB_SETTINGS=JSON.stringify(i),y(),o=g(),r.info("Removed restore_from from settings after successful restore")}catch(e){r.error("Startup restore failed",{backup_filename:o.restore_from,error:e.message}),r.info("Continuing with fresh database after restore failure")}A(),G(),await W(),O();try{Q(),r.info("Replication manager initialized")}catch(e){r.warn("Failed to initialize replication manager",{error:e.message})}try{Z(),r.info("Write forwarder initialized")}catch(e){r.warn("Failed to initialize write forwarder",{error:e.message})}if(o?.s3)try{Y(),r.info("Backup scheduling started")}catch(e){r.warn("Failed to start backup scheduling",{error:e.message})}c=$({max_connections:1e3,idle_timeout:600*1e3,request_timeout:5*1e3});let s=null;try{const{http_port:e}=v();s=await ne(e),s&&r.info("HTTP server started",{http_port:e})}catch(e){r.warn("Failed to start HTTP server",{error:e.message})}if(T()){const{tcp_port:e,http_port:i}=v();H(e,i)}else U();const a=C.createServer((e={})=>{if(!c.add_connection(e))return;const i=D();e.on("data",async n=>{c.update_activity(e.id);try{const h=i.parse_messages(n);for(const z of h){const u=z,l=u?.op||null;if(!l){f(e,{message:"Missing operation type"});continue}if(!pe(l)){f(e,{message:"Invalid operation type"});continue}const I=c.create_request_timeout(e.id,l);try{switch(l){case"authentication":await ce(e,u?.data||{});break;case"setup":await _e(e,u?.data||{});break;case"insert_one":case"update_one":case"delete_one":case"bulk_write":case"find_one":case"find":case"create_index":case"drop_index":case"get_indexes":await re(e,l,u?.data||{},b,n.length,c,d);break;case"ping":oe(e);break;case"admin":await te(e,u?.data||{},b,c,d);break;case"reload":if(!b(e)){f(e,{message:"Authentication required"});break}try{let p=null;try{p=g()}catch{}let m=null;try{await y(),m=g()}catch{m={port:1983,authentication:{}}}const w={ok:1,status:"success",message:"Configuration reloaded successfully",changes:{port_changed:p?p.port!==m.port:!1,authentication_changed:p?p.authentication?.password_hash!==m.authentication?.password_hash:!1},timestamp:new Date().toISOString()},R=_(w);e.write(R)}catch(p){const m={ok:0,error:`Reload operation failed: ${p.message}`},w=_(m);e.write(w)}break;default:f(e,{message:`Operation ${l} not implemented`})}}finally{clearTimeout(I)}}}catch(h){r.error("Message parsing failed",{client_id:e.id,error:h.message}),f(e,{message:"Invalid message format"}),e.end()}}),e.on("end",()=>{r.info("Client disconnected",{socket_id:e.id}),d.delete(e.id),c.remove_connection(e.id)}),e.on("error",n=>{r.error("Socket error",{socket_id:e.id,error:n.message}),d.delete(e.id),c.remove_connection(e.id)})});return a.cleanup=async()=>{try{await se(),L(),await X(),await ee(),c&&c.shutdown(),d.clear(),await F(),await new Promise(e=>setTimeout(e,100)),await B(),M(),ie()}catch{}},a};if(import.meta.url===`file://${process.argv[1]}`){const{create_context_logger:t}=x("main"),r=t();if(process.argv.includes("--generate-recovery-token"))try{O();const n=ae();console.log("Emergency Recovery Token Generated"),console.log(`Visit: ${n.url}`),console.log("Token expires in 10 minutes"),r.info("Recovery token generated via CLI",{expires_at:new Date(n.expires_at).toISOString()}),process.exit(0)}catch(n){console.error("Failed to generate recovery token:",n.message),r.error("Recovery token generation failed",{error:n.message}),process.exit(1)}const{tcp_port:o,http_port:s}=v(),a={worker_count:process.env.WORKER_COUNT?parseInt(process.env.WORKER_COUNT):void 0,port:o,environment:process.env.NODE_ENV||"development"},{has_settings:e}=await import("./lib/load_settings.js"),i=e();r.info("Starting JoystickDB server...",{workers:a.worker_count||"auto",tcp_port:o,http_port:s,environment:a.environment,has_settings:i,port_source:i?"JOYSTICK_DB_SETTINGS":"default"}),N(a)}export{ce as authentication,pe as check_op_type,De as create_server,Be as parse_data,_e as setup};
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.2220",
5
- "canary_version": "0.0.0-canary.2219",
4
+ "version": "0.0.0-canary.2222",
5
+ "canary_version": "0.0.0-canary.2221",
6
6
  "description": "JoystickDB - A minimalist database server for the Joystick framework",
7
7
  "main": "./dist/server/index.js",
8
8
  "scripts": {
@@ -181,10 +181,16 @@ export const parse_data = (raw_data) => {
181
181
 
182
182
  /**
183
183
  * Checks if a socket connection is authenticated.
184
+ * In development mode, authentication is bypassed for TCP connections.
184
185
  * @param {net.Socket} socket - Socket connection to check
185
- * @returns {boolean} True if socket is authenticated
186
+ * @returns {boolean} True if socket is authenticated or in development mode
186
187
  */
187
188
  const check_authentication = (socket) => {
189
+ // NOTE: Bypass authentication in development mode for TCP connections.
190
+ if (is_development_mode()) {
191
+ return true;
192
+ }
193
+
188
194
  return authenticated_clients.has(socket.id);
189
195
  };
190
196
 
@@ -0,0 +1,195 @@
1
+ import test from 'ava';
2
+ import net from 'net';
3
+ import { create_server } from '../../../src/server/index.js';
4
+ import { encode_message, create_message_parser } from '../../../src/server/lib/tcp_protocol.js';
5
+ import { initialize_database, cleanup_database } from '../../../src/server/lib/query_engine.js';
6
+ import { reset_auth_state } from '../../../src/server/lib/auth_manager.js';
7
+
8
+ // Dynamic port allocation
9
+ let current_port = 4000;
10
+ const get_next_port = () => {
11
+ return ++current_port;
12
+ };
13
+
14
+ let server;
15
+ let port;
16
+ let original_node_env;
17
+
18
+ test.beforeEach(async () => {
19
+ // Store original NODE_ENV
20
+ original_node_env = process.env.NODE_ENV;
21
+
22
+ // Set NODE_ENV to development
23
+ process.env.NODE_ENV = 'development';
24
+
25
+ // Reset auth state and clean up environment variables
26
+ reset_auth_state();
27
+ delete process.env.JOYSTICK_DB_SETTINGS;
28
+
29
+ // Initialize database for testing
30
+ initialize_database();
31
+
32
+ // Create server with dynamic port
33
+ const test_port = get_next_port();
34
+ server = await create_server();
35
+
36
+ // Start server on specific port
37
+ await new Promise((resolve) => {
38
+ server.listen(test_port, () => {
39
+ port = test_port;
40
+ setTimeout(resolve, 100);
41
+ });
42
+ });
43
+ });
44
+
45
+ test.afterEach(async () => {
46
+ // Restore original NODE_ENV
47
+ process.env.NODE_ENV = original_node_env;
48
+
49
+ if (server) {
50
+ await server.cleanup();
51
+ server.close();
52
+ server = null;
53
+ }
54
+
55
+ // Clean up database
56
+ try {
57
+ await cleanup_database(true); // Remove test database directory
58
+ } catch (error) {
59
+ // Ignore cleanup errors
60
+ }
61
+
62
+ // Clean up environment variables
63
+ reset_auth_state();
64
+ delete process.env.JOYSTICK_DB_SETTINGS;
65
+ });
66
+
67
+ const create_client = () => {
68
+ return new Promise((resolve, reject) => {
69
+ const client = net.createConnection(port, 'localhost');
70
+ const parser = create_message_parser();
71
+
72
+ client.on('connect', () => {
73
+ resolve({
74
+ client,
75
+ send: (data) => {
76
+ const encoded = encode_message(data);
77
+ client.write(encoded);
78
+ },
79
+ receive: () => {
80
+ return new Promise((resolve) => {
81
+ const handler = (data) => {
82
+ try {
83
+ const messages = parser.parse_messages(data);
84
+ for (const message of messages) {
85
+ client.off('data', handler);
86
+ resolve(message);
87
+ return;
88
+ }
89
+ } catch (error) {
90
+ // Continue listening
91
+ }
92
+ };
93
+ client.on('data', handler);
94
+ });
95
+ },
96
+ close: () => {
97
+ client.end();
98
+ }
99
+ });
100
+ });
101
+
102
+ client.on('error', reject);
103
+ });
104
+ };
105
+
106
+ test('development mode - database operations work without authentication', async (t) => {
107
+ const { client, send, receive, close } = await create_client();
108
+
109
+ try {
110
+ // Try ping operation without authentication - should work in development mode
111
+ send({ op: 'ping' });
112
+ const response = await receive();
113
+
114
+ t.is(response.ok, 1);
115
+ // Ping operation just returns { ok: 1 } without a message
116
+ } finally {
117
+ close();
118
+ }
119
+ });
120
+
121
+ test('development mode - find operation works without authentication', async (t) => {
122
+ const { client, send, receive, close } = await create_client();
123
+
124
+ try {
125
+ // Try find operation without authentication - should work in development mode
126
+ send({ op: 'find', data: { collection: 'users', filter: {} } });
127
+ const response = await receive();
128
+
129
+ t.is(response.ok, 1);
130
+ t.true(Array.isArray(response.documents));
131
+ } finally {
132
+ close();
133
+ }
134
+ });
135
+
136
+ test('development mode - insert operation works without authentication', async (t) => {
137
+ const { client, send, receive, close } = await create_client();
138
+
139
+ try {
140
+ // Try insert operation without authentication - should work in development mode
141
+ send({
142
+ op: 'insert_one',
143
+ data: {
144
+ collection: 'users',
145
+ document: { name: 'Test User', email: 'test@example.com' }
146
+ }
147
+ });
148
+ const response = await receive();
149
+
150
+ t.is(response.ok, 1);
151
+ t.true(typeof response.inserted_id === 'string');
152
+ } finally {
153
+ close();
154
+ }
155
+ });
156
+
157
+ test('development mode - admin operation works without authentication', async (t) => {
158
+ const { client, send, receive, close } = await create_client();
159
+
160
+ try {
161
+ // Try admin operation without authentication - should work in development mode
162
+ send({ op: 'admin' });
163
+ const response = await receive();
164
+
165
+ t.true(typeof response.authentication === 'object');
166
+ t.is(response.authentication.authenticated_clients, 0); // No clients authenticated since we bypassed auth
167
+ t.true(typeof response.server === 'object');
168
+ t.true(typeof response.database === 'object');
169
+ } finally {
170
+ close();
171
+ }
172
+ });
173
+
174
+ test('development mode - authentication still works if explicitly used', async (t) => {
175
+ const { client, send, receive, close } = await create_client();
176
+
177
+ try {
178
+ // Setup authentication first
179
+ send({ op: 'setup' });
180
+ const setup_response = await receive();
181
+
182
+ t.true(setup_response.ok === 1 || setup_response.ok === true);
183
+ t.is(typeof setup_response.password, 'string');
184
+
185
+ // Now authenticate with the password
186
+ send({ op: 'authentication', data: { password: setup_response.password } });
187
+ const auth_response = await receive();
188
+
189
+ t.is(auth_response.ok, 1);
190
+ t.is(auth_response.version, '1.0.0');
191
+ t.is(auth_response.message, 'Authentication successful');
192
+ } finally {
193
+ close();
194
+ }
195
+ });