@wowoengine/sawitdb 2.4.0 → 2.6.0

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.
Files changed (38) hide show
  1. package/README.md +86 -25
  2. package/bin/sawit-server.js +12 -1
  3. package/cli/benchmark.js +318 -0
  4. package/cli/local.js +98 -20
  5. package/cli/remote.js +65 -11
  6. package/cli/test.js +203 -0
  7. package/cli/test_security.js +140 -0
  8. package/docs/DB Event.md +32 -0
  9. package/docs/index.html +699 -336
  10. package/package.json +10 -7
  11. package/src/SawitClient.js +122 -98
  12. package/src/SawitServer.js +78 -450
  13. package/src/SawitWorker.js +55 -0
  14. package/src/WowoEngine.js +360 -449
  15. package/src/modules/BTreeIndex.js +114 -43
  16. package/src/modules/ClusterManager.js +78 -0
  17. package/src/modules/Env.js +33 -0
  18. package/src/modules/Pager.js +215 -6
  19. package/src/modules/QueryParser.js +310 -82
  20. package/src/modules/ThreadManager.js +84 -0
  21. package/src/modules/ThreadPool.js +154 -0
  22. package/src/modules/WAL.js +340 -0
  23. package/src/server/DatabaseRegistry.js +92 -0
  24. package/src/server/auth/AuthManager.js +92 -0
  25. package/src/server/router/RequestRouter.js +278 -0
  26. package/src/server/session/ClientSession.js +19 -0
  27. package/src/services/IndexManager.js +183 -0
  28. package/src/services/QueryExecutor.js +11 -0
  29. package/src/services/TableManager.js +162 -0
  30. package/src/services/event/DBEvent.js +61 -0
  31. package/src/services/event/DBEventHandler.js +39 -0
  32. package/src/services/executors/AggregateExecutor.js +153 -0
  33. package/src/services/executors/DeleteExecutor.js +134 -0
  34. package/src/services/executors/InsertExecutor.js +113 -0
  35. package/src/services/executors/SelectExecutor.js +130 -0
  36. package/src/services/executors/UpdateExecutor.js +156 -0
  37. package/src/services/logic/ConditionEvaluator.js +75 -0
  38. package/src/services/logic/JoinProcessor.js +230 -0
package/package.json CHANGED
@@ -3,16 +3,19 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "2.4.0",
7
- "description": "A networked document database with Indonesian Agricultural Query Language (AQL) - Network Edition",
6
+ "version": "2.6.0",
7
+ "description": "A high-performance hybrid paged database with Agricultural Query Language (AQL) and Generic SQL support. Features single-file storage (.sawit), object caching, worker-thread parallelism, and crash recovery.",
8
8
  "main": "src/WowoEngine.js",
9
9
  "scripts": {
10
10
  "start": "node bin/sawit-server.js",
11
+ "start:cluster": "set SAWIT_CLUSTER_MODE=true && node bin/sawit-server.js",
11
12
  "server": "node bin/sawit-server.js",
12
- "test": "node tests/test_server_injection.js",
13
+ "test": "node cli/test.js",
13
14
  "example": "node examples/example_client.js",
14
15
  "cli": "node cli/remote.js sawitdb://localhost:7878/default",
15
- "dev": "SAWIT_LOG_LEVEL=debug node bin/sawit-server.js"
16
+ "dev": "set SAWIT_LOG_LEVEL=debug && node bin/sawit-server.js",
17
+ "bench": "node cli/benchmark.js --spawn --workers=1 --clients=1",
18
+ "bench:cluster": "node cli/benchmark.js --spawn --cluster --workers=4 --clients=10"
16
19
  },
17
20
  "keywords": [
18
21
  "database",
@@ -44,14 +47,14 @@
44
47
  "license": "MIT",
45
48
  "repository": {
46
49
  "type": "git",
47
- "url": "https://github.com/WowoEngine/SawitDB.git"
50
+ "url": "git+https://github.com/WowoEngine/SawitDB.git"
48
51
  },
49
52
  "engines": {
50
53
  "node": ">=12.0.0"
51
54
  },
52
55
  "bin": {
53
- "sawitdb-server": "./bin/sawit-server.js",
54
- "sawitdb-cli": "./cli/remote.js"
56
+ "sawitdb-server": "bin/sawit-server.js",
57
+ "sawitdb-cli": "cli/remote.js"
55
58
  },
56
59
  "files": [
57
60
  "src/",
@@ -16,10 +16,10 @@ class SawitClient {
16
16
  this.pendingRequests = [];
17
17
  this.requestId = 0;
18
18
 
19
- this._parseConnectionString(connectionString);
19
+ this.#parseConnectionString(connectionString);
20
20
  }
21
21
 
22
- _parseConnectionString(connStr) {
22
+ #parseConnectionString(connStr) {
23
23
  // Parse sawitdb://[user:pass@]host:port/database
24
24
  const url = connStr.replace('sawitdb://', 'http://'); // Trick to use URL parser
25
25
  const parsed = new URL(url);
@@ -33,13 +33,16 @@ class SawitClient {
33
33
 
34
34
  async connect() {
35
35
  return new Promise((resolve, reject) => {
36
- this.socket = net.createConnection({ host: this.host, port: this.port }, () => {
37
- console.log(`[Client] Connected to ${this.host}:${this.port}`);
38
- this.connected = true;
39
- });
36
+ this.socket = net.createConnection(
37
+ { host: this.host, port: this.port },
38
+ () => {
39
+ console.log(`[Client] Connected to ${this.host}:${this.port}`);
40
+ this.connected = true;
41
+ }
42
+ );
40
43
 
41
44
  this.socket.on('data', (data) => {
42
- this._handleData(data);
45
+ this.#handleData(data);
43
46
  });
44
47
 
45
48
  this.socket.on('end', () => {
@@ -79,40 +82,46 @@ class SawitClient {
79
82
 
80
83
  async _authenticate() {
81
84
  return new Promise((resolve, reject) => {
82
- this._sendRequest({
83
- type: 'auth',
84
- payload: {
85
- username: this.username,
86
- password: this.password
87
- }
88
- }, (response) => {
89
- if (response.type === 'auth_success') {
90
- this.authenticated = true;
91
- console.log('[Client] Authenticated successfully');
92
- resolve();
93
- } else if (response.type === 'error') {
94
- reject(new Error(response.error));
85
+ this.#sendRequest(
86
+ {
87
+ type: 'auth',
88
+ payload: {
89
+ username: this.username,
90
+ password: this.password
91
+ }
92
+ },
93
+ (response) => {
94
+ if (response.type === 'auth_success') {
95
+ this.authenticated = true;
96
+ console.log('[Client] Authenticated successfully');
97
+ resolve();
98
+ } else if (response.type === 'error') {
99
+ reject(new Error(response.error));
100
+ }
95
101
  }
96
- });
102
+ );
97
103
  });
98
104
  }
99
105
 
100
106
  async use(database) {
101
107
  return new Promise((resolve, reject) => {
102
- this._sendRequest({
103
- type: 'use',
104
- payload: { database }
105
- }, (response) => {
106
- if (response.type === 'use_success') {
107
- this.currentDatabase = database;
108
- console.log(`[Client] Using database '${database}'`);
109
- resolve(response);
110
- } else if (response.type === 'error') {
111
- reject(new Error(response.error));
112
- } else {
113
- resolve(response);
108
+ this.#sendRequest(
109
+ {
110
+ type: 'use',
111
+ payload: { database }
112
+ },
113
+ (response) => {
114
+ if (response.type === 'use_success') {
115
+ this.currentDatabase = database;
116
+ console.log(`[Client] Using database '${database}'`);
117
+ resolve(response);
118
+ } else if (response.type === 'error') {
119
+ reject(new Error(response.error));
120
+ } else {
121
+ resolve(response);
122
+ }
114
123
  }
115
- });
124
+ );
116
125
  });
117
126
  }
118
127
 
@@ -122,96 +131,111 @@ class SawitClient {
122
131
  }
123
132
 
124
133
  return new Promise((resolve, reject) => {
125
- this._sendRequest({
126
- type: 'query',
127
- payload: {
128
- query: queryString,
129
- params: params
130
- }
131
- }, (response) => {
132
- if (response.type === 'query_result') {
133
- resolve(response.result);
134
- } else if (response.type === 'error') {
135
- reject(new Error(response.error));
136
- } else {
137
- resolve(response);
134
+ this.#sendRequest(
135
+ {
136
+ type: 'query',
137
+ payload: {
138
+ query: queryString,
139
+ params: params
140
+ }
141
+ },
142
+ (response) => {
143
+ if (response.type === 'query_result') {
144
+ resolve(response.result);
145
+ } else if (response.type === 'error') {
146
+ reject(new Error(response.error));
147
+ } else {
148
+ resolve(response);
149
+ }
138
150
  }
139
- });
151
+ );
140
152
  });
141
153
  }
142
154
 
143
155
  async listDatabases() {
144
156
  return new Promise((resolve, reject) => {
145
- this._sendRequest({
146
- type: 'list_databases',
147
- payload: {}
148
- }, (response) => {
149
- if (response.type === 'database_list') {
150
- resolve(response.databases);
151
- } else if (response.type === 'error') {
152
- reject(new Error(response.error));
153
- } else {
154
- resolve(response);
157
+ this.#sendRequest(
158
+ {
159
+ type: 'list_databases',
160
+ payload: {}
161
+ },
162
+ (response) => {
163
+ if (response.type === 'database_list') {
164
+ resolve(response.databases);
165
+ } else if (response.type === 'error') {
166
+ reject(new Error(response.error));
167
+ } else {
168
+ resolve(response);
169
+ }
155
170
  }
156
- });
171
+ );
157
172
  });
158
173
  }
159
174
 
160
175
  async dropDatabase(database) {
161
176
  return new Promise((resolve, reject) => {
162
- this._sendRequest({
163
- type: 'drop_database',
164
- payload: { database }
165
- }, (response) => {
166
- if (response.type === 'drop_success') {
167
- if (this.currentDatabase === database) {
168
- this.currentDatabase = null;
177
+ this.#sendRequest(
178
+ {
179
+ type: 'drop_database',
180
+ payload: { database }
181
+ },
182
+ (response) => {
183
+ if (response.type === 'drop_success') {
184
+ if (this.currentDatabase === database) {
185
+ this.currentDatabase = null;
186
+ }
187
+ resolve(response.message);
188
+ } else if (response.type === 'error') {
189
+ reject(new Error(response.error));
190
+ } else {
191
+ resolve(response);
169
192
  }
170
- resolve(response.message);
171
- } else if (response.type === 'error') {
172
- reject(new Error(response.error));
173
- } else {
174
- resolve(response);
175
193
  }
176
- });
194
+ );
177
195
  });
178
196
  }
179
197
 
180
198
  async ping() {
181
199
  return new Promise((resolve, reject) => {
182
200
  const start = Date.now();
183
- this._sendRequest({
184
- type: 'ping',
185
- payload: {}
186
- }, (response) => {
187
- if (response.type === 'pong') {
188
- const latency = Date.now() - start;
189
- resolve({ latency, serverTime: response.timestamp });
190
- } else {
191
- resolve(response);
201
+ this.#sendRequest(
202
+ {
203
+ type: 'ping',
204
+ payload: {}
205
+ },
206
+ (response) => {
207
+ if (response.type === 'pong') {
208
+ const latency = Date.now() - start;
209
+ resolve({ latency, serverTime: response.timestamp });
210
+ } else {
211
+ resolve(response);
212
+ }
192
213
  }
193
- });
214
+ );
194
215
  });
195
216
  }
196
217
 
197
218
  async stats() {
198
219
  return new Promise((resolve, reject) => {
199
- this._sendRequest({
200
- type: 'stats',
201
- payload: {}
202
- }, (response) => {
203
- if (response.type === 'stats') {
204
- resolve(response.stats);
205
- } else if (response.type === 'error') {
206
- reject(new Error(response.error));
207
- } else {
208
- resolve(response);
220
+ this.#sendRequest(
221
+ {
222
+ type: 'stats',
223
+ payload: {}
224
+ },
225
+ (response) => {
226
+ if (response.type === 'stats') {
227
+ resolve(response.stats);
228
+ } else if (response.type === 'error') {
229
+ reject(new Error(response.error));
230
+ } else {
231
+ resolve(response);
232
+ }
209
233
  }
210
- });
234
+ );
211
235
  });
212
236
  }
213
237
 
214
- _sendRequest(request, callback) {
238
+ #sendRequest(request, callback) {
215
239
  const id = ++this.requestId;
216
240
  this.pendingRequests.push({ id, handler: callback });
217
241
 
@@ -219,7 +243,7 @@ class SawitClient {
219
243
  this.socket.write(JSON.stringify(request) + '\n');
220
244
  } catch (err) {
221
245
  console.error('[Client] Failed to send request:', err.message);
222
- const idx = this.pendingRequests.findIndex(r => r.id === id);
246
+ const idx = this.pendingRequests.findIndex((r) => r.id === id);
223
247
  if (idx !== -1) {
224
248
  this.pendingRequests.splice(idx, 1);
225
249
  }
@@ -227,7 +251,7 @@ class SawitClient {
227
251
  }
228
252
  }
229
253
 
230
- _handleData(data) {
254
+ #handleData(data) {
231
255
  this.buffer += data.toString();
232
256
 
233
257
  let newlineIndex;
@@ -237,14 +261,14 @@ class SawitClient {
237
261
 
238
262
  try {
239
263
  const response = JSON.parse(message);
240
- this._handleResponse(response);
264
+ this.#handleResponse(response);
241
265
  } catch (err) {
242
266
  console.error('[Client] Failed to parse response:', err.message);
243
267
  }
244
268
  }
245
269
  }
246
270
 
247
- _handleResponse(response) {
271
+ #handleResponse(response) {
248
272
  if (this.pendingRequests.length > 0) {
249
273
  const request = this.pendingRequests.shift();
250
274
  if (request.handler) {