@wxn0brp/db-storage-bin 0.0.2

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.
@@ -0,0 +1,40 @@
1
+ import { unlink } from "fs/promises";
2
+ import { saveHeaderAndPayload } from "./head.js";
3
+ import { HEADER_SIZE } from "./static.js";
4
+ import { readData, roundUpCapacity, writeData } from "./utils.js";
5
+ import { _log } from "../log.js";
6
+ export async function optimize(cmp) {
7
+ await _log(3, "Starting database optimization");
8
+ const collections = cmp.meta.collections;
9
+ const allData = new Map();
10
+ for (const { name, offset } of collections) {
11
+ await _log(6, "Reading collection for optimization:", name);
12
+ const len = await readData(cmp.fd, offset, 4);
13
+ const data = await readData(cmp.fd, offset + 4, len.readInt32LE(0));
14
+ allData.set(name, data);
15
+ }
16
+ await _log(5, "Closing file for optimization");
17
+ await cmp.close();
18
+ await _log(6, "Deleting old database file for optimization");
19
+ await unlink(cmp.path);
20
+ await new Promise(resolve => setTimeout(resolve, 100));
21
+ await _log(5, "Re-opening database file for optimization");
22
+ await cmp.open();
23
+ let offset = roundUpCapacity(cmp.meta, cmp.meta.payloadLength + HEADER_SIZE) + cmp.meta.blockSize;
24
+ for (const [collection, data] of allData) {
25
+ await _log(6, "Writing optimized collection:", collection);
26
+ const len = roundUpCapacity(cmp.meta, data.length + 4);
27
+ const buf = Buffer.alloc(4);
28
+ buf.writeInt32LE(data.length, 0);
29
+ await writeData(cmp.fd, offset, buf, 4);
30
+ await writeData(cmp.fd, offset + 4, data, len);
31
+ cmp.meta.collections.push({
32
+ name: collection,
33
+ offset,
34
+ capacity: len
35
+ });
36
+ offset += len;
37
+ }
38
+ await saveHeaderAndPayload(cmp);
39
+ await _log(3, "Database optimization complete");
40
+ }
@@ -0,0 +1,2 @@
1
+ import { BinManager } from "./index.js";
2
+ export declare function removeCollection(cmp: BinManager, collection: string): Promise<void>;
package/dist/bin/rm.js ADDED
@@ -0,0 +1,25 @@
1
+ import { findCollection } from "./data.js";
2
+ import { saveHeaderAndPayload } from "./head.js";
3
+ import { writeData } from "./utils.js";
4
+ export async function removeCollection(cmp, collection) {
5
+ const { meta, fd, options } = cmp;
6
+ const collectionMeta = findCollection(cmp, collection);
7
+ if (!collectionMeta)
8
+ throw new Error("Collection not found");
9
+ if (meta.collections.length === 1) {
10
+ meta.collections = [];
11
+ meta.freeList = [];
12
+ await fd.truncate(0);
13
+ await saveHeaderAndPayload(cmp);
14
+ return;
15
+ }
16
+ meta.collections.splice(meta.collections.findIndex(c => c.name === collection), 1);
17
+ meta.freeList.push({
18
+ offset: collectionMeta.offset,
19
+ capacity: collectionMeta.capacity
20
+ });
21
+ if (options.overwriteRemovedCollection) {
22
+ await writeData(fd, collectionMeta.offset, Buffer.alloc(collectionMeta.capacity), collectionMeta.capacity);
23
+ }
24
+ await saveHeaderAndPayload(cmp);
25
+ }
@@ -0,0 +1,2 @@
1
+ export declare const HEADER_SIZE = 64;
2
+ export declare const VERSION = 1;
@@ -0,0 +1,2 @@
1
+ export const HEADER_SIZE = 64;
2
+ export const VERSION = 1;
@@ -0,0 +1,8 @@
1
+ import { FileHandle } from "fs/promises";
2
+ import { Block, FileMeta } from "./head.js";
3
+ export declare function roundUpCapacity(result: FileMeta, size: number): number;
4
+ export declare function writeData(fd: FileHandle, offset: number, data: Buffer, capacity: number): Promise<void>;
5
+ export declare function readData(fd: FileHandle, offset: number, length: number): Promise<Buffer>;
6
+ export declare function optimizeFreeList(blocks: Block[]): Block[];
7
+ export declare function detectCollisions(result: FileMeta, start: number, size: number, skip?: string[]): boolean;
8
+ export declare function pushToFreeList(result: FileMeta, offset: number, len: number): void;
@@ -0,0 +1,72 @@
1
+ import { _log } from "../log.js";
2
+ export function roundUpCapacity(result, size) {
3
+ return Math.ceil(size / result.blockSize) * result.blockSize;
4
+ }
5
+ export async function writeData(fd, offset, data, capacity) {
6
+ if (!fd)
7
+ throw new Error("File not open");
8
+ if (data.length > capacity)
9
+ throw new Error("Data size exceeds capacity");
10
+ await _log(6, "Writing data at offset:", offset, "length:", data.length, "capacity:", capacity);
11
+ const { bytesWritten } = await fd.write(data, 0, data.length, offset);
12
+ await _log(5, "Bytes written:", bytesWritten);
13
+ if (data.length < capacity) {
14
+ const pad = Buffer.alloc(capacity - data.length, 0);
15
+ const padStart = offset + data.length;
16
+ await _log(6, "Padding with zeros:", pad.length, "at offset:", padStart);
17
+ const { bytesWritten: padBytesWritten } = await fd.write(pad, 0, pad.length, padStart);
18
+ await _log(6, "Bytes written:", padBytesWritten);
19
+ }
20
+ await _log(6, "Data written");
21
+ }
22
+ export async function readData(fd, offset, length) {
23
+ if (!fd)
24
+ throw new Error("File not open");
25
+ await _log(6, "Reading data from offset:", offset, "length:", length);
26
+ const buf = Buffer.alloc(length);
27
+ const { bytesRead } = await fd.read(buf, 0, length, offset);
28
+ await _log(5, "Bytes read:", bytesRead);
29
+ return buf;
30
+ }
31
+ export function optimizeFreeList(blocks) {
32
+ if (blocks.length <= 1)
33
+ return blocks;
34
+ const sorted = [...blocks].sort((a, b) => a.offset - b.offset);
35
+ const merged = [];
36
+ let current = sorted[0];
37
+ for (let i = 1; i < sorted.length; i++) {
38
+ const next = sorted[i];
39
+ if (current.offset + current.capacity === next.offset) {
40
+ current = {
41
+ offset: current.offset,
42
+ capacity: current.capacity + next.capacity
43
+ };
44
+ }
45
+ else {
46
+ merged.push(current);
47
+ current = next;
48
+ }
49
+ }
50
+ merged.push(current);
51
+ return merged;
52
+ }
53
+ function checkCollection(start1, end1, start2, end2) {
54
+ _log(6, "Checking collection:", start1, end1, start2, end2);
55
+ return start1 < end2 && start2 < end1;
56
+ }
57
+ export function detectCollisions(result, start, size, skip = []) {
58
+ for (const { name, offset, capacity } of result.collections) {
59
+ if (skip.includes(name))
60
+ continue;
61
+ if (checkCollection(offset, offset + capacity, start, start + size))
62
+ return true;
63
+ }
64
+ return false;
65
+ }
66
+ export function pushToFreeList(result, offset, len) {
67
+ result.freeList.push({
68
+ offset,
69
+ capacity: roundUpCapacity(result, len),
70
+ });
71
+ result.freeList = optimizeFreeList(result.freeList);
72
+ }
@@ -0,0 +1,6 @@
1
+ import { FileHandle } from "fs/promises";
2
+ export declare function crc32(buf: Uint8Array | string, seed?: number): number;
3
+ export declare function getFileCrc(fd: FileHandle, short?: boolean): Promise<{
4
+ storedCrc: number;
5
+ computedCrc: number;
6
+ }>;
package/dist/crc32.js ADDED
@@ -0,0 +1,36 @@
1
+ import { HEADER_SIZE } from "./bin/static.js";
2
+ const CRC32_TABLE = new Uint32Array(256);
3
+ for (let i = 0; i < 256; i++) {
4
+ let crc = i;
5
+ for (let j = 0; j < 8; j++) {
6
+ crc = (crc & 1) ? (0xEDB88320 ^ (crc >>> 1)) : (crc >>> 1);
7
+ }
8
+ CRC32_TABLE[i] = crc >>> 0;
9
+ }
10
+ export function crc32(buf, seed = 0xFFFFFFFF) {
11
+ if (typeof buf === "string") {
12
+ buf = new TextEncoder().encode(buf);
13
+ }
14
+ let crc = seed ^ 0xFFFFFFFF;
15
+ for (let i = 0; i < buf.length; i++) {
16
+ const byte = buf[i];
17
+ crc = (crc >>> 8) ^ CRC32_TABLE[(crc ^ byte) & 0xFF];
18
+ }
19
+ return (crc ^ 0xFFFFFFFF) >>> 0;
20
+ }
21
+ export async function getFileCrc(fd, short = false) {
22
+ const { size } = await fd.stat();
23
+ if (size < HEADER_SIZE)
24
+ return { storedCrc: 0, computedCrc: 0 };
25
+ const buffer = Buffer.alloc(size);
26
+ await fd.read(buffer, 0, size, 0);
27
+ const storedCrc = buffer.readUInt32LE(16);
28
+ if (short && storedCrc === 0)
29
+ return { storedCrc: 0, computedCrc: 0 };
30
+ buffer.fill(0, 16, 20);
31
+ const computedCrc = crc32(buffer);
32
+ return {
33
+ storedCrc,
34
+ computedCrc
35
+ };
36
+ }
@@ -0,0 +1,10 @@
1
+ import { ValtheraClass } from "@wxn0brp/db-core";
2
+ import { BinFileAction } from "./actions.js";
3
+ import { BinManager, Options } from "./bin/index.js";
4
+ export * from "./actions.js";
5
+ export * from "./bin/index.js";
6
+ export declare function createBinValthera(path: string, opts?: Partial<Options>, init?: boolean): Promise<{
7
+ db: ValtheraClass;
8
+ actions: BinFileAction;
9
+ mgr: BinManager;
10
+ }>;
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ import { ValtheraClass } from "@wxn0brp/db-core";
2
+ import { BinFileAction } from "./actions.js";
3
+ import { BinManager } from "./bin/index.js";
4
+ export * from "./actions.js";
5
+ export * from "./bin/index.js";
6
+ export async function createBinValthera(path, opts = {}, init = true) {
7
+ const mgr = new BinManager(path, opts);
8
+ const actions = new BinFileAction(mgr);
9
+ const db = new ValtheraClass({ dbAction: actions });
10
+ if (init)
11
+ await actions.init();
12
+ return {
13
+ db,
14
+ actions,
15
+ mgr,
16
+ };
17
+ }
package/dist/log.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function _log(level: number, ...data: any[]): Promise<void>;
package/dist/log.js ADDED
@@ -0,0 +1,12 @@
1
+ const dir = process.cwd() + "/";
2
+ export async function _log(level, ...data) {
3
+ const logLevel = parseInt(process.env.VDB_BIN_LOG_LEVEL || '0', 10);
4
+ if (logLevel < level)
5
+ return;
6
+ let line = new Error().stack.split('\n')[3].trim();
7
+ let path = line.slice(line.indexOf("(")).replace(dir, "").replace("(", "").replace(")", "");
8
+ const at = line.slice(3, line.indexOf("(") - 1);
9
+ if (path.length < 2)
10
+ path = line.replace(dir, "").replace("at ", ""); // if path is 2 (callback):
11
+ console.log(`[${level}] ` + "\x1b[36m" + path + ":", "\x1b[33m" + at + "\x1b[0m", ...data);
12
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@wxn0brp/db-storage-bin",
3
+ "version": "0.0.2",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "author": "wxn0brP",
7
+ "license": "MIT",
8
+ "type": "module",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/wxn0brp/ValtheraDB-storage-bin.git"
12
+ },
13
+ "homepage": "https://github.com/wxn0brp/ValtheraDB-storage-bin",
14
+ "devDependencies": {
15
+ "@types/node": "^24.3.0",
16
+ "@wxn0brp/db-core": "^0.1.5",
17
+ "tsc-alias": "^1.8.10",
18
+ "typescript": "^5.9.2"
19
+ },
20
+ "dependencies": {
21
+ "@msgpack/msgpack": "^3.1.2"
22
+ },
23
+ "peerDependencies": {
24
+ "@wxn0brp/db-core": ">=0.1.5"
25
+ },
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "default": "./dist/index.js",
30
+ "import": "./dist/index.js"
31
+ },
32
+ "./*": {
33
+ "types": "./dist/*.d.ts",
34
+ "default": "./dist/*.js",
35
+ "import": "./dist/*.js"
36
+ }
37
+ }
38
+ }
package/src/actions.ts ADDED
@@ -0,0 +1,122 @@
1
+ import { CustomFileCpu, genId } from "@wxn0brp/db-core";
2
+ import dbActionBase from "@wxn0brp/db-core/base/actions";
3
+ import Data from "@wxn0brp/db-core/types/data";
4
+ import FileCpu from "@wxn0brp/db-core/types/fileCpu";
5
+ import { DbOpts } from "@wxn0brp/db-core/types/options";
6
+ import { VQuery } from "@wxn0brp/db-core/types/query";
7
+ import { findUtil } from "@wxn0brp/db-core/utils/action";
8
+ import { BinManager } from "./bin";
9
+
10
+ export class BinFileAction extends dbActionBase {
11
+ folder: string;
12
+ options: DbOpts;
13
+ fileCpu: FileCpu;
14
+
15
+ /**
16
+ * Creates a new instance of dbActionC.
17
+ * @constructor
18
+ * @param folder - The folder where database files are stored.
19
+ * @param options - The options object.
20
+ */
21
+ constructor(private mgr: BinManager) {
22
+ super();
23
+ this.fileCpu = new CustomFileCpu(this.mgr.read.bind(this.mgr), this.mgr.write.bind(this.mgr));
24
+ }
25
+
26
+ async init() {
27
+ await this.mgr.open();
28
+ }
29
+
30
+ /**
31
+ * Get a list of available databases in the specified folder.
32
+ */
33
+ async getCollections() {
34
+ const collections = this.mgr.meta.collections.map(c => c.name);
35
+ return collections;
36
+ }
37
+
38
+ /**
39
+ * Check and create the specified collection if it doesn't exist.
40
+ */
41
+ async ensureCollection({ collection }: VQuery) {
42
+ if (await this.issetCollection(arguments[0])) return;
43
+ await this.mgr.write(collection, []);
44
+ return true;
45
+ }
46
+
47
+ /**
48
+ * Check if a collection exists.
49
+ */
50
+ async issetCollection({ collection }: VQuery) {
51
+ const collections = await this.getCollections();
52
+ return collections.includes(collection);
53
+ }
54
+
55
+ /**
56
+ * Add a new entry to the specified database.
57
+ */
58
+ async add({ collection, data, id_gen = true }: VQuery) {
59
+ await this.ensureCollection(arguments[0]);
60
+
61
+ if (id_gen) data._id = data._id || genId();
62
+ await this.fileCpu.add(collection, data);
63
+ return data;
64
+ }
65
+
66
+ /**
67
+ * Find entries in the specified database based on search criteria.
68
+ */
69
+ async find(query: VQuery) {
70
+ await this.ensureCollection(query);
71
+ return await findUtil(query, this.fileCpu, [query.collection]);
72
+ }
73
+
74
+ /**
75
+ * Find the first matching entry in the specified database based on search criteria.
76
+ */
77
+ async findOne({ collection, search, context = {}, findOpts = {} }: VQuery) {
78
+ await this.ensureCollection(arguments[0]);
79
+ let data = await this.fileCpu.findOne(collection, search, context, findOpts) as Data;
80
+ return data || null;
81
+ }
82
+
83
+ /**
84
+ * Update entries in the specified database based on search criteria and an updater function or object.
85
+ */
86
+ async update({ collection, search, updater, context = {} }: VQuery) {
87
+ await this.ensureCollection(arguments[0]);
88
+ return await this.fileCpu.update(collection, false, search, updater, context);
89
+ }
90
+
91
+ /**
92
+ * Update the first matching entry in the specified database based on search criteria and an updater function or object.
93
+ */
94
+ async updateOne({ collection, search, updater, context = {} }: VQuery) {
95
+ await this.ensureCollection(arguments[0]);
96
+ return await this.fileCpu.update(collection, true, search, updater, context);
97
+ }
98
+
99
+ /**
100
+ * Remove entries from the specified database based on search criteria.
101
+ */
102
+ async remove({ collection, search, context = {} }: VQuery) {
103
+ await this.ensureCollection(arguments[0]);
104
+ return await this.fileCpu.remove(collection, false, search, context);
105
+ }
106
+
107
+ /**
108
+ * Remove the first matching entry from the specified database based on search criteria.
109
+ */
110
+ async removeOne({ collection, search, context = {} }: VQuery) {
111
+ await this.ensureCollection(arguments[0]);
112
+ return await this.fileCpu.remove(collection, true, search, context);
113
+ }
114
+
115
+ /**
116
+ * Removes a database collection from the file system.
117
+ */
118
+ async removeCollection({ collection }: VQuery) {
119
+ await this.mgr.removeCollection(collection);
120
+ return true;
121
+ }
122
+ }
@@ -0,0 +1,91 @@
1
+ import { BinManager, CollectionMeta } from ".";
2
+ import { _log } from "../log";
3
+ import { FileMeta, saveHeaderAndPayload } from "./head";
4
+ import { detectCollisions, pushToFreeList, readData, roundUpCapacity, writeData } from "./utils";
5
+
6
+ export function findCollection(cmp: BinManager, name: string): CollectionMeta | undefined {
7
+ return cmp.meta.collections.find(c => c.name === name);
8
+ }
9
+
10
+ export async function findFreeSlot(cmp: BinManager, size: number): Promise<FileMeta["freeList"][number] | undefined> {
11
+ const { meta } = cmp;
12
+ await _log(6, "Finding free slot for size:", size);
13
+ const idx = meta.freeList.findIndex(f => f.capacity >= size);
14
+
15
+ if (idx === -1) {
16
+ await _log(6, "No suitable free slot found.");
17
+ return undefined;
18
+ }
19
+
20
+ const slot = meta.freeList[idx];
21
+ await _log(6, "Free slot found at index:", idx, "with capacity:", slot.capacity);
22
+
23
+ meta.freeList.splice(idx, 1);
24
+ await _log(6, "Slot removed from freeList:", slot);
25
+
26
+ return slot;
27
+ }
28
+
29
+ export async function writeLogic(cmp: BinManager, collection: string, data: object[]) {
30
+ const { fd, meta } = cmp;
31
+ await _log(3, "Writing data to collection:", collection);
32
+
33
+ const existingCollection = findCollection(cmp, collection);
34
+ const encoded = Buffer.from(await cmp.options.format.encode(data));
35
+ const length = encoded.length;
36
+ const capacity = roundUpCapacity(meta, length + 4);
37
+
38
+ let offset = existingCollection?.offset;
39
+ let existingOffset = existingCollection?.offset;
40
+ let existingCapacity = existingCollection?.capacity;
41
+
42
+ const collision = detectCollisions(meta, offset, capacity, [collection]);
43
+ if (collision || !existingCollection) {
44
+ if (collision) await _log(2, "Collision detected");
45
+ const slot = await findFreeSlot(cmp, capacity);
46
+ if (slot) {
47
+ offset = slot.offset;
48
+ await _log(4, "Found free slot at offset:", offset);
49
+ } else {
50
+ offset = meta.fileSize;
51
+ meta.fileSize += capacity;
52
+ await _log(4, "No free slot found, appending at offset:", offset);
53
+ }
54
+
55
+ if (!existingCollection) {
56
+ meta.collections.push({ name: collection, offset, capacity });
57
+ } else if (collision) {
58
+ pushToFreeList(meta, existingOffset, existingCapacity);
59
+ meta.collections = meta.collections.map(c => {
60
+ if (c.offset === existingOffset) return { name: c.name, offset, capacity };
61
+ return c;
62
+ })
63
+ }
64
+
65
+ await _log(3, "Collection written");
66
+ await saveHeaderAndPayload(cmp);
67
+ }
68
+
69
+ const buf = Buffer.alloc(4);
70
+ buf.writeUInt32LE(length, 0);
71
+ await writeData(fd, offset, buf, 4);
72
+ await writeData(fd, offset + 4, encoded, capacity);
73
+
74
+ if (existingCollection && length >= existingCollection.capacity) {
75
+ meta.collections = meta.collections.map(c => {
76
+ if (c.offset === offset) return { name: c.name, offset, capacity };
77
+ return c;
78
+ });
79
+ await saveHeaderAndPayload(cmp);
80
+ await _log(2, "Capacity exceeded");
81
+ }
82
+ }
83
+
84
+ export async function readLogic(cmp: BinManager, collection: string) {
85
+ const collectionMeta = findCollection(cmp, collection);
86
+ if (!collectionMeta) throw new Error("Collection not found");
87
+
88
+ const len = await readData(cmp.fd, collectionMeta.offset, 4);
89
+ const data = await readData(cmp.fd, collectionMeta.offset + 4, len.readUInt32LE(0));
90
+ return await cmp.options.format.decode(data);
91
+ }