@wowoengine/sawitdb 2.4.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.
- package/LICENSE +21 -0
- package/README.md +245 -0
- package/bin/sawit-server.js +68 -0
- package/cli/local.js +49 -0
- package/cli/remote.js +165 -0
- package/docs/index.html +727 -0
- package/docs/sawitdb.jpg +0 -0
- package/package.json +63 -0
- package/src/SawitClient.js +265 -0
- package/src/SawitServer.js +602 -0
- package/src/WowoEngine.js +539 -0
- package/src/modules/BTreeIndex.js +282 -0
- package/src/modules/Pager.js +70 -0
- package/src/modules/QueryParser.js +569 -0
package/docs/sawitdb.jpg
ADDED
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wowoengine/sawitdb",
|
|
3
|
+
"publishConfig": {
|
|
4
|
+
"access": "public"
|
|
5
|
+
},
|
|
6
|
+
"version": "2.4.0",
|
|
7
|
+
"description": "A networked document database with Indonesian Agricultural Query Language (AQL) - Network Edition",
|
|
8
|
+
"main": "src/WowoEngine.js",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node bin/sawit-server.js",
|
|
11
|
+
"server": "node bin/sawit-server.js",
|
|
12
|
+
"test": "node tests/test_server_injection.js",
|
|
13
|
+
"example": "node examples/example_client.js",
|
|
14
|
+
"cli": "node cli/remote.js sawitdb://localhost:7878/default",
|
|
15
|
+
"dev": "SAWIT_LOG_LEVEL=debug node bin/sawit-server.js"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"database",
|
|
19
|
+
"database-engine",
|
|
20
|
+
"database-server",
|
|
21
|
+
"database-client",
|
|
22
|
+
"database-remote",
|
|
23
|
+
"database-local",
|
|
24
|
+
"database-embedded",
|
|
25
|
+
"database-single-file",
|
|
26
|
+
"database-binary",
|
|
27
|
+
"database-file",
|
|
28
|
+
"sawitdb",
|
|
29
|
+
"wowoengine",
|
|
30
|
+
"wowoengine/sawitdb",
|
|
31
|
+
"document-database",
|
|
32
|
+
"nosql",
|
|
33
|
+
"json",
|
|
34
|
+
"btree",
|
|
35
|
+
"indexing",
|
|
36
|
+
"tcp-server",
|
|
37
|
+
"networking",
|
|
38
|
+
"agricultural",
|
|
39
|
+
"indonesian",
|
|
40
|
+
"mongodb-like",
|
|
41
|
+
"embedded-database"
|
|
42
|
+
],
|
|
43
|
+
"author": "SawitDB Community",
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "https://github.com/WowoEngine/SawitDB.git"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=12.0.0"
|
|
51
|
+
},
|
|
52
|
+
"bin": {
|
|
53
|
+
"sawitdb-server": "./bin/sawit-server.js",
|
|
54
|
+
"sawitdb-cli": "./cli/remote.js"
|
|
55
|
+
},
|
|
56
|
+
"files": [
|
|
57
|
+
"src/",
|
|
58
|
+
"bin/",
|
|
59
|
+
"cli/",
|
|
60
|
+
"docs/",
|
|
61
|
+
"README.md"
|
|
62
|
+
]
|
|
63
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
const net = require('net');
|
|
2
|
+
const { URL } = require('url');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SawitDB Client - Connect to SawitDB Server
|
|
6
|
+
* Usage: sawitdb://[username:password@]host:port/database
|
|
7
|
+
*/
|
|
8
|
+
class SawitClient {
|
|
9
|
+
constructor(connectionString) {
|
|
10
|
+
this.connectionString = connectionString;
|
|
11
|
+
this.socket = null;
|
|
12
|
+
this.connected = false;
|
|
13
|
+
this.authenticated = false;
|
|
14
|
+
this.currentDatabase = null;
|
|
15
|
+
this.buffer = '';
|
|
16
|
+
this.pendingRequests = [];
|
|
17
|
+
this.requestId = 0;
|
|
18
|
+
|
|
19
|
+
this._parseConnectionString(connectionString);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
_parseConnectionString(connStr) {
|
|
23
|
+
// Parse sawitdb://[user:pass@]host:port/database
|
|
24
|
+
const url = connStr.replace('sawitdb://', 'http://'); // Trick to use URL parser
|
|
25
|
+
const parsed = new URL(url);
|
|
26
|
+
|
|
27
|
+
this.host = parsed.hostname || 'localhost';
|
|
28
|
+
this.port = parseInt(parsed.port) || 7878;
|
|
29
|
+
this.database = parsed.pathname.replace('/', '') || null;
|
|
30
|
+
this.username = parsed.username || null;
|
|
31
|
+
this.password = parsed.password || null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async connect() {
|
|
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
|
+
});
|
|
40
|
+
|
|
41
|
+
this.socket.on('data', (data) => {
|
|
42
|
+
this._handleData(data);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
this.socket.on('end', () => {
|
|
46
|
+
console.log('[Client] Disconnected from server');
|
|
47
|
+
this.connected = false;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
this.socket.on('error', (err) => {
|
|
51
|
+
console.error('[Client] Socket error:', err.message);
|
|
52
|
+
this.connected = false;
|
|
53
|
+
reject(err);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Wait for welcome message
|
|
57
|
+
const welcomeHandler = (response) => {
|
|
58
|
+
if (response.type === 'welcome') {
|
|
59
|
+
console.log(`[Client] ${response.message} v${response.version}`);
|
|
60
|
+
this._initConnection().then(resolve).catch(reject);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
this.pendingRequests.push({ id: 'welcome', handler: welcomeHandler });
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async _initConnection() {
|
|
69
|
+
// Authenticate if credentials provided
|
|
70
|
+
if (this.username && this.password) {
|
|
71
|
+
await this._authenticate();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Select database if specified
|
|
75
|
+
if (this.database) {
|
|
76
|
+
await this.use(this.database);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async _authenticate() {
|
|
81
|
+
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));
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async use(database) {
|
|
101
|
+
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);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async query(queryString, params = []) {
|
|
120
|
+
if (!this.connected) {
|
|
121
|
+
throw new Error('Not connected to server');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
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);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async listDatabases() {
|
|
144
|
+
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);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async dropDatabase(database) {
|
|
161
|
+
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;
|
|
169
|
+
}
|
|
170
|
+
resolve(response.message);
|
|
171
|
+
} else if (response.type === 'error') {
|
|
172
|
+
reject(new Error(response.error));
|
|
173
|
+
} else {
|
|
174
|
+
resolve(response);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async ping() {
|
|
181
|
+
return new Promise((resolve, reject) => {
|
|
182
|
+
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);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async stats() {
|
|
198
|
+
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);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
_sendRequest(request, callback) {
|
|
215
|
+
const id = ++this.requestId;
|
|
216
|
+
this.pendingRequests.push({ id, handler: callback });
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
this.socket.write(JSON.stringify(request) + '\n');
|
|
220
|
+
} catch (err) {
|
|
221
|
+
console.error('[Client] Failed to send request:', err.message);
|
|
222
|
+
const idx = this.pendingRequests.findIndex(r => r.id === id);
|
|
223
|
+
if (idx !== -1) {
|
|
224
|
+
this.pendingRequests.splice(idx, 1);
|
|
225
|
+
}
|
|
226
|
+
throw err;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
_handleData(data) {
|
|
231
|
+
this.buffer += data.toString();
|
|
232
|
+
|
|
233
|
+
let newlineIndex;
|
|
234
|
+
while ((newlineIndex = this.buffer.indexOf('\n')) !== -1) {
|
|
235
|
+
const message = this.buffer.substring(0, newlineIndex);
|
|
236
|
+
this.buffer = this.buffer.substring(newlineIndex + 1);
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
const response = JSON.parse(message);
|
|
240
|
+
this._handleResponse(response);
|
|
241
|
+
} catch (err) {
|
|
242
|
+
console.error('[Client] Failed to parse response:', err.message);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
_handleResponse(response) {
|
|
248
|
+
if (this.pendingRequests.length > 0) {
|
|
249
|
+
const request = this.pendingRequests.shift();
|
|
250
|
+
if (request.handler) {
|
|
251
|
+
request.handler(response);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
disconnect() {
|
|
257
|
+
if (this.socket) {
|
|
258
|
+
this.socket.end();
|
|
259
|
+
this.socket = null;
|
|
260
|
+
this.connected = false;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
module.exports = SawitClient;
|