@liorandb/core 1.0.4 → 1.0.6
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/dist/index.d.ts +73 -0
- package/dist/index.js +440 -0
- package/package.json +38 -27
- package/src/{LioranManager.js → LioranManager.ts} +24 -19
- package/src/core/collection.ts +163 -0
- package/src/core/{database.js → database.ts} +23 -37
- package/src/core/{query.js → query.ts} +11 -13
- package/src/index.ts +5 -0
- package/src/types/index.ts +12 -0
- package/src/utils/{encryption.js → encryption.ts} +10 -15
- package/src/utils/rootpath.ts +54 -0
- package/src/utils/secureKey.ts +15 -0
- package/tsconfig.json +13 -0
- package/src/core/collection.js +0 -160
- package/src/index.d.ts +0 -26
- package/src/index.js +0 -4
- package/src/utils/rootpath.js +0 -77
- package/src/utils/secureKey.js +0 -21
|
@@ -0,0 +1,163 @@
|
|
|
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 interface UpdateOptions {
|
|
7
|
+
upsert?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class Collection<T = any> {
|
|
11
|
+
dir: string;
|
|
12
|
+
db: ClassicLevel<string, string>;
|
|
13
|
+
private queue: Promise<any>;
|
|
14
|
+
|
|
15
|
+
constructor(dir: string) {
|
|
16
|
+
this.dir = dir;
|
|
17
|
+
this.db = new ClassicLevel(dir);
|
|
18
|
+
this.queue = Promise.resolve();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private _enqueue<R>(task: () => Promise<R>): Promise<R> {
|
|
22
|
+
this.queue = this.queue.then(task).catch(console.error);
|
|
23
|
+
return this.queue;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async close(): Promise<void> {
|
|
27
|
+
try {
|
|
28
|
+
await this.db.close();
|
|
29
|
+
} catch {}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async insertOne(doc: T & { _id?: string }): Promise<T> {
|
|
33
|
+
return this._enqueue(async () => {
|
|
34
|
+
const _id = doc._id ?? uuid();
|
|
35
|
+
const final = { _id, ...doc } as T;
|
|
36
|
+
await this.db.put(String(_id), encryptData(final));
|
|
37
|
+
return final;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async insertMany(docs: (T & { _id?: string })[] = []): Promise<T[]> {
|
|
42
|
+
return this._enqueue(async () => {
|
|
43
|
+
const ops: Array<{ type: "put"; key: string; value: string }> = [];
|
|
44
|
+
const out: T[] = [];
|
|
45
|
+
|
|
46
|
+
for (const d of docs) {
|
|
47
|
+
const _id = d._id ?? uuid();
|
|
48
|
+
const final = { _id, ...d } as T;
|
|
49
|
+
ops.push({
|
|
50
|
+
type: "put",
|
|
51
|
+
key: String(_id),
|
|
52
|
+
value: encryptData(final)
|
|
53
|
+
});
|
|
54
|
+
out.push(final);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
await this.db.batch(ops);
|
|
58
|
+
return out;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async find(query: any = {}): Promise<T[]> {
|
|
63
|
+
return this._enqueue(async () => {
|
|
64
|
+
const out: T[] = [];
|
|
65
|
+
for await (const [, enc] of this.db.iterator()) {
|
|
66
|
+
const value = decryptData(enc);
|
|
67
|
+
if (matchDocument(value, query)) out.push(value);
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async findOne(query: any = {}): Promise<T | null> {
|
|
74
|
+
return this._enqueue(async () => {
|
|
75
|
+
for await (const [, enc] of this.db.iterator()) {
|
|
76
|
+
const value = decryptData(enc);
|
|
77
|
+
if (matchDocument(value, query)) return value;
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async updateOne(
|
|
84
|
+
filter: any = {},
|
|
85
|
+
update: any = {},
|
|
86
|
+
options: UpdateOptions = { upsert: false }
|
|
87
|
+
): Promise<T | null> {
|
|
88
|
+
return this._enqueue(async () => {
|
|
89
|
+
for await (const [key, enc] of this.db.iterator()) {
|
|
90
|
+
const value = decryptData(enc);
|
|
91
|
+
if (matchDocument(value, filter)) {
|
|
92
|
+
const updated = applyUpdate(value, update);
|
|
93
|
+
updated._id = value._id;
|
|
94
|
+
await this.db.put(key, encryptData(updated));
|
|
95
|
+
return updated;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (options.upsert) {
|
|
100
|
+
const doc = applyUpdate(filter, update);
|
|
101
|
+
doc._id ??= uuid();
|
|
102
|
+
await this.db.put(String(doc._id), encryptData(doc));
|
|
103
|
+
return doc;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return null;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async updateMany(filter: any = {}, update: any = {}): Promise<T[]> {
|
|
111
|
+
return this._enqueue(async () => {
|
|
112
|
+
const updated: T[] = [];
|
|
113
|
+
for await (const [key, enc] of this.db.iterator()) {
|
|
114
|
+
const value = decryptData(enc);
|
|
115
|
+
if (matchDocument(value, filter)) {
|
|
116
|
+
const doc = applyUpdate(value, update);
|
|
117
|
+
doc._id = value._id;
|
|
118
|
+
await this.db.put(key, encryptData(doc));
|
|
119
|
+
updated.push(doc);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return updated;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async deleteOne(filter: any = {}): Promise<boolean> {
|
|
127
|
+
return this._enqueue(async () => {
|
|
128
|
+
for await (const [key, enc] of this.db.iterator()) {
|
|
129
|
+
const value = decryptData(enc);
|
|
130
|
+
if (matchDocument(value, filter)) {
|
|
131
|
+
await this.db.del(key);
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async deleteMany(filter: any = {}): Promise<number> {
|
|
140
|
+
return this._enqueue(async () => {
|
|
141
|
+
let count = 0;
|
|
142
|
+
for await (const [key, enc] of this.db.iterator()) {
|
|
143
|
+
const value = decryptData(enc);
|
|
144
|
+
if (matchDocument(value, filter)) {
|
|
145
|
+
await this.db.del(key);
|
|
146
|
+
count++;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return count;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async countDocuments(filter: any = {}): Promise<number> {
|
|
154
|
+
return this._enqueue(async () => {
|
|
155
|
+
let c = 0;
|
|
156
|
+
for await (const [, enc] of this.db.iterator()) {
|
|
157
|
+
const value = decryptData(enc);
|
|
158
|
+
if (matchDocument(value, filter)) c++;
|
|
159
|
+
}
|
|
160
|
+
return c;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import { Collection } from "./collection.js";
|
|
4
|
+
import type { LioranManager } from "../LioranManager.js";
|
|
4
5
|
|
|
5
6
|
export class LioranDB {
|
|
6
|
-
|
|
7
|
+
basePath: string;
|
|
8
|
+
dbName: string;
|
|
9
|
+
manager: LioranManager;
|
|
10
|
+
collections: Map<string, Collection>;
|
|
11
|
+
|
|
12
|
+
constructor(basePath: string, dbName: string, manager: LioranManager) {
|
|
7
13
|
this.basePath = basePath;
|
|
8
14
|
this.dbName = dbName;
|
|
9
15
|
this.manager = manager;
|
|
@@ -14,66 +20,51 @@ export class LioranDB {
|
|
|
14
20
|
}
|
|
15
21
|
}
|
|
16
22
|
|
|
17
|
-
|
|
18
|
-
collection(name) {
|
|
23
|
+
collection<T = any>(name: string): Collection<T> {
|
|
19
24
|
if (this.collections.has(name)) {
|
|
20
|
-
return this.collections.get(name)
|
|
25
|
+
return this.collections.get(name)!;
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
const colPath = path.join(this.basePath, name);
|
|
24
29
|
|
|
25
|
-
// auto-create directory
|
|
26
30
|
if (!fs.existsSync(colPath)) {
|
|
27
31
|
fs.mkdirSync(colPath, { recursive: true });
|
|
28
32
|
}
|
|
29
33
|
|
|
30
|
-
const col = new Collection(colPath);
|
|
34
|
+
const col = new Collection<T>(colPath);
|
|
31
35
|
this.collections.set(name, col);
|
|
32
36
|
return col;
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
|
|
36
|
-
async createCollection(name) {
|
|
39
|
+
async createCollection(name: string): Promise<boolean> {
|
|
37
40
|
const colPath = path.join(this.basePath, name);
|
|
38
41
|
|
|
39
42
|
if (fs.existsSync(colPath)) {
|
|
40
43
|
throw new Error("Collection already exists");
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
// create folder
|
|
44
46
|
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
|
-
|
|
47
|
+
this.collections.set(name, new Collection(colPath));
|
|
50
48
|
return true;
|
|
51
49
|
}
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
async deleteCollection(name) {
|
|
51
|
+
async deleteCollection(name: string): Promise<boolean> {
|
|
55
52
|
const colPath = path.join(this.basePath, name);
|
|
56
53
|
|
|
57
54
|
if (!fs.existsSync(colPath)) {
|
|
58
55
|
throw new Error("Collection does not exist");
|
|
59
56
|
}
|
|
60
57
|
|
|
61
|
-
// close LevelDB instance if opened
|
|
62
58
|
if (this.collections.has(name)) {
|
|
63
|
-
|
|
64
|
-
await col.close().catch(() => {});
|
|
65
|
-
await col.db.clear().catch(() => {});
|
|
59
|
+
await this.collections.get(name)!.close();
|
|
66
60
|
this.collections.delete(name);
|
|
67
61
|
}
|
|
68
62
|
|
|
69
|
-
// force delete directory
|
|
70
63
|
await fs.promises.rm(colPath, { recursive: true, force: true });
|
|
71
|
-
|
|
72
64
|
return true;
|
|
73
65
|
}
|
|
74
66
|
|
|
75
|
-
|
|
76
|
-
async renameCollection(oldName, newName) {
|
|
67
|
+
async renameCollection(oldName: string, newName: string): Promise<boolean> {
|
|
77
68
|
const oldPath = path.join(this.basePath, oldName);
|
|
78
69
|
const newPath = path.join(this.basePath, newName);
|
|
79
70
|
|
|
@@ -81,29 +72,24 @@ export class LioranDB {
|
|
|
81
72
|
if (fs.existsSync(newPath)) throw new Error("New collection name exists");
|
|
82
73
|
|
|
83
74
|
if (this.collections.has(oldName)) {
|
|
84
|
-
await this.collections.get(oldName)
|
|
75
|
+
await this.collections.get(oldName)!.close();
|
|
85
76
|
this.collections.delete(oldName);
|
|
86
77
|
}
|
|
87
78
|
|
|
88
79
|
await fs.promises.rename(oldPath, newPath);
|
|
89
|
-
|
|
90
|
-
const newCol = new Collection(newPath);
|
|
91
|
-
this.collections.set(newName, newCol);
|
|
92
|
-
|
|
80
|
+
this.collections.set(newName, new Collection(newPath));
|
|
93
81
|
return true;
|
|
94
82
|
}
|
|
95
83
|
|
|
96
|
-
|
|
97
|
-
async dropCollection(name) {
|
|
84
|
+
async dropCollection(name: string): Promise<boolean> {
|
|
98
85
|
return this.deleteCollection(name);
|
|
99
86
|
}
|
|
100
87
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
88
|
+
async listCollections(): Promise<string[]> {
|
|
89
|
+
const dirs = await fs.promises.readdir(this.basePath, {
|
|
90
|
+
withFileTypes: true
|
|
91
|
+
});
|
|
104
92
|
|
|
105
|
-
return dirs
|
|
106
|
-
.filter(dirent => dirent.isDirectory())
|
|
107
|
-
.map(dirent => dirent.name);
|
|
93
|
+
return dirs.filter(d => d.isDirectory()).map(d => d.name);
|
|
108
94
|
}
|
|
109
95
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
function getByPath(obj, path) {
|
|
1
|
+
function getByPath(obj: any, path: string): any {
|
|
2
2
|
return path.split(".").reduce((o, p) => (o ? o[p] : undefined), obj);
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
-
export function matchDocument(doc, query) {
|
|
5
|
+
export function matchDocument(doc: any, query: any): boolean {
|
|
6
6
|
for (const key of Object.keys(query)) {
|
|
7
7
|
const cond = query[key];
|
|
8
8
|
const val = getByPath(doc, key);
|
|
@@ -14,9 +14,10 @@ export function matchDocument(doc, query) {
|
|
|
14
14
|
if (op === "$gte" && !(val >= v)) return false;
|
|
15
15
|
if (op === "$lt" && !(val < v)) return false;
|
|
16
16
|
if (op === "$lte" && !(val <= v)) return false;
|
|
17
|
-
if (op === "$ne" &&
|
|
17
|
+
if (op === "$ne" && val === v) return false;
|
|
18
18
|
if (op === "$eq" && val !== v) return false;
|
|
19
|
-
if (op === "$in" && (!Array.isArray(v) || !v.includes(val)))
|
|
19
|
+
if (op === "$in" && (!Array.isArray(v) || !v.includes(val)))
|
|
20
|
+
return false;
|
|
20
21
|
}
|
|
21
22
|
} else {
|
|
22
23
|
if (val !== cond) return false;
|
|
@@ -25,38 +26,35 @@ export function matchDocument(doc, query) {
|
|
|
25
26
|
return true;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
export function applyUpdate(oldDoc, update) {
|
|
29
|
+
export function applyUpdate(oldDoc: any, update: any): any {
|
|
29
30
|
const doc = structuredClone(oldDoc);
|
|
30
31
|
|
|
31
|
-
// $set
|
|
32
32
|
if (update.$set) {
|
|
33
33
|
for (const k in update.$set) {
|
|
34
34
|
const parts = k.split(".");
|
|
35
35
|
let cur = doc;
|
|
36
36
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
37
|
-
cur[parts[i]]
|
|
37
|
+
cur[parts[i]] ??= {};
|
|
38
38
|
cur = cur[parts[i]];
|
|
39
39
|
}
|
|
40
|
-
cur[parts.at(-1)] = update.$set[k];
|
|
40
|
+
cur[parts.at(-1)!] = update.$set[k];
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
// $inc
|
|
45
44
|
if (update.$inc) {
|
|
46
45
|
for (const k in update.$inc) {
|
|
47
46
|
const val = getByPath(doc, k) ?? 0;
|
|
48
47
|
const parts = k.split(".");
|
|
49
48
|
let cur = doc;
|
|
50
49
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
51
|
-
cur[parts[i]]
|
|
50
|
+
cur[parts[i]] ??= {};
|
|
52
51
|
cur = cur[parts[i]];
|
|
53
52
|
}
|
|
54
|
-
cur[parts.at(-1)] = val + update.$inc[k];
|
|
53
|
+
cur[parts.at(-1)!] = val + update.$inc[k];
|
|
55
54
|
}
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
const hasOp = Object.keys(update).some((k) => k.startsWith("$"));
|
|
57
|
+
const hasOp = Object.keys(update).some(k => k.startsWith("$"));
|
|
60
58
|
if (!hasOp) {
|
|
61
59
|
return { ...doc, ...update };
|
|
62
60
|
}
|
package/src/index.ts
ADDED
|
@@ -2,14 +2,9 @@ import crypto from "crypto";
|
|
|
2
2
|
import { getMasterKey } from "./secureKey.js";
|
|
3
3
|
|
|
4
4
|
const algorithm = "aes-256-gcm";
|
|
5
|
+
let ACTIVE_KEY: Buffer = getMasterKey();
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
let ACTIVE_KEY = getMasterKey();
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Allows LioranManager to inject a custom encryption key
|
|
11
|
-
*/
|
|
12
|
-
export function setEncryptionKey(key) {
|
|
7
|
+
export function setEncryptionKey(key: string | Buffer): void {
|
|
13
8
|
if (!key) return;
|
|
14
9
|
|
|
15
10
|
if (typeof key === "string") {
|
|
@@ -19,7 +14,7 @@ export function setEncryptionKey(key) {
|
|
|
19
14
|
|
|
20
15
|
if (Buffer.isBuffer(key)) {
|
|
21
16
|
if (key.length !== 32) {
|
|
22
|
-
throw new Error("Encryption key must be 32 bytes
|
|
17
|
+
throw new Error("Encryption key must be 32 bytes");
|
|
23
18
|
}
|
|
24
19
|
ACTIVE_KEY = key;
|
|
25
20
|
return;
|
|
@@ -28,9 +23,9 @@ export function setEncryptionKey(key) {
|
|
|
28
23
|
throw new Error("Invalid encryption key format");
|
|
29
24
|
}
|
|
30
25
|
|
|
31
|
-
export function encryptData(
|
|
26
|
+
export function encryptData(obj: any): string {
|
|
32
27
|
const iv = crypto.randomBytes(16);
|
|
33
|
-
const data = Buffer.from(JSON.stringify(
|
|
28
|
+
const data = Buffer.from(JSON.stringify(obj), "utf8");
|
|
34
29
|
|
|
35
30
|
const cipher = crypto.createCipheriv(algorithm, ACTIVE_KEY, iv);
|
|
36
31
|
const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
|
|
@@ -39,11 +34,11 @@ export function encryptData(plainObj) {
|
|
|
39
34
|
return Buffer.concat([iv, tag, encrypted]).toString("base64");
|
|
40
35
|
}
|
|
41
36
|
|
|
42
|
-
export function decryptData(
|
|
43
|
-
const buf = Buffer.from(
|
|
44
|
-
const iv = buf.
|
|
45
|
-
const tag = buf.
|
|
46
|
-
const encrypted = buf.
|
|
37
|
+
export function decryptData(enc: string): any {
|
|
38
|
+
const buf = Buffer.from(enc, "base64");
|
|
39
|
+
const iv = buf.subarray(0, 16);
|
|
40
|
+
const tag = buf.subarray(16, 32);
|
|
41
|
+
const encrypted = buf.subarray(32);
|
|
47
42
|
|
|
48
43
|
const decipher = crypto.createDecipheriv(algorithm, ACTIVE_KEY, iv);
|
|
49
44
|
decipher.setAuthTag(tag);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import os from "os";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
|
|
5
|
+
export function getDefaultRootPath(): string {
|
|
6
|
+
let dbPath = process.env.LIORANDB_PATH;
|
|
7
|
+
|
|
8
|
+
if (!dbPath) {
|
|
9
|
+
const homeDir = os.homedir();
|
|
10
|
+
dbPath = path.join(homeDir, "LioranDB", "db");
|
|
11
|
+
|
|
12
|
+
if (!fs.existsSync(dbPath)) {
|
|
13
|
+
fs.mkdirSync(dbPath, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
process.env.LIORANDB_PATH = dbPath;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return dbPath;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getBaseDBFolder(): string {
|
|
23
|
+
return getDefaultRootPath();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function installSystemEnv(dbPath = getDefaultRootPath()) {
|
|
27
|
+
const platform = os.platform();
|
|
28
|
+
const scriptsDir = path.join(os.tmpdir(), "lioran_env_setup");
|
|
29
|
+
|
|
30
|
+
if (!fs.existsSync(scriptsDir)) {
|
|
31
|
+
fs.mkdirSync(scriptsDir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (platform === "win32") {
|
|
35
|
+
const script = path.join(scriptsDir, "set-lioran-env.ps1");
|
|
36
|
+
fs.writeFileSync(
|
|
37
|
+
script,
|
|
38
|
+
`[System.Environment]::SetEnvironmentVariable("LIORANDB_PATH","${dbPath}","Machine")`
|
|
39
|
+
);
|
|
40
|
+
return { platform: "windows", script };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (platform === "linux" || platform === "darwin") {
|
|
44
|
+
const script = path.join(scriptsDir, "set-lioran-env.sh");
|
|
45
|
+
fs.writeFileSync(
|
|
46
|
+
script,
|
|
47
|
+
`export LIORANDB_PATH="${dbPath}"`
|
|
48
|
+
);
|
|
49
|
+
fs.chmodSync(script, 0o755);
|
|
50
|
+
return { platform, script };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import os from "os";
|
|
3
|
+
|
|
4
|
+
export function getMasterKey(): Buffer {
|
|
5
|
+
const fingerprint = [
|
|
6
|
+
os.hostname(),
|
|
7
|
+
os.platform(),
|
|
8
|
+
os.arch(),
|
|
9
|
+
os.cpus()?.[0]?.model ?? "unknown",
|
|
10
|
+
os.cpus()?.length ?? 0,
|
|
11
|
+
os.totalmem()
|
|
12
|
+
].join("|");
|
|
13
|
+
|
|
14
|
+
return crypto.createHash("sha256").update(fingerprint).digest();
|
|
15
|
+
}
|
package/tsconfig.json
ADDED
package/src/core/collection.js
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
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
|
-
}
|