xeondb-driver 0.1.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 (2) hide show
  1. package/index.js +194 -0
  2. package/package.json +28 -0
package/index.js ADDED
@@ -0,0 +1,194 @@
1
+ const net = require('net');
2
+
3
+ class XeondbClient {
4
+ /**
5
+ * @param {Object} options
6
+ * @param {string} options.host - The database server IP or hostname
7
+ * @param {number} options.port - The database server port
8
+ */
9
+ constructor({ host = '127.0.0.1', port = 8080 } = {}) {
10
+ this.host = host;
11
+ this.port = port;
12
+ this.socket = null;
13
+ this.keyspace = null;
14
+ this.connected = false;
15
+ this._buffer = '';
16
+ this._pending = [];
17
+ }
18
+
19
+ /**
20
+ * Connect to the Xeondb server
21
+ * @returns {Promise<boolean>} true if connected, false otherwise
22
+ */
23
+ connect() {
24
+ return new Promise((resolve) => {
25
+ this.socket = net.createConnection({ host: this.host, port: this.port }, () => {
26
+ this.connected = true;
27
+ this._installSocketHandlers();
28
+ resolve(true);
29
+ });
30
+ this.socket.on('error', () => {
31
+ this.connected = false;
32
+ resolve(false);
33
+ });
34
+ });
35
+ }
36
+
37
+ _installSocketHandlers() {
38
+ if (!this.socket) return;
39
+ this._buffer = '';
40
+
41
+ this.socket.on('data', (data) => {
42
+ this._buffer += data.toString('utf8');
43
+ while (true) {
44
+ const nl = this._buffer.indexOf('\n');
45
+ if (nl === -1) return;
46
+ const line = this._buffer.slice(0, nl).trim();
47
+ this._buffer = this._buffer.slice(nl + 1);
48
+ const pending = this._pending.shift();
49
+ if (!pending) continue;
50
+ pending.resolve(line);
51
+ }
52
+ });
53
+
54
+ this.socket.on('error', (err) => {
55
+ this.connected = false;
56
+ this._rejectAllPending(err);
57
+ });
58
+
59
+ this.socket.on('close', () => {
60
+ this.connected = false;
61
+ this._rejectAllPending(new Error('Connection closed'));
62
+ });
63
+ }
64
+
65
+ _rejectAllPending(err) {
66
+ while (this._pending.length) {
67
+ const p = this._pending.shift();
68
+ p.reject(err);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Select a keyspace to use
74
+ * @param {string} keyspace
75
+ * @returns {Promise<void>}
76
+ */
77
+ async selectKeyspace(keyspace) {
78
+ if (!this.connected) throw new Error('Not connected');
79
+ if (!XeondbClient._isIdentifier(keyspace)) throw new Error('Invalid keyspace');
80
+ const res = await this.query(`USE ${keyspace};`);
81
+ if (!res || res.ok !== true) throw new Error(res && res.error ? res.error : 'Failed to select keyspace');
82
+ this.keyspace = keyspace;
83
+ }
84
+
85
+ static _isIdentifier(s) {
86
+ return typeof s === 'string' && /^[A-Za-z_][A-Za-z0-9_]*$/.test(s);
87
+ }
88
+
89
+ queryRaw(cmd) {
90
+ return new Promise((resolve, reject) => {
91
+ if (!this.connected || !this.socket) return reject(new Error('Not connected'));
92
+ const sql = String(cmd || '').trim();
93
+ if (!sql) return reject(new Error('Empty query'));
94
+ this._pending.push({ resolve, reject });
95
+ this.socket.write(sql + '\n', (err) => {
96
+ if (err) {
97
+ this._pending.pop();
98
+ reject(err);
99
+ }
100
+ });
101
+ });
102
+ }
103
+
104
+ async query(cmd) {
105
+ const raw = await this.queryRaw(cmd);
106
+ try {
107
+ return JSON.parse(raw);
108
+ } catch {
109
+ return { ok: false, error: 'Bad JSON', raw };
110
+ }
111
+ }
112
+
113
+ static _cellString(v) {
114
+ if (v === null || v === undefined) return '';
115
+ if (typeof v === 'string') return v;
116
+ if (typeof v === 'number' || typeof v === 'boolean') return String(v);
117
+ try {
118
+ return JSON.stringify(v);
119
+ } catch {
120
+ return String(v);
121
+ }
122
+ }
123
+
124
+ static _truncateCell(s, maxLen = 60) {
125
+ if (s.length <= maxLen) return s;
126
+ return s.slice(0, maxLen - 3) + '...';
127
+ }
128
+
129
+ static _printTable(headers, rows) {
130
+ const widths = headers.map((h) => h.length);
131
+ for (const r of rows) {
132
+ for (let i = 0; i < headers.length; i++) {
133
+ widths[i] = Math.max(widths[i], r[i].length);
134
+ }
135
+ }
136
+ const line = '+' + widths.map((w) => '-'.repeat(w + 2)).join('+') + '+';
137
+ const fmtRow = (cols) =>
138
+ '|' + cols.map((c, i) => ` ${c}${' '.repeat(widths[i] - c.length)} `).join('|') + '|';
139
+ console.log(line);
140
+ console.log(fmtRow(headers));
141
+ console.log(line);
142
+ for (const r of rows) console.log(fmtRow(r));
143
+ console.log(line);
144
+ }
145
+
146
+ async queryTable(cmd) {
147
+ const res = await this.query(cmd);
148
+ if (res && typeof res === 'object' && Array.isArray(res.rows)) {
149
+ if (res.rows.length === 0) {
150
+ console.log('(no rows)');
151
+ return res;
152
+ }
153
+ const headers = Object.keys(res.rows[0] || {});
154
+ const rows = res.rows.map((row) =>
155
+ headers.map((h) => XeondbClient._truncateCell(XeondbClient._cellString(row ? row[h] : undefined)))
156
+ );
157
+ XeondbClient._printTable(headers, rows);
158
+ return res;
159
+ }
160
+ if (res && typeof res === 'object' && Object.prototype.hasOwnProperty.call(res, 'found')) {
161
+ if (!res.found) {
162
+ console.log('(no rows)');
163
+ return res;
164
+ }
165
+ const row = res.row || {};
166
+ const headers = Object.keys(row);
167
+ const values = headers.map((h) => XeondbClient._truncateCell(XeondbClient._cellString(row[h])));
168
+ XeondbClient._printTable(headers, [values]);
169
+ return res;
170
+ }
171
+
172
+ const entries = res && typeof res === 'object' ? Object.entries(res) : [['result', res]];
173
+ const rows = entries.map(([k, v]) => [k, XeondbClient._truncateCell(XeondbClient._cellString(v))]);
174
+ XeondbClient._printTable(['key', 'value'], rows);
175
+ return res;
176
+ }
177
+
178
+ async execTable(cmd) {
179
+ const res = await this.queryTable(cmd);
180
+ return !!(res && typeof res === 'object' && res.ok === true);
181
+ }
182
+
183
+ /**
184
+ * Close the connection
185
+ */
186
+ close() {
187
+ if (this.socket) {
188
+ this.socket.end();
189
+ this.connected = false;
190
+ }
191
+ }
192
+ }
193
+
194
+ module.exports = { XeondbClient };
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "xeondb-driver",
3
+ "version": "0.1.0",
4
+ "description": "Node.js client for Xeondb.",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "scripts": {
8
+ "test": "echo \"No tests specified\" && exit 0"
9
+ },
10
+ "keywords": [
11
+ "database",
12
+ "xeondb",
13
+ "client",
14
+ "nodejs",
15
+ "driver"
16
+ ],
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/Voyrox/Xeondb.git",
20
+ "directory": "packages/nodeJS"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/Voyrox/Xeondb/issues"
24
+ },
25
+ "homepage": "https://github.com/Voyrox/Xeondb#readme",
26
+ "author": "Voyrox",
27
+ "license": "MIT"
28
+ }