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 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
@@ -0,0 +1,2 @@
1
+ const { AstraDB, Db, Collection } = require('./lib/client');
2
+ module.exports = { AstraDB, Db, Collection };
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();