@liorandb/core 1.0.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.md ADDED
@@ -0,0 +1,41 @@
1
+ LIORANDB LICENSE
2
+
3
+ Copyright (c) 2025 Swaraj Puppalwar
4
+ All rights reserved.
5
+
6
+ Permission is hereby granted to any person obtaining a copy of this software and associated documentation files (the "Software") to use, copy, modify, and distribute the Software for PERSONAL, NON-COMMERCIAL PURPOSES ONLY, subject to the following conditions:
7
+
8
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9
+
10
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11
+
12
+ ---
13
+
14
+ SPECIAL CONDITIONS FOR LIORANDB
15
+
16
+ Notwithstanding the general permission above, the following additional conditions apply specifically to LioranDB:
17
+
18
+ 1. Personal Use
19
+ Permission is granted to use the Software for personal projects, educational purposes, experimentation, and non-commercial development only.
20
+
21
+ 2. Commercial and Production Use
22
+ Any use of the Software for commercial purposes, including but not limited to:
23
+
24
+ * deployment in production environments,
25
+ * integration into paid products or services,
26
+ * hosting for third parties,
27
+ * resale or monetization of any form,
28
+
29
+ is strictly prohibited without obtaining a separate commercial license from the official LioranDB SaaS platform or direct written authorization from Swaraj Puppalwar or Lioran Group.
30
+
31
+ 3. No Resale or Redistribution
32
+ You may not sell, sublicense, redistribute, or repackage the Software as a standalone product or as part of any commercial offering without explicit written permission and a valid commercial agreement.
33
+
34
+ 4. Ownership
35
+ All intellectual property rights in the Software, including but not limited to source code, architecture, brand identity, and system design, remain the exclusive property of Swaraj Puppalwar and Lioran Group.
36
+
37
+ By using this Software, you agree to comply fully with this license and all associated conditions.
38
+
39
+ This license supersedes and replaces any previous licensing terms related to LioranDB.
40
+
41
+ © 2025 Swaraj Puppalwar & Lioran Group. All rights reserved.
package/README.md ADDED
@@ -0,0 +1,224 @@
1
+ # @liorandb/core
2
+
3
+ **LioranDB Core Module** – Lightweight, local-first, peer-to-peer database management for Node.js.
4
+
5
+ This is the **core system-level module** of LioranDB. It provides foundational database management functionality, including collections, queries, updates, encryption, and environment setup. **Note:** This is not the final database product, but a core module designed to be used in larger systems.
6
+
7
+ ---
8
+
9
+ ## Table of Contents
10
+
11
+ * [Installation](#installation)
12
+ * [Overview](#overview)
13
+ * [Getting Started](#getting-started)
14
+ * [API Reference](#api-reference)
15
+
16
+ * [LioranManager](#lioranmanager)
17
+ * [LioranDB](#liorandb)
18
+ * [Collection](#collection)
19
+ * [Query Operators](#query-operators)
20
+ * [Update Operators](#update-operators)
21
+ * [Utilities](#utilities)
22
+ * [Encryption](#encryption)
23
+ * [Environment Setup](#environment-setup)
24
+ * [License](#license)
25
+
26
+ ---
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ npm install @liorandb/core
32
+ ```
33
+
34
+ > Node.js v18+ recommended.
35
+
36
+ ---
37
+
38
+ ## Overview
39
+
40
+ `@liorandb/core` provides:
41
+
42
+ * Local-first, file-based database directories.
43
+ * MongoDB-style API (`db`, `collection`, `insertOne`, `find`, `updateOne`, etc.).
44
+ * Peer-to-peer-friendly design.
45
+ * Data encryption at rest.
46
+ * Automatic environment configuration.
47
+ * TypeScript typings for full developer support.
48
+
49
+ This module is intended for **Node.js projects** and can serve as the core database engine for larger LioranDB systems.
50
+
51
+ ---
52
+
53
+ ## Getting Started
54
+
55
+ ```javascript
56
+ import { LioranManager } from "@liorandb/core";
57
+
58
+ async function main() {
59
+ const manager = new LioranManager();
60
+ const db = await manager.db("myDatabase");
61
+
62
+ const users = db.collection("users");
63
+
64
+ // Insert a document
65
+ const user = await users.insertOne({ name: "Alice", age: 25 });
66
+
67
+ // Query documents
68
+ const results = await users.find({ age: { $gte: 18 } });
69
+
70
+ console.log(results);
71
+ }
72
+
73
+ main();
74
+ ```
75
+
76
+ ---
77
+
78
+ ## API Reference
79
+
80
+ ### LioranManager
81
+
82
+ Manages databases and provides MongoDB-style client access.
83
+
84
+ ```ts
85
+ class LioranManager {
86
+ rootPath: string;
87
+ db(name: string): Promise<LioranDB>;
88
+ createDatabase(name: string): Promise<LioranDB>;
89
+ openDatabase(name: string): Promise<LioranDB>;
90
+ closeDatabase(name: string): Promise<void>;
91
+ renameDatabase(oldName: string, newName: string): Promise<boolean>;
92
+ deleteDatabase(name: string): Promise<boolean>;
93
+ dropDatabase(name: string): Promise<boolean>;
94
+ listDatabases(): Promise<string[]>;
95
+ }
96
+ ```
97
+
98
+ **Example:**
99
+
100
+ ```javascript
101
+ const manager = new LioranManager();
102
+ await manager.createDatabase("testDB");
103
+ const db = await manager.db("testDB");
104
+ ```
105
+
106
+ ### LioranDB
107
+
108
+ Represents a single database instance with multiple collections.
109
+
110
+ ```ts
111
+ class LioranDB {
112
+ basePath: string;
113
+ dbName: string;
114
+ collection<T>(name: string): Collection<T>;
115
+ createCollection(name: string): Promise<boolean>;
116
+ deleteCollection(name: string): Promise<boolean>;
117
+ dropCollection(name: string): Promise<boolean>;
118
+ renameCollection(oldName: string, newName: string): Promise<boolean>;
119
+ listCollections(): Promise<string[]>;
120
+ }
121
+ ```
122
+
123
+ **Example:**
124
+
125
+ ```javascript
126
+ const users = db.collection("users");
127
+ await db.createCollection("products");
128
+ const collections = await db.listCollections();
129
+ ```
130
+
131
+ ### Collection
132
+
133
+ Handles documents within a database.
134
+
135
+ ```ts
136
+ class Collection<T extends { _id?: string }> {
137
+ insertOne(doc: T): Promise<T>;
138
+ insertMany(docs: T[]): Promise<T[]>;
139
+ find(query?: FilterQuery<T>): Promise<T[]>;
140
+ findOne(query?: FilterQuery<T>): Promise<T | null>;
141
+ updateOne(filter: FilterQuery<T>, update: UpdateQuery<T>, options?: { upsert?: boolean }): Promise<T | null>;
142
+ updateMany(filter: FilterQuery<T>, update: UpdateQuery<T>): Promise<T[]>;
143
+ deleteOne(filter: FilterQuery<T>): Promise<boolean>;
144
+ deleteMany(filter: FilterQuery<T>): Promise<number>;
145
+ countDocuments(filter?: FilterQuery<T>): Promise<number>;
146
+ close(): Promise<void>;
147
+ }
148
+ ```
149
+
150
+ **Example:**
151
+
152
+ ```javascript
153
+ await users.insertOne({ name: "Bob", age: 30 });
154
+ const adults = await users.find({ age: { $gte: 18 } });
155
+ await users.updateOne({ name: "Bob" }, { $inc: { age: 1 } });
156
+ await users.deleteOne({ name: "Alice" });
157
+ ```
158
+
159
+ ### Query Operators
160
+
161
+ * `$gt`, `$gte`, `$lt`, `$lte`, `$ne`, `$eq`, `$in`
162
+
163
+ **Example:**
164
+
165
+ ```javascript
166
+ users.find({ age: { $gte: 18, $lt: 65 } });
167
+ ```
168
+
169
+ ### Update Operators
170
+
171
+ * `$set` – set field values
172
+ * `$inc` – increment numeric fields
173
+
174
+ **Example:**
175
+
176
+ ```javascript
177
+ users.updateOne({ name: "Alice" }, { $set: { city: "Mumbai" }, $inc: { age: 1 } });
178
+ ```
179
+
180
+ ### Utilities
181
+
182
+ ```ts
183
+ function getBaseDBFolder(): string;
184
+ ```
185
+
186
+ Returns the root folder for LioranDB databases. Automatically sets environment variables if missing.
187
+
188
+ ---
189
+
190
+ ## Encryption
191
+
192
+ All documents are encrypted using **AES-256-GCM**.
193
+
194
+ * Uses a master key stored in `.secureKey` within the base folder.
195
+ * Data is encrypted automatically before storage and decrypted on retrieval.
196
+
197
+ **Utility Functions:**
198
+
199
+ * `encryptData(obj)`
200
+ * `decryptData(encStr)`
201
+
202
+ **Master Key Management:**
203
+
204
+ * Managed via `getMasterKey()`
205
+ * Auto-generates 256-bit key if not found.
206
+
207
+ ---
208
+
209
+ ## Environment Setup
210
+
211
+ * `getBaseDBFolder()` ensures `LIORANDB_PATH` is set.
212
+ * Auto-generates scripts for **Windows PowerShell** or **Linux/macOS bash**.
213
+ * Guides users to set system-wide environment variables if missing.
214
+
215
+ ---
216
+
217
+ ## License
218
+
219
+ **Author:** Swaraj Puppalwar
220
+ **License:** LIORANDB LICENSE
221
+
222
+ ---
223
+
224
+ **Keywords:** p2p-database, lioran, liorandb, p2p-db, peer-to-peer-db, peer-to-peer-database, localfirst-db, localfirst-database
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@liorandb/core",
3
+ "version": "1.0.0",
4
+ "description": "LioranDB is a lightweight, local-first, peer-to-peer, file-based database with a MongoDB-style Node.js API and a simple CLI for seamless distributed development.",
5
+ "main": "./src/index.js",
6
+ "types": "./src/index.d.ts",
7
+ "type": "module",
8
+ "scripts": {},
9
+ "bin": {},
10
+ "keywords": [
11
+ "p2p-database",
12
+ "lioran",
13
+ "liorandb",
14
+ "p2p-db",
15
+ "peer-to-peer-db",
16
+ "peer-to-peer-database",
17
+ "localfirst-db",
18
+ "localfirst-database"
19
+ ],
20
+ "author": "Swaraj Puppalwar",
21
+ "license": "LIORANDB LICENSE",
22
+ "dependencies": {
23
+ "classic-level": "^1.3.0",
24
+ "crypto": "^1.0.1",
25
+ "uuid": "^9.0.0"
26
+ }
27
+ }
@@ -0,0 +1,120 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import { LioranDB } from "./core/database.js";
4
+
5
+ export class LioranManager {
6
+ constructor() {
7
+ this.rootPath = path.join(
8
+ process.env.LIORANDB_PATH ||
9
+ path.join(process.env.HOME || process.env.USERPROFILE),
10
+ "LioranDB",
11
+ "db"
12
+ );
13
+
14
+ if (!fs.existsSync(this.rootPath)) {
15
+ fs.mkdirSync(this.rootPath, { recursive: true });
16
+ }
17
+
18
+ this.openDBs = new Map();
19
+ }
20
+
21
+ // -----------------------------------------
22
+ // MongoDB-style: client.db("name")
23
+ // Auto-create database if not exists
24
+ // -----------------------------------------
25
+ async db(name) {
26
+ return this.openDatabase(name);
27
+ }
28
+
29
+ // -----------------------------------------
30
+ // Create a new database (returns db instance)
31
+ // -----------------------------------------
32
+ async createDatabase(name) {
33
+ const dbPath = path.join(this.rootPath, name);
34
+
35
+ if (fs.existsSync(dbPath)) {
36
+ throw new Error(`Database "${name}" already exists`);
37
+ }
38
+
39
+ await fs.promises.mkdir(dbPath, { recursive: true });
40
+ return this.openDatabase(name);
41
+ }
42
+
43
+ // -----------------------------------------
44
+ // Open DB - AUTO CREATE if missing
45
+ // -----------------------------------------
46
+ async openDatabase(name) {
47
+ const dbPath = path.join(this.rootPath, name);
48
+
49
+ // Auto-create if not exists
50
+ if (!fs.existsSync(dbPath)) {
51
+ await fs.promises.mkdir(dbPath, { recursive: true });
52
+ }
53
+
54
+ if (this.openDBs.has(name)) {
55
+ return this.openDBs.get(name);
56
+ }
57
+
58
+ const db = new LioranDB(dbPath, name, this);
59
+ this.openDBs.set(name, db);
60
+
61
+ return db;
62
+ }
63
+
64
+ // -----------------------------------------
65
+ // Close database
66
+ // -----------------------------------------
67
+ async closeDatabase(name) {
68
+ if (!this.openDBs.has(name)) return;
69
+
70
+ const db = this.openDBs.get(name);
71
+
72
+ for (const [, col] of db.collections.entries()) {
73
+ await col.close();
74
+ }
75
+
76
+ this.openDBs.delete(name);
77
+ }
78
+
79
+ // -----------------------------------------
80
+ // Rename database
81
+ // -----------------------------------------
82
+ async renameDatabase(oldName, newName) {
83
+ const oldPath = path.join(this.rootPath, oldName);
84
+ const newPath = path.join(this.rootPath, newName);
85
+
86
+ if (!fs.existsSync(oldPath)) throw new Error(`Database "${oldName}" not found`);
87
+ if (fs.existsSync(newPath)) throw new Error(`Database "${newName}" already exists`);
88
+
89
+ await this.closeDatabase(oldName);
90
+ await fs.promises.rename(oldPath, newPath);
91
+
92
+ return true;
93
+ }
94
+
95
+ // -----------------------------------------
96
+ // Delete / Drop database
97
+ // -----------------------------------------
98
+ async deleteDatabase(name) {
99
+ return this.dropDatabase(name);
100
+ }
101
+
102
+ async dropDatabase(name) {
103
+ const dbPath = path.join(this.rootPath, name);
104
+
105
+ if (!fs.existsSync(dbPath)) return false;
106
+
107
+ await this.closeDatabase(name);
108
+ await fs.promises.rm(dbPath, { recursive: true, force: true });
109
+
110
+ return true;
111
+ }
112
+
113
+ // -----------------------------------------
114
+ // List all databases
115
+ // -----------------------------------------
116
+ async listDatabases() {
117
+ const items = await fs.promises.readdir(this.rootPath, { withFileTypes: true });
118
+ return items.filter(i => i.isDirectory()).map(i => i.name);
119
+ }
120
+ }
@@ -0,0 +1,160 @@
1
+ import { ClassicLevel } from "classic-level";
2
+ import { matchDocument, applyUpdate } from "./query.js";
3
+ import { v4 as uuid } from "uuid";
4
+ import { encryptData, decryptData } from "../utils/encryption.js";
5
+
6
+ export class Collection {
7
+ constructor(dir) {
8
+ this.dir = dir;
9
+ this.db = new ClassicLevel(dir, { valueEncoding: "json" });
10
+
11
+ // Queue system
12
+ this.queue = Promise.resolve(); // start with a resolved promise
13
+ }
14
+
15
+ // Queue wrapper
16
+ _enqueue(task) {
17
+ // Add task to the queue
18
+ this.queue = this.queue.then(() => task()).catch(console.error);
19
+ return this.queue;
20
+ }
21
+
22
+ async close() {
23
+ if (this.db) {
24
+ try {
25
+ await this.db.close();
26
+ } catch (err) {
27
+ console.warn("Warning: close() failed", err);
28
+ }
29
+ }
30
+ }
31
+
32
+ async insertOne(doc) {
33
+ return this._enqueue(async () => {
34
+ const _id = doc._id ?? uuid();
35
+ const final = { _id, ...doc };
36
+ const encrypted = encryptData(final);
37
+ await this.db.put(String(_id), encrypted);
38
+ return final;
39
+ });
40
+ }
41
+
42
+ async insertMany(docs = []) {
43
+ return this._enqueue(async () => {
44
+ const ops = [];
45
+ const out = [];
46
+
47
+ for (const d of docs) {
48
+ const _id = d._id ?? uuid();
49
+ const final = { _id, ...d };
50
+ const encrypted = encryptData(final);
51
+ ops.push({ type: "put", key: String(_id), value: encrypted });
52
+ out.push(final);
53
+ }
54
+
55
+ await this.db.batch(ops);
56
+ return out;
57
+ });
58
+ }
59
+
60
+ async find(query = {}) {
61
+ return this._enqueue(async () => {
62
+ const out = [];
63
+ for await (const [, encValue] of this.db.iterator()) {
64
+ const value = decryptData(encValue);
65
+ if (matchDocument(value, query)) out.push(value);
66
+ }
67
+ return out;
68
+ });
69
+ }
70
+
71
+ async findOne(query = {}) {
72
+ return this._enqueue(async () => {
73
+ for await (const [, encValue] of this.db.iterator()) {
74
+ const value = decryptData(encValue);
75
+ if (matchDocument(value, query)) return value;
76
+ }
77
+ return null;
78
+ });
79
+ }
80
+
81
+ async updateOne(filter = {}, update = {}, options = { upsert: false }) {
82
+ return this._enqueue(async () => {
83
+ for await (const [key, encValue] of this.db.iterator()) {
84
+ const value = decryptData(encValue);
85
+ if (matchDocument(value, filter)) {
86
+ const updated = applyUpdate(value, update);
87
+ updated._id = value._id;
88
+ const encrypted = encryptData(updated);
89
+ await this.db.put(key, encrypted);
90
+ return updated;
91
+ }
92
+ }
93
+
94
+ if (options.upsert) {
95
+ const newDoc = applyUpdate(filter, update);
96
+ newDoc._id = newDoc._id ?? uuid();
97
+ const encrypted = encryptData(newDoc);
98
+ await this.db.put(String(newDoc._id), encrypted);
99
+ return newDoc;
100
+ }
101
+
102
+ return null;
103
+ });
104
+ }
105
+
106
+ async updateMany(filter = {}, update = {}) {
107
+ return this._enqueue(async () => {
108
+ const updated = [];
109
+ for await (const [key, encValue] of this.db.iterator()) {
110
+ const value = decryptData(encValue);
111
+ if (matchDocument(value, filter)) {
112
+ const newDoc = applyUpdate(value, update);
113
+ newDoc._id = value._id;
114
+ const encrypted = encryptData(newDoc);
115
+ await this.db.put(key, encrypted);
116
+ updated.push(newDoc);
117
+ }
118
+ }
119
+ return updated;
120
+ });
121
+ }
122
+
123
+ async deleteOne(filter = {}) {
124
+ return this._enqueue(async () => {
125
+ for await (const [key, encValue] of this.db.iterator()) {
126
+ const value = decryptData(encValue);
127
+ if (matchDocument(value, filter)) {
128
+ await this.db.del(key);
129
+ return true;
130
+ }
131
+ }
132
+ return false;
133
+ });
134
+ }
135
+
136
+ async deleteMany(filter = {}) {
137
+ return this._enqueue(async () => {
138
+ let count = 0;
139
+ for await (const [key, encValue] of this.db.iterator()) {
140
+ const value = decryptData(encValue);
141
+ if (matchDocument(value, filter)) {
142
+ await this.db.del(key);
143
+ count++;
144
+ }
145
+ }
146
+ return count;
147
+ });
148
+ }
149
+
150
+ async countDocuments(filter = {}) {
151
+ return this._enqueue(async () => {
152
+ let c = 0;
153
+ for await (const [, encValue] of this.db.iterator()) {
154
+ const value = decryptData(encValue);
155
+ if (matchDocument(value, filter)) c++;
156
+ }
157
+ return c;
158
+ });
159
+ }
160
+ }
@@ -0,0 +1,109 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import { Collection } from "./collection.js";
4
+
5
+ export class LioranDB {
6
+ constructor(basePath, dbName, manager) {
7
+ this.basePath = basePath;
8
+ this.dbName = dbName;
9
+ this.manager = manager;
10
+ this.collections = new Map();
11
+
12
+ if (!fs.existsSync(basePath)) {
13
+ fs.mkdirSync(basePath, { recursive: true });
14
+ }
15
+ }
16
+
17
+ /** Get or auto-create collection object */
18
+ collection(name) {
19
+ if (this.collections.has(name)) {
20
+ return this.collections.get(name);
21
+ }
22
+
23
+ const colPath = path.join(this.basePath, name);
24
+
25
+ // auto-create directory
26
+ if (!fs.existsSync(colPath)) {
27
+ fs.mkdirSync(colPath, { recursive: true });
28
+ }
29
+
30
+ const col = new Collection(colPath);
31
+ this.collections.set(name, col);
32
+ return col;
33
+ }
34
+
35
+ /** Create collection manually */
36
+ async createCollection(name) {
37
+ const colPath = path.join(this.basePath, name);
38
+
39
+ if (fs.existsSync(colPath)) {
40
+ throw new Error("Collection already exists");
41
+ }
42
+
43
+ // create folder
44
+ await fs.promises.mkdir(colPath, { recursive: true });
45
+
46
+ // load into memory map
47
+ const col = new Collection(colPath);
48
+ this.collections.set(name, col);
49
+
50
+ return true;
51
+ }
52
+
53
+ /** Delete collection fully */
54
+ async deleteCollection(name) {
55
+ const colPath = path.join(this.basePath, name);
56
+
57
+ if (!fs.existsSync(colPath)) {
58
+ throw new Error("Collection does not exist");
59
+ }
60
+
61
+ // close LevelDB instance if opened
62
+ if (this.collections.has(name)) {
63
+ const col = this.collections.get(name);
64
+ await col.close().catch(() => {});
65
+ await col.db.clear().catch(() => {});
66
+ this.collections.delete(name);
67
+ }
68
+
69
+ // force delete directory
70
+ await fs.promises.rm(colPath, { recursive: true, force: true });
71
+
72
+ return true;
73
+ }
74
+
75
+ /** Rename collection */
76
+ async renameCollection(oldName, newName) {
77
+ const oldPath = path.join(this.basePath, oldName);
78
+ const newPath = path.join(this.basePath, newName);
79
+
80
+ if (!fs.existsSync(oldPath)) throw new Error("Collection does not exist");
81
+ if (fs.existsSync(newPath)) throw new Error("New collection name exists");
82
+
83
+ if (this.collections.has(oldName)) {
84
+ await this.collections.get(oldName).close().catch(() => {});
85
+ this.collections.delete(oldName);
86
+ }
87
+
88
+ await fs.promises.rename(oldPath, newPath);
89
+
90
+ const newCol = new Collection(newPath);
91
+ this.collections.set(newName, newCol);
92
+
93
+ return true;
94
+ }
95
+
96
+ /** Drop a collection (alias deleteCollection) */
97
+ async dropCollection(name) {
98
+ return this.deleteCollection(name);
99
+ }
100
+
101
+ /** List all collections */
102
+ async listCollections() {
103
+ const dirs = await fs.promises.readdir(this.basePath, { withFileTypes: true });
104
+
105
+ return dirs
106
+ .filter(dirent => dirent.isDirectory())
107
+ .map(dirent => dirent.name);
108
+ }
109
+ }
@@ -0,0 +1,65 @@
1
+ function getByPath(obj, path) {
2
+ return path.split(".").reduce((o, p) => (o ? o[p] : undefined), obj);
3
+ }
4
+
5
+ export function matchDocument(doc, query) {
6
+ for (const key of Object.keys(query)) {
7
+ const cond = query[key];
8
+ const val = getByPath(doc, key);
9
+
10
+ if (cond && typeof cond === "object" && !Array.isArray(cond)) {
11
+ for (const op of Object.keys(cond)) {
12
+ const v = cond[op];
13
+ if (op === "$gt" && !(val > v)) return false;
14
+ if (op === "$gte" && !(val >= v)) return false;
15
+ if (op === "$lt" && !(val < v)) return false;
16
+ if (op === "$lte" && !(val <= v)) return false;
17
+ if (op === "$ne" && (val === v)) return false;
18
+ if (op === "$eq" && val !== v) return false;
19
+ if (op === "$in" && (!Array.isArray(v) || !v.includes(val))) return false;
20
+ }
21
+ } else {
22
+ if (val !== cond) return false;
23
+ }
24
+ }
25
+ return true;
26
+ }
27
+
28
+ export function applyUpdate(oldDoc, update) {
29
+ const doc = structuredClone(oldDoc);
30
+
31
+ // $set
32
+ if (update.$set) {
33
+ for (const k in update.$set) {
34
+ const parts = k.split(".");
35
+ let cur = doc;
36
+ for (let i = 0; i < parts.length - 1; i++) {
37
+ cur[parts[i]] = cur[parts[i]] ?? {};
38
+ cur = cur[parts[i]];
39
+ }
40
+ cur[parts.at(-1)] = update.$set[k];
41
+ }
42
+ }
43
+
44
+ // $inc
45
+ if (update.$inc) {
46
+ for (const k in update.$inc) {
47
+ const val = getByPath(doc, k) ?? 0;
48
+ const parts = k.split(".");
49
+ let cur = doc;
50
+ for (let i = 0; i < parts.length - 1; i++) {
51
+ cur[parts[i]] = cur[parts[i]] ?? {};
52
+ cur = cur[parts[i]];
53
+ }
54
+ cur[parts.at(-1)] = val + update.$inc[k];
55
+ }
56
+ }
57
+
58
+ // Direct merge (no operators)
59
+ const hasOp = Object.keys(update).some((k) => k.startsWith("$"));
60
+ if (!hasOp) {
61
+ return { ...doc, ...update };
62
+ }
63
+
64
+ return doc;
65
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,123 @@
1
+ // ================================================
2
+ // @liorandb/core - Type Definitions
3
+ // ================================================
4
+
5
+ declare module "@liorandb/core" {
6
+ // --------------------------
7
+ // Generic Query Operators
8
+ // --------------------------
9
+
10
+ export interface QueryOperators<T> {
11
+ $gt?: T;
12
+ $gte?: T;
13
+ $lt?: T;
14
+ $lte?: T;
15
+ $ne?: T;
16
+ $in?: T[];
17
+ }
18
+
19
+ export type FilterQuery<T> = {
20
+ [K in keyof T]?: T[K] | QueryOperators<T[K]>;
21
+ };
22
+
23
+ // --------------------------
24
+ // Update Operators
25
+ // --------------------------
26
+
27
+ export interface UpdateSet<T> {
28
+ $set?: Partial<T>;
29
+ }
30
+
31
+ export interface UpdateInc<T> {
32
+ $inc?: {
33
+ [K in keyof T]?: number;
34
+ };
35
+ }
36
+
37
+ export type UpdateQuery<T> = Partial<T> | (UpdateSet<T> & UpdateInc<T>);
38
+
39
+ // --------------------------
40
+ // Collection Class
41
+ // --------------------------
42
+
43
+ export class Collection<T extends { _id?: string }> {
44
+ constructor(dir: string);
45
+
46
+ close(): Promise<void>;
47
+
48
+ insertOne(doc: T): Promise<T>;
49
+ insertMany(docs: T[]): Promise<T[]>;
50
+
51
+ find(query?: FilterQuery<T>): Promise<T[]>;
52
+ findOne(query?: FilterQuery<T>): Promise<T | null>;
53
+
54
+ updateOne(
55
+ filter: FilterQuery<T>,
56
+ update: UpdateQuery<T>,
57
+ options?: { upsert?: boolean }
58
+ ): Promise<T | null>;
59
+
60
+ updateMany(
61
+ filter: FilterQuery<T>,
62
+ update: UpdateQuery<T>
63
+ ): Promise<T[]>;
64
+
65
+ deleteOne(filter: FilterQuery<T>): Promise<boolean>;
66
+ deleteMany(filter: FilterQuery<T>): Promise<number>;
67
+
68
+ countDocuments(filter?: FilterQuery<T>): Promise<number>;
69
+ }
70
+
71
+ // --------------------------
72
+ // LioranDB: Database Instance
73
+ // --------------------------
74
+
75
+ export class LioranDB {
76
+ basePath: string;
77
+ dbName: string;
78
+
79
+ constructor(basePath: string, dbName: string, manager: LioranManager);
80
+
81
+ collection<T extends { _id?: string }>(name: string): Collection<T>;
82
+
83
+ createCollection(name: string): Promise<boolean>;
84
+ deleteCollection(name: string): Promise<boolean>;
85
+ dropCollection(name: string): Promise<boolean>;
86
+ renameCollection(oldName: string, newName: string): Promise<boolean>;
87
+
88
+ listCollections(): Promise<string[]>;
89
+ }
90
+
91
+ // --------------------------
92
+ // LioranManager
93
+ // --------------------------
94
+
95
+ export class LioranManager {
96
+ rootPath: string;
97
+
98
+ constructor();
99
+
100
+ // MongoDB-style
101
+ db(name: string): Promise<LioranDB>;
102
+
103
+ createDatabase(name: string): Promise<LioranDB>;
104
+ openDatabase(name: string): Promise<LioranDB>;
105
+ closeDatabase(name: string): Promise<void>;
106
+
107
+ renameDatabase(oldName: string, newName: string): Promise<boolean>;
108
+
109
+ deleteDatabase(name: string): Promise<boolean>;
110
+ dropDatabase(name: string): Promise<boolean>;
111
+
112
+ listDatabases(): Promise<string[]>;
113
+ }
114
+
115
+ // --------------------------
116
+ // Utils
117
+ // --------------------------
118
+
119
+ export function getBaseDBFolder(): string;
120
+
121
+ // Package exports
122
+ export { LioranManager, LioranDB, getBaseDBFolder };
123
+ }
package/src/index.js ADDED
@@ -0,0 +1,4 @@
1
+ // src/index.js
2
+ export { LioranManager } from "./LioranManager.js";
3
+ export { LioranDB } from "./core/database.js";
4
+ export { getBaseDBFolder } from "./utils/rootpath.js";
@@ -0,0 +1,30 @@
1
+ import crypto from "crypto";
2
+ import { getMasterKey } from "./secureKey.js";
3
+
4
+ const algorithm = "aes-256-gcm";
5
+ const key = Buffer.from(getMasterKey(), "hex");
6
+
7
+ export function encryptData(plainObj) {
8
+ const iv = crypto.randomBytes(16); // random 16-byte IV
9
+ const data = Buffer.from(JSON.stringify(plainObj), "utf8");
10
+
11
+ const cipher = crypto.createCipheriv(algorithm, key, iv);
12
+ const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
13
+ const tag = cipher.getAuthTag();
14
+
15
+ // store as iv + tag + data, base64 encoded
16
+ return Buffer.concat([iv, tag, encrypted]).toString("base64");
17
+ }
18
+
19
+ export function decryptData(encStr) {
20
+ const buf = Buffer.from(encStr, "base64");
21
+ const iv = buf.slice(0, 16);
22
+ const tag = buf.slice(16, 32);
23
+ const encrypted = buf.slice(32);
24
+
25
+ const decipher = crypto.createDecipheriv(algorithm, key, iv);
26
+ decipher.setAuthTag(tag);
27
+
28
+ const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
29
+ return JSON.parse(decrypted.toString("utf8"));
30
+ }
@@ -0,0 +1,77 @@
1
+ import os from "os";
2
+ import path from "path";
3
+ import fs from "fs";
4
+
5
+ export function getBaseDBFolder() {
6
+ let dbPath = process.env.LIORANDB_PATH;
7
+
8
+ // If LIORANDB_PATH is NOT set system-wide
9
+ if (!dbPath) {
10
+ const homeDir = os.homedir();
11
+ dbPath = path.join(homeDir, "LioranDB");
12
+
13
+ // Ensure directory exists
14
+ if (!fs.existsSync(dbPath)) {
15
+ fs.mkdirSync(dbPath, { recursive: true });
16
+ }
17
+
18
+ // Set env var for current process
19
+ process.env.LIORANDB_PATH = dbPath;
20
+
21
+ // Create OS-specific installer scripts
22
+ createSystemEnvInstaller(dbPath);
23
+ }
24
+
25
+ return dbPath;
26
+ }
27
+
28
+ function createSystemEnvInstaller(dbPath) {
29
+ const platform = os.platform();
30
+ const scriptsDir = path.join(os.tmpdir(), "lioran_env_setup");
31
+
32
+ if (!fs.existsSync(scriptsDir)) {
33
+ fs.mkdirSync(scriptsDir, { recursive: true });
34
+ }
35
+
36
+ if (platform === "win32") {
37
+ const winScript = path.join(scriptsDir, "set-lioran-env.ps1");
38
+ fs.writeFileSync(
39
+ winScript,
40
+ `Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
41
+ [System.Environment]::SetEnvironmentVariable("LIORANDB_PATH", "${dbPath}", "Machine")
42
+ Write-Host "LIORANDB_PATH set system-wide to: ${dbPath}"`,
43
+ "utf8"
44
+ );
45
+
46
+ console.log(`
47
+ ⚠️ LIORANDB_PATH is not set system-wide.
48
+ Run this PowerShell script as ADMIN:
49
+
50
+ ${winScript}
51
+ `);
52
+ }
53
+
54
+ if (platform === "linux" || platform === "darwin") {
55
+ const bashScript = path.join(scriptsDir, "set-lioran-env.sh");
56
+ fs.writeFileSync(
57
+ bashScript,
58
+ `#!/bin/bash
59
+ echo 'export LIORANDB_PATH="${dbPath}"' >> ~/.bashrc
60
+ echo 'export LIORANDB_PATH="${dbPath}"' >> ~/.zshrc
61
+ echo "LIORANDB_PATH set system-wide to: ${dbPath}"
62
+ source ~/.bashrc 2>/dev/null
63
+ source ~/.zshrc 2>/dev/null
64
+ `,
65
+ "utf8"
66
+ );
67
+
68
+ fs.chmodSync(bashScript, 0o755);
69
+
70
+ console.log(`
71
+ ⚠️ LIORANDB_PATH is not set system-wide.
72
+ Run this script:
73
+
74
+ bash ${bashScript}
75
+ `);
76
+ }
77
+ }
@@ -0,0 +1,18 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import crypto from "crypto";
4
+ import { getBaseDBFolder } from "./rootpath.js";
5
+
6
+ const KEY_FILE = path.join(getBaseDBFolder(), ".secureKey");
7
+
8
+ export function getMasterKey() {
9
+ if (!fs.existsSync(KEY_FILE)) {
10
+ // generate 32-byte random key (256-bit)
11
+ const key = crypto.randomBytes(32).toString("hex");
12
+ fs.writeFileSync(KEY_FILE, key, { encoding: "utf8", mode: 0o600 });
13
+ return key;
14
+ }
15
+
16
+ const key = fs.readFileSync(KEY_FILE, "utf8").trim();
17
+ return key;
18
+ }