astradb 1.0.1
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/README.md +46 -0
- package/bin/cli.js +51 -0
- package/index.js +2 -0
- package/lib/client.js +274 -0
- package/package.json +32 -0
- package/scripts/postinstall.js +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# AstraDB
|
|
2
|
+
|
|
3
|
+
Fast NoSQL database with MongoDB-compatible API.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install astradb
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
const { AstraDB } = require('astradb');
|
|
15
|
+
|
|
16
|
+
// Connect
|
|
17
|
+
const client = new AstraDB('astradb://localhost:8080');
|
|
18
|
+
await client.connect();
|
|
19
|
+
|
|
20
|
+
// Use
|
|
21
|
+
const users = client.db('myapp').collection('users');
|
|
22
|
+
await users.insertOne({ name: 'Alice', age: 25 });
|
|
23
|
+
const all = await users.find();
|
|
24
|
+
console.log(all);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Start Server
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx astradb start
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Server runs on: http://localhost:8080
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
- ✅ LSM-tree storage engine
|
|
39
|
+
- ✅ Hash & B-tree indexes
|
|
40
|
+
- ✅ Write-Ahead Logging (WAL)
|
|
41
|
+
- ✅ Auto-compaction
|
|
42
|
+
- ✅ Authentication & rate limiting
|
|
43
|
+
|
|
44
|
+
## License
|
|
45
|
+
|
|
46
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const { spawn } = require("child_process");
|
|
5
|
+
|
|
6
|
+
function getBinaryName() {
|
|
7
|
+
const platform = process.platform;
|
|
8
|
+
const arch = process.arch;
|
|
9
|
+
|
|
10
|
+
if (platform === "win32") {
|
|
11
|
+
return "astradb-windows-x64.exe";
|
|
12
|
+
}
|
|
13
|
+
if (platform === "darwin") {
|
|
14
|
+
return arch === "arm64"
|
|
15
|
+
? "astradb-darwin-arm64"
|
|
16
|
+
: "astradb-darwin-x64";
|
|
17
|
+
}
|
|
18
|
+
if (platform === "linux") {
|
|
19
|
+
return arch === "arm64"
|
|
20
|
+
? "astradb-linux-arm64"
|
|
21
|
+
: "astradb-linux-x64";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const command = process.argv[2];
|
|
28
|
+
|
|
29
|
+
if (!command || command === "start") {
|
|
30
|
+
const binaryName = getBinaryName();
|
|
31
|
+
const binaryPath = path.join(__dirname, binaryName);
|
|
32
|
+
|
|
33
|
+
console.log("Starting AstraDB...");
|
|
34
|
+
|
|
35
|
+
const child = spawn(binaryPath, {
|
|
36
|
+
stdio: "inherit",
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
child.on("error", (err) => {
|
|
40
|
+
console.error("Failed to start AstraDB:", err);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
} else if (command === "version") {
|
|
44
|
+
console.log("AstraDB CLI v1.0.0");
|
|
45
|
+
} else {
|
|
46
|
+
console.log(`
|
|
47
|
+
Usage:
|
|
48
|
+
astradb start
|
|
49
|
+
astradb version
|
|
50
|
+
`);
|
|
51
|
+
}
|
package/index.js
ADDED
package/lib/client.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AstraDB Client - Final Version
|
|
3
|
+
* Works with and without auth — exactly like MongoDB
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const axios = require('axios');
|
|
7
|
+
|
|
8
|
+
class AstraDB {
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} connectionString
|
|
11
|
+
*
|
|
12
|
+
* No auth: 'astradb://localhost:8080'
|
|
13
|
+
* With auth: 'astradb://admin:password@localhost:8080'
|
|
14
|
+
*/
|
|
15
|
+
constructor(connectionString) {
|
|
16
|
+
const parsed = this._parse(connectionString);
|
|
17
|
+
|
|
18
|
+
this.host = parsed.host;
|
|
19
|
+
this.port = parsed.port;
|
|
20
|
+
this.username = parsed.username; // null if no auth
|
|
21
|
+
this.password = parsed.password; // null if no auth
|
|
22
|
+
this.baseURL = `http://${parsed.host}:${parsed.port}`;
|
|
23
|
+
this._apiKey = null;
|
|
24
|
+
this._ready = false;
|
|
25
|
+
|
|
26
|
+
this._http = axios.create({
|
|
27
|
+
baseURL: this.baseURL,
|
|
28
|
+
timeout: 30000,
|
|
29
|
+
headers: { 'Content-Type': 'application/json' }
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
_parse(str) {
|
|
34
|
+
if (!str) throw new Error('Connection string required');
|
|
35
|
+
|
|
36
|
+
// Remove protocol
|
|
37
|
+
const s = str.replace(/^astradb:\/\//, '').replace(/^mongodb:\/\//, '');
|
|
38
|
+
|
|
39
|
+
const atIndex = s.lastIndexOf('@');
|
|
40
|
+
|
|
41
|
+
if (atIndex === -1) {
|
|
42
|
+
// No credentials: 'localhost:8080'
|
|
43
|
+
const parts = s.split(':');
|
|
44
|
+
return { host: parts[0], port: parseInt(parts[1] || 8080), username: null, password: null };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Has credentials: 'admin:pass@localhost:8080'
|
|
48
|
+
const userInfo = s.substring(0, atIndex);
|
|
49
|
+
const hostInfo = s.substring(atIndex + 1);
|
|
50
|
+
const colonIdx = userInfo.indexOf(':');
|
|
51
|
+
const username = decodeURIComponent(userInfo.substring(0, colonIdx));
|
|
52
|
+
const password = decodeURIComponent(userInfo.substring(colonIdx + 1));
|
|
53
|
+
const hostParts = hostInfo.split(':');
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
host: hostParts[0],
|
|
57
|
+
port: parseInt(hostParts[1] || 8080),
|
|
58
|
+
username,
|
|
59
|
+
password
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Connect to server
|
|
65
|
+
* - No credentials → just check server is alive
|
|
66
|
+
* - With credentials → login and get session
|
|
67
|
+
*/
|
|
68
|
+
async connect() {
|
|
69
|
+
// Check server is alive
|
|
70
|
+
try {
|
|
71
|
+
await this._http.get('/health');
|
|
72
|
+
} catch {
|
|
73
|
+
throw new Error(`Cannot connect to AstraDB at ${this.baseURL}.\nIs the server running?\n cd testDB && go run cmd/astradb/main.go`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Login if credentials provided
|
|
77
|
+
if (this.username && this.password) {
|
|
78
|
+
try {
|
|
79
|
+
const res = await this._http.post('/api/auth/login', {
|
|
80
|
+
username: this.username,
|
|
81
|
+
password: this.password
|
|
82
|
+
});
|
|
83
|
+
this._apiKey = res.data.apiKey;
|
|
84
|
+
this._http.defaults.headers['X-API-Key'] = this._apiKey;
|
|
85
|
+
} catch (err) {
|
|
86
|
+
if (err.response && err.response.status === 401) {
|
|
87
|
+
throw new Error(`Wrong username or password for "${this.username}"`);
|
|
88
|
+
}
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this._ready = true;
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async close() {
|
|
98
|
+
this._apiKey = null;
|
|
99
|
+
this._ready = false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async ping() {
|
|
103
|
+
try {
|
|
104
|
+
const res = await this._http.get('/health');
|
|
105
|
+
return res.data.status === 'ok';
|
|
106
|
+
} catch { return false; }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
db(dbName) {
|
|
110
|
+
if (!this._ready) throw new Error('Call await client.connect() first');
|
|
111
|
+
return new Db(this._http, dbName);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async listDatabases() {
|
|
115
|
+
const res = await this._http.get('/api/databases');
|
|
116
|
+
return (res.data.databases || []).map(name => ({ name }));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ────────────────────────────────
|
|
121
|
+
class Db {
|
|
122
|
+
constructor(http, dbName) {
|
|
123
|
+
this._http = http;
|
|
124
|
+
this._dbName = dbName;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
collection(name) { return new Collection(this._http, this._dbName, name); }
|
|
128
|
+
|
|
129
|
+
async listCollections() {
|
|
130
|
+
const res = await this._http.get(`/api/collections?db=${this._dbName}`);
|
|
131
|
+
return (res.data.collections || []).map(n => ({ name: n }));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async stats() {
|
|
135
|
+
const res = await this._http.get(`/api/stats?db=${this._dbName}`);
|
|
136
|
+
return res.data.stats;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async dropDatabase() {
|
|
140
|
+
const cols = await this.listCollections();
|
|
141
|
+
for (const { name } of cols) await this.collection(name).drop();
|
|
142
|
+
return { ok: 1 };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ────────────────────────────────
|
|
147
|
+
class Collection {
|
|
148
|
+
constructor(http, db, col) {
|
|
149
|
+
this._http = http;
|
|
150
|
+
this._db = db;
|
|
151
|
+
this._col = col;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ── INSERT ──
|
|
155
|
+
async insertOne(doc) {
|
|
156
|
+
const r = await this._http.post('/api/insert', { db: this._db, collection: this._col, data: doc });
|
|
157
|
+
return { acknowledged: true, insertedId: r.data.id };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async insertMany(docs) {
|
|
161
|
+
const results = await Promise.all(docs.map(d => this.insertOne(d)));
|
|
162
|
+
return { acknowledged: true, insertedCount: results.length, insertedIds: results.map(r => r.insertedId) };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ── FIND ──
|
|
166
|
+
async find(filter = {}, options = {}) {
|
|
167
|
+
const r = await this._http.post('/api/query', {
|
|
168
|
+
db: this._db, collection: this._col, filter,
|
|
169
|
+
sort: options.sort || null, limit: options.limit || null,
|
|
170
|
+
skip: options.skip || null, projection: options.projection || null
|
|
171
|
+
});
|
|
172
|
+
return r.data.data || [];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async findOne(filter = {}, options = {}) {
|
|
176
|
+
const docs = await this.find(filter, { ...options, limit: 1 });
|
|
177
|
+
return docs[0] || null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async findById(id) { return this.findOne({ _id: id }); }
|
|
181
|
+
async countDocuments(filter = {}) { return (await this.find(filter)).length; }
|
|
182
|
+
async estimatedDocumentCount() { return this.countDocuments(); }
|
|
183
|
+
async exists(filter) { return (await this.findOne(filter)) !== null; }
|
|
184
|
+
|
|
185
|
+
async distinct(field, filter = {}) {
|
|
186
|
+
const docs = await this.find(filter);
|
|
187
|
+
const seen = new Set();
|
|
188
|
+
return docs.reduce((acc, doc) => {
|
|
189
|
+
const v = this._get(doc, field);
|
|
190
|
+
if (v !== undefined) {
|
|
191
|
+
const k = JSON.stringify(v);
|
|
192
|
+
if (!seen.has(k)) { seen.add(k); acc.push(v); }
|
|
193
|
+
}
|
|
194
|
+
return acc;
|
|
195
|
+
}, []);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ── UPDATE ──
|
|
199
|
+
async updateOne(filter, update) {
|
|
200
|
+
const r = await this._http.post('/api/update', { db: this._db, collection: this._col, filter, update, multi: false });
|
|
201
|
+
return { acknowledged: true, matchedCount: r.data.updated, modifiedCount: r.data.updated };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async updateMany(filter, update) {
|
|
205
|
+
const r = await this._http.post('/api/update', { db: this._db, collection: this._col, filter, update, multi: true });
|
|
206
|
+
return { acknowledged: true, matchedCount: r.data.updated, modifiedCount: r.data.updated };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async replaceOne(filter, doc) { return this.updateOne(filter, { $set: doc }); }
|
|
210
|
+
|
|
211
|
+
async findOneAndUpdate(filter, update, opts = {}) {
|
|
212
|
+
const before = await this.findOne(filter);
|
|
213
|
+
if (!before) return { value: null };
|
|
214
|
+
await this.updateOne(filter, update);
|
|
215
|
+
const val = opts.returnDocument === 'after' ? await this.findOne(filter) : before;
|
|
216
|
+
return { value: val, ok: 1 };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ── DELETE ──
|
|
220
|
+
async deleteOne(filter) {
|
|
221
|
+
const r = await this._http.post('/api/delete', { db: this._db, collection: this._col, filter, multi: false });
|
|
222
|
+
return { acknowledged: true, deletedCount: r.data.deleted };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async deleteMany(filter = {}) {
|
|
226
|
+
const r = await this._http.post('/api/delete', { db: this._db, collection: this._col, filter, multi: true });
|
|
227
|
+
return { acknowledged: true, deletedCount: r.data.deleted };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async findOneAndDelete(filter) {
|
|
231
|
+
const doc = await this.findOne(filter);
|
|
232
|
+
if (!doc) return { value: null };
|
|
233
|
+
await this.deleteOne(filter);
|
|
234
|
+
return { value: doc, ok: 1 };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async drop() { await this.deleteMany({}); return true; }
|
|
238
|
+
|
|
239
|
+
// ── INDEXES ──
|
|
240
|
+
async createIndex(fieldOrSpec, opts = {}) {
|
|
241
|
+
const fields = Array.isArray(fieldOrSpec) ? fieldOrSpec
|
|
242
|
+
: typeof fieldOrSpec === 'string' ? [fieldOrSpec]
|
|
243
|
+
: Object.keys(fieldOrSpec);
|
|
244
|
+
await this._http.post('/api/createIndex', {
|
|
245
|
+
db: this._db, collection: this._col, fields,
|
|
246
|
+
type: opts.btree ? 'btree' : (opts.type || 'hash'),
|
|
247
|
+
unique: opts.unique || false
|
|
248
|
+
});
|
|
249
|
+
return fields.join('_') + '_1';
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async createIndexes(specs) {
|
|
253
|
+
return Promise.all(specs.map(s => this.createIndex(s.key, s)));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ── BULK ──
|
|
257
|
+
async bulkWrite(ops) {
|
|
258
|
+
const results = [];
|
|
259
|
+
for (const op of ops) {
|
|
260
|
+
if (op.insertOne) results.push(await this.insertOne(op.insertOne.document));
|
|
261
|
+
if (op.updateOne) results.push(await this.updateOne(op.updateOne.filter, op.updateOne.update));
|
|
262
|
+
if (op.updateMany) results.push(await this.updateMany(op.updateMany.filter, op.updateMany.update));
|
|
263
|
+
if (op.deleteOne) results.push(await this.deleteOne(op.deleteOne.filter));
|
|
264
|
+
if (op.deleteMany) results.push(await this.deleteMany(op.deleteMany.filter));
|
|
265
|
+
}
|
|
266
|
+
return { acknowledged: true, results };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
_get(obj, path) {
|
|
270
|
+
return path.split('.').reduce((v, k) => v && typeof v === 'object' ? v[k] : undefined, obj);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
module.exports = { AstraDB, Db, Collection };
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "astradb",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Fast NoSQL database with MongoDB-compatible API",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"astradb": "bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"postinstall": "node scripts/postinstall.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"database",
|
|
14
|
+
"nosql",
|
|
15
|
+
"mongodb",
|
|
16
|
+
"astradb",
|
|
17
|
+
"lsm-tree"
|
|
18
|
+
],
|
|
19
|
+
"author": "AnshulKhichi",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"homepage": "https://github.com/AnshulKhichi11/astradb",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/AnshulKhichi11/astradb.git"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"axios": "^1.6.0"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=14.0.0"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const axios = require("axios");
|
|
4
|
+
|
|
5
|
+
const BASE_URL = "https://github.com/AnshulKhichi11/AstraDB/releases/download/v1.0.0";
|
|
6
|
+
|
|
7
|
+
function getBinaryName() {
|
|
8
|
+
const platform = process.platform;
|
|
9
|
+
|
|
10
|
+
if (platform === "win32") {
|
|
11
|
+
return "astradb-windows-x64.exe";
|
|
12
|
+
}
|
|
13
|
+
if (platform === "darwin") {
|
|
14
|
+
return "astradb-darwin-x64";
|
|
15
|
+
}
|
|
16
|
+
if (platform === "linux") {
|
|
17
|
+
return "astradb-linux-x64";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
throw new Error("Unsupported platform");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function downloadBinary() {
|
|
24
|
+
const binaryName = getBinaryName();
|
|
25
|
+
const url = `${BASE_URL}/${binaryName}`;
|
|
26
|
+
|
|
27
|
+
const binDir = path.join(__dirname, "..", "bin");
|
|
28
|
+
const binaryPath = path.join(binDir, binaryName);
|
|
29
|
+
|
|
30
|
+
if (!fs.existsSync(binDir)) {
|
|
31
|
+
fs.mkdirSync(binDir);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log("Downloading AstraDB from:", url);
|
|
35
|
+
|
|
36
|
+
const response = await axios({
|
|
37
|
+
method: "GET",
|
|
38
|
+
url,
|
|
39
|
+
responseType: "stream",
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const writer = fs.createWriteStream(binaryPath);
|
|
43
|
+
|
|
44
|
+
response.data.pipe(writer);
|
|
45
|
+
|
|
46
|
+
writer.on("finish", () => {
|
|
47
|
+
fs.chmodSync(binaryPath, 0o755);
|
|
48
|
+
console.log("AstraDB binary installed successfully");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
writer.on("error", (err) => {
|
|
52
|
+
console.error("Download failed:", err);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
downloadBinary();
|