@wxn0brp/db-storage-bin 0.0.6 → 0.0.7

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/bin/data.js CHANGED
@@ -60,7 +60,7 @@ export async function writeLogic(cmp, collection, data) {
60
60
  const buf = Buffer.alloc(4);
61
61
  buf.writeUInt32LE(length, 0);
62
62
  await writeData(fd, offset, buf, 4);
63
- await writeData(fd, offset + 4, encoded, capacity);
63
+ await writeData(fd, offset + 4, encoded, capacity - 4);
64
64
  if (existingCollection && length >= existingCollection.capacity) {
65
65
  meta.collections = meta.collections.map(c => {
66
66
  if (c.offset === offset)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wxn0brp/db-storage-bin",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "author": "wxn0brP",
@@ -34,5 +34,8 @@
34
34
  "default": "./dist/*.js",
35
35
  "import": "./dist/*.js"
36
36
  }
37
- }
37
+ },
38
+ "files": [
39
+ "dist"
40
+ ]
38
41
  }
@@ -1,24 +0,0 @@
1
- name: Build
2
-
3
- on:
4
- push:
5
- branches:
6
- - master
7
- tags:
8
- - "*"
9
-
10
- workflow_dispatch:
11
-
12
- concurrency:
13
- group: build
14
- cancel-in-progress: true
15
-
16
- jobs:
17
- build:
18
- uses: wxn0brP/workflow-dist/.github/workflows/build-ts.yml@main
19
- with:
20
- scriptsHandling: "remove-all"
21
- publishToNpm: true
22
-
23
- secrets:
24
- NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
package/CHANGELOG.md DELETED
@@ -1,50 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
-
5
- ### [0.0.6](https://github.com/wxn0brp/ValtheraDB-storage-bin/compare/v0.0.5...v0.0.6) (2025-09-18)
6
-
7
- ### [0.0.5](https://github.com/wxn0brp/ValtheraDB-storage-bin/compare/v0.0.4...v0.0.5) (2025-09-04)
8
-
9
-
10
- ### Bug Fixes
11
-
12
- * crc and new crc mode ([cc79715](https://github.com/wxn0brp/ValtheraDB-storage-bin/commit/cc7971591dbcd39fd5b9371644d2c3e9997f0dbc))
13
-
14
- ### [0.0.4](https://github.com/wxn0brp/ValtheraDB-storage-bin/compare/v0.0.3...v0.0.4) (2025-09-04)
15
-
16
-
17
- ### Features
18
-
19
- * add generic, BinManager.write<T> ([a2bb851](https://github.com/wxn0brp/ValtheraDB-storage-bin/commit/a2bb851c2b3977d133b85bde917dd268b3256a41))
20
- * format.encode(data, collection) ([6a20cb5](https://github.com/wxn0brp/ValtheraDB-storage-bin/commit/6a20cb57444ac6e9e1d5a61a63316f6e3184118e))
21
-
22
- ### [0.0.3](https://github.com/wxn0brp/ValtheraDB-storage-bin/compare/v0.0.2...v0.0.3) (2025-08-24)
23
-
24
-
25
- ### Bug Fixes
26
-
27
- * crc ([2b872a1](https://github.com/wxn0brp/ValtheraDB-storage-bin/commit/2b872a11fc42c32bbbeed80c76d62524426392d5))
28
-
29
- ### 0.0.2 (2025-08-24)
30
-
31
-
32
- ### Features
33
-
34
- * check file integrality ([e2b1aac](https://github.com/wxn0brp/ValtheraDB-storage-bin/commit/e2b1aacddfd5d3cb8a21bb4400827608c361e9ed))
35
- * update ([cc2104c](https://github.com/wxn0brp/ValtheraDB-storage-bin/commit/cc2104c8dace53180efc76365930d63dd917d383))
36
- * update ([4e5a9a8](https://github.com/wxn0brp/ValtheraDB-storage-bin/commit/4e5a9a82ee01f96084d27b1cfd893e129d6675b7))
37
- * update ([fbec71a](https://github.com/wxn0brp/ValtheraDB-storage-bin/commit/fbec71a7cff65ed221ddbd7956bc252862e87760))
38
- * update ([e3b8edb](https://github.com/wxn0brp/ValtheraDB-storage-bin/commit/e3b8edb5c7f833ce0a948be878173e207946229b))
39
- * update ([65d73e4](https://github.com/wxn0brp/ValtheraDB-storage-bin/commit/65d73e4034944ecaa5963aa79d16a7fef795ce26))
40
- * update ([baebb1f](https://github.com/wxn0brp/ValtheraDB-storage-bin/commit/baebb1f4258303133ec315f72cffaefdbf252600))
41
- * update ([5f7c905](https://github.com/wxn0brp/ValtheraDB-storage-bin/commit/5f7c9053e97620cd2a5db4f9f703f5f8009f78ed))
42
- * update ([60d0347](https://github.com/wxn0brp/ValtheraDB-storage-bin/commit/60d0347b6f3ff562d851aee842c090d081ab0a8c))
43
- * update logs ([d79bd84](https://github.com/wxn0brp/ValtheraDB-storage-bin/commit/d79bd8439111f43ad4c99e44c860516695dc3123))
44
-
45
-
46
- ### Bug Fixes
47
-
48
- * capacity ([05de295](https://github.com/wxn0brp/ValtheraDB-storage-bin/commit/05de2951c005f98697e067e3857a331777110110))
49
- * data len int32 to uint32 ([534a3b0](https://github.com/wxn0brp/ValtheraDB-storage-bin/commit/534a3b07d465320c0334a2fa557fb4bc070e7139))
50
- * optimize ([d704727](https://github.com/wxn0brp/ValtheraDB-storage-bin/commit/d7047276d31ccba5425de32fc28a8a65fcd5e02f))
@@ -1,44 +0,0 @@
1
- # Data Structure
2
-
3
- This document describes the data structure used by the binary database format.
4
-
5
- ## File Layout
6
-
7
- The file is structured as follows:
8
-
9
- 1. **Header (64 bytes)**: Contains metadata about the file.
10
- 2. **Payload**: Contains the serialized collections and free list data.
11
- 3. **Data Blocks**: Contains the actual data for each collection.
12
-
13
- ### Header
14
-
15
- The header is a fixed-size block of 64 bytes, structured as follows:
16
-
17
- | Offset | Size | Name | Description |
18
- |--------|------|--------------|-------------------------------------------------------|
19
- | 0 | 4 | Version | File format version (currently 1) |
20
- | 4 | 4 | Payload Len | Length of the payload data |
21
- | 8 | 4 | Payload Off | Offset of the payload data from the header start |
22
- | 12 | 4 | Block Size | Preferred block size for allocations |
23
- | 16 | 4 | CRC32 | CRC32 checksum of the file (excluding this field) |
24
- | 20 | 44 | Reserved | Reserved for future use |
25
-
26
- ### Payload
27
-
28
- The payload contains the serialized list of collections and free blocks. It is a msgpack-encoded object with the following structure:
29
-
30
- ```ts
31
- {
32
- c: [string, number, number][]; // Collections: [name, offset, capacity]
33
- f: [number, number][]; // Free blocks: [offset, capacity]
34
- }
35
- ```
36
-
37
- ### Data Blocks
38
-
39
- Each collection's data is stored in a data block. A data block consists of:
40
-
41
- 1. **Length (4 bytes)**: A 32-bit unsigned integer representing the length of the data (Uint32).
42
- 2. **Data (variable)**: The actual data, padded to the nearest block size.
43
-
44
- The data is serialized using msgpack.
package/src/actions.ts DELETED
@@ -1,122 +0,0 @@
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
- }
package/src/bin/data.ts DELETED
@@ -1,100 +0,0 @@
1
- import { BinManager, CollectionMeta } from ".";
2
- import { getFileCrc } from "../crc32";
3
- import { _log } from "../log";
4
- import { FileMeta, saveHeaderAndPayload } from "./head";
5
- import { detectCollisions, pushToFreeList, readData, roundUpCapacity, writeData } from "./utils";
6
-
7
- export function findCollection(cmp: BinManager, name: string): CollectionMeta | undefined {
8
- return cmp.meta.collections.find(c => c.name === name);
9
- }
10
-
11
- export async function findFreeSlot(cmp: BinManager, size: number): Promise<FileMeta["freeList"][number] | undefined> {
12
- const { meta } = cmp;
13
- await _log(6, "Finding free slot for size:", size);
14
- const idx = meta.freeList.findIndex(f => f.capacity >= size);
15
-
16
- if (idx === -1) {
17
- await _log(6, "No suitable free slot found.");
18
- return undefined;
19
- }
20
-
21
- const slot = meta.freeList[idx];
22
- await _log(6, "Free slot found at index:", idx, "with capacity:", slot.capacity);
23
-
24
- meta.freeList.splice(idx, 1);
25
- await _log(6, "Slot removed from freeList:", slot);
26
-
27
- return slot;
28
- }
29
-
30
- export async function writeLogic(cmp: BinManager, collection: string, data: any) {
31
- const { fd, meta } = cmp;
32
- await _log(3, "Writing data to collection:", collection);
33
-
34
- const existingCollection = findCollection(cmp, collection);
35
- const encoded = Buffer.from(await cmp.options.format.encode(data, collection));
36
- const length = encoded.length;
37
- const capacity = roundUpCapacity(meta, length + 4);
38
-
39
- let offset = existingCollection?.offset;
40
- let existingOffset = existingCollection?.offset;
41
- let existingCapacity = existingCollection?.capacity;
42
-
43
- const collision = detectCollisions(meta, offset, capacity, [collection]);
44
- if (collision || !existingCollection) {
45
- if (collision) await _log(2, "Collision detected");
46
- const slot = await findFreeSlot(cmp, capacity);
47
- if (slot) {
48
- offset = slot.offset;
49
- await _log(4, "Found free slot at offset:", offset);
50
- } else {
51
- offset = meta.fileSize;
52
- meta.fileSize += capacity;
53
- await _log(4, "No free slot found, appending at offset:", offset);
54
- }
55
-
56
- if (!existingCollection) {
57
- meta.collections.push({ name: collection, offset, capacity });
58
- } else if (collision) {
59
- pushToFreeList(meta, existingOffset, existingCapacity);
60
- meta.collections = meta.collections.map(c => {
61
- if (c.offset === existingOffset) return { name: c.name, offset, capacity };
62
- return c;
63
- })
64
- }
65
-
66
- await _log(3, "Collection written");
67
- await saveHeaderAndPayload(cmp);
68
- }
69
-
70
- const buf = Buffer.alloc(4);
71
- buf.writeUInt32LE(length, 0);
72
- await writeData(fd, offset, buf, 4);
73
- await writeData(fd, offset + 4, encoded, capacity);
74
-
75
- if (existingCollection && length >= existingCollection.capacity) {
76
- meta.collections = meta.collections.map(c => {
77
- if (c.offset === offset) return { name: c.name, offset, capacity };
78
- return c;
79
- });
80
- await saveHeaderAndPayload(cmp);
81
- await _log(2, "Capacity exceeded");
82
- } else {
83
- const crc = cmp.options.crc;
84
- if (crc === 3 || crc === 4) {
85
- const { computedCrc } = await getFileCrc(fd);
86
- const crcBuf = Buffer.alloc(16);
87
- crcBuf.writeUInt32LE(computedCrc);
88
- await writeData(fd, 16, crcBuf, 16);
89
- }
90
- }
91
- }
92
-
93
- export async function readLogic(cmp: BinManager, collection: string) {
94
- const collectionMeta = findCollection(cmp, collection);
95
- if (!collectionMeta) throw new Error("Collection not found");
96
-
97
- const len = await readData(cmp.fd, collectionMeta.offset, 4);
98
- const data = await readData(cmp.fd, collectionMeta.offset + 4, len.readUInt32LE(0));
99
- return await cmp.options.format.decode(data, collection);
100
- }
package/src/bin/head.ts DELETED
@@ -1,183 +0,0 @@
1
- import { BinManager, CollectionMeta } from ".";
2
- import { getFileCrc } from "../crc32";
3
- import { _log } from "../log";
4
- import { findFreeSlot } from "./data";
5
- import { HEADER_SIZE, VERSION } from "./static";
6
- import { detectCollisions, pushToFreeList, roundUpCapacity, writeData } from "./utils";
7
-
8
- export interface Block {
9
- offset: number;
10
- capacity: number;
11
- };
12
-
13
- export interface FileMeta {
14
- collections: CollectionMeta[];
15
- freeList: Block[];
16
- fileSize: number;
17
- payloadLength: number;
18
- payloadOffset: number;
19
- blockSize: number;
20
- }
21
-
22
- export async function openFile(cmp: BinManager) {
23
- const { fd, options } = cmp;
24
- const stats = await fd.stat();
25
- const fileSize = stats.size;
26
- await _log(2, "File size:", fileSize);
27
-
28
- const meta: FileMeta = {
29
- collections: [],
30
- freeList: [],
31
- fileSize,
32
- payloadLength: 0,
33
- payloadOffset: 0,
34
- blockSize: options.preferredSize ?? 256,
35
- }
36
- cmp.meta = meta;
37
-
38
- if (fileSize < HEADER_SIZE) {
39
- await _log(2, "Initializing new file header");
40
- await saveHeaderAndPayload(cmp);
41
- await _log(6, "Header initialized with size:", HEADER_SIZE);
42
- return meta;
43
- }
44
-
45
- const headerBuf = Buffer.alloc(HEADER_SIZE);
46
- await fd.read(headerBuf, 0, HEADER_SIZE, 0);
47
- await _log(6, "Header read from file");
48
-
49
- const version = headerBuf.readUInt32LE(0);
50
- if (version !== VERSION) {
51
- await _log(6, "err", `Unsupported file version: ${version}`);
52
- throw new Error(`Unsupported file version ${version}`);
53
- }
54
- await _log(2, "File version:", version);
55
-
56
- const payloadLength = headerBuf.readUInt32LE(4);
57
- meta.payloadLength = payloadLength;
58
- await _log(6, "Payload length:", payloadLength);
59
-
60
- const payloadOffset = headerBuf.readUInt32LE(8);
61
- meta.payloadOffset = payloadOffset;
62
- await _log(6, "Payload offset:", payloadOffset);
63
-
64
- const blockSize = headerBuf.readUInt32LE(12);
65
- meta.blockSize = blockSize;
66
- await _log(2, "Block size:", blockSize);
67
-
68
- if (options.crc) {
69
- const { computedCrc, storedCrc } = await getFileCrc(fd);
70
- const validCrc = computedCrc === storedCrc || storedCrc === 0;
71
- await _log(2, "CRC:", computedCrc, "Needed CRC:", storedCrc, "Valid:", validCrc);
72
- if (storedCrc === 0) {
73
- await _log(1, "Warning: CRC is zero, CRC will not be checked");
74
- }
75
- if (!validCrc) {
76
- await _log(0, "err", "Invalid CRC");
77
- if (options.crc === 2 || options.crc === 4)
78
- throw new Error("Invalid CRC");
79
- }
80
- }
81
-
82
- if (payloadOffset + payloadLength > fileSize - HEADER_SIZE) {
83
- await _log(6, "err", "Invalid payload length");
84
- throw new Error("Invalid payload length");
85
- }
86
-
87
- if (payloadLength === 0) {
88
- await _log(6, "Empty payload, initializing collections and freeList");
89
- return meta;
90
- }
91
-
92
- await readHeaderPayload(cmp);
93
- return meta;
94
- }
95
-
96
- export async function readHeaderPayload(cmp: BinManager) {
97
- const { fd, meta } = cmp;
98
- const { payloadLength, payloadOffset } = meta;
99
-
100
- const payloadBuf = Buffer.alloc(payloadLength);
101
- const { bytesRead } = await fd.read(payloadBuf, 0, payloadLength, HEADER_SIZE + payloadOffset);
102
- await _log(6, `Payload header read, bytesRead: ${bytesRead}`);
103
-
104
- if (bytesRead < payloadLength) {
105
- await _log(6, "err", `Incomplete payload header read: expected ${payloadLength} bytes, got ${bytesRead}`);
106
- throw new Error(`Incomplete payload header read: expected ${payloadLength} bytes, got ${bytesRead}`);
107
- }
108
-
109
- const obj = await cmp.options.format.decode(payloadBuf, "") as {
110
- c: [string, number, number][];
111
- f: [number, number][];
112
- };
113
-
114
- meta.collections = (obj.c || []).map(([name, offset, capacity]) => ({ name, offset, capacity }));
115
- meta.freeList = (obj.f || []).map(([offset, capacity]) => ({ offset, capacity }));
116
-
117
- await _log(6, "Collections and freeList loaded", meta);
118
- }
119
-
120
- export function getHeaderPayload(meta: FileMeta) {
121
- return {
122
- c: meta.collections.map(({ name, offset, capacity }) => ([name, offset, capacity])),
123
- f: meta.freeList.map(({ offset, capacity }) => [offset, capacity]),
124
- };
125
- }
126
-
127
- export async function saveHeaderAndPayload(cmp: BinManager, recursion = false) {
128
- const { fd, meta, options } = cmp;
129
- if (!fd) throw new Error("File not open");
130
-
131
- const { collections, freeList, fileSize } = meta;
132
- await _log(6, "Saving header payload:", collections, freeList);
133
-
134
- const payloadObj = getHeaderPayload(meta);
135
-
136
- const payloadBuf = Buffer.from(await cmp.options.format.encode(payloadObj, ""));
137
- if (payloadBuf.length > 64 * 1024) {
138
- console.error("Header payload too large");
139
- throw new Error("Header payload too large");
140
- }
141
-
142
- await _log(6, "Header payload length:", payloadBuf.length);
143
-
144
- const headerBuf = Buffer.alloc(HEADER_SIZE);
145
- headerBuf.writeUInt32LE(VERSION, 0);
146
- headerBuf.writeUInt32LE(payloadBuf.length, 4);
147
- headerBuf.writeUInt32LE(meta.payloadOffset, 8);
148
- headerBuf.writeUInt32LE(meta.blockSize, 12);
149
- meta.payloadLength = payloadBuf.length;
150
-
151
- if (options.crc) {
152
- const { computedCrc: crc } = await getFileCrc(fd);
153
- headerBuf.writeUInt32LE(crc, 16);
154
- }
155
-
156
- await _log(6, "Writing header:", headerBuf.toString("hex"));
157
-
158
- // Write header
159
- await fd.write(headerBuf, 0, HEADER_SIZE, 0);
160
- // Write payload
161
- const roundPayload = roundUpCapacity(meta, payloadBuf.length);
162
-
163
- if (detectCollisions(meta, HEADER_SIZE + meta.payloadOffset, roundPayload)) {
164
- await _log(2, "Collision detected");
165
- const slot = !recursion && await findFreeSlot(cmp, roundPayload);
166
- if (slot) {
167
- meta.payloadOffset = slot.offset - HEADER_SIZE;
168
- } else {
169
- meta.payloadOffset = meta.fileSize - HEADER_SIZE;
170
- meta.fileSize += roundPayload;
171
- }
172
- pushToFreeList(meta, meta.payloadOffset, roundPayload);
173
-
174
- return await saveHeaderAndPayload(cmp, true);
175
- }
176
-
177
- await writeData(fd, HEADER_SIZE + meta.payloadOffset, payloadBuf, roundPayload);
178
-
179
- await _log(6, "Payload written");
180
-
181
- // Update file size if header + payload bigger
182
- meta.fileSize = Math.max(fileSize, HEADER_SIZE + roundPayload);
183
- }
package/src/bin/index.ts DELETED
@@ -1,115 +0,0 @@
1
- import * as msgpack from "@msgpack/msgpack";
2
- import { access, constants, FileHandle, open } from "fs/promises";
3
- import { getFileCrc } from "../crc32";
4
- import { _log } from "../log";
5
- import { readLogic, writeLogic } from "./data";
6
- import { FileMeta, openFile } from "./head";
7
- import { optimize } from "./optimize";
8
- import { removeCollection } from "./rm";
9
-
10
- async function safeOpen(path: string) {
11
- try {
12
- await access(path, constants.F_OK);
13
- return await open(path, "r+");
14
- } catch {
15
- _log(1, "Creating new file");
16
- return await open(path, "w+");
17
- }
18
- }
19
-
20
- export interface CollectionMeta {
21
- name: string;
22
- offset: number;
23
- capacity: number;
24
- }
25
-
26
- export interface Options {
27
- preferredSize: number;
28
- /**
29
- * 0 - crc off
30
- * 1 - warn if error
31
- * 2 - throw if error
32
- * 3 - 1 & save always on edit
33
- * 4 - 2 & save always on edit
34
- */
35
- crc: number;
36
- overwriteRemovedCollection: boolean;
37
- format: {
38
- encode(data: any, collection: string): Promise<Parameters<typeof Buffer.from>[0]>;
39
- decode(data: Buffer, collection: string): Promise<any>;
40
- }
41
- }
42
-
43
- export class BinManager {
44
- public fd: null | FileHandle = null;
45
- public meta: FileMeta;
46
- public options: Options;
47
-
48
- /**
49
- * Constructs a new BinManager instance.
50
- * @param path - File path.
51
- * @param [preferredSize=512] - The preferred block size for the database. Must be a positive number (preferredSize > 0)
52
- * @throws If the path is not provided, or the preferred size is
53
- * not a positive number.
54
- */
55
- constructor(public path: string, options?: Partial<Options>) {
56
- if (!path) throw new Error("Path not provided");
57
-
58
- this.options = {
59
- preferredSize: 512,
60
- crc: 2,
61
- overwriteRemovedCollection: false,
62
- format: {
63
- encode: async (data: any) => msgpack.encode(data),
64
- decode: async (data: Buffer) => msgpack.decode(data)
65
- },
66
- ...options
67
- }
68
-
69
- if (!this.options.preferredSize || this.options.preferredSize <= 0) throw new Error("Preferred size not provided");
70
- }
71
-
72
- async open() {
73
- this.fd = await safeOpen(this.path);
74
- await openFile(this);
75
- }
76
-
77
- async close() {
78
- if (this.fd) {
79
- const buff = Buffer.alloc(8);
80
- if (this.options.crc) {
81
- const { computedCrc: crc } = await getFileCrc(this.fd);
82
- buff.writeUInt32LE(crc, 0);
83
- } else {
84
- buff.fill(0, 0, 8);
85
- }
86
- await this.fd.write(buff, 0, 8, 16);
87
- await this.fd.close();
88
- this.fd = null;
89
- }
90
- }
91
-
92
- [Symbol.asyncDispose]() {
93
- return this.close();
94
- }
95
-
96
- async write<T = object[]>(collection: string, data: T) {
97
- if (!this.fd) throw new Error("File not open");
98
- await writeLogic(this, collection, data);
99
- }
100
-
101
- async read(collection: string) {
102
- if (!this.fd) throw new Error("File not open");
103
- return await readLogic(this, collection);
104
- }
105
-
106
- async optimize() {
107
- if (!this.fd) throw new Error("File not open");
108
- await optimize(this);
109
- }
110
-
111
- async removeCollection(collection: string) {
112
- if (!this.fd) throw new Error("File not open");
113
- await removeCollection(this, collection);
114
- }
115
- }
@@ -1,46 +0,0 @@
1
- import { unlink } from "fs/promises";
2
- import { BinManager } from ".";
3
- import { saveHeaderAndPayload } from "./head";
4
- import { HEADER_SIZE } from "./static";
5
- import { readData, roundUpCapacity, writeData } from "./utils";
6
- import { _log } from "../log";
7
-
8
- export async function optimize(cmp: BinManager) {
9
- await _log(3, "Starting database optimization");
10
- const collections = cmp.meta.collections;
11
-
12
- const allData = new Map<string, Buffer>();
13
- for (const { name, offset } of collections) {
14
- await _log(6, "Reading collection for optimization:", name);
15
- const len = await readData(cmp.fd, offset, 4);
16
- const data = await readData(cmp.fd, offset + 4, len.readInt32LE(0));
17
- allData.set(name, data);
18
- }
19
-
20
- await _log(5, "Closing file for optimization");
21
- await cmp.close();
22
- await _log(6, "Deleting old database file for optimization");
23
- await unlink(cmp.path);
24
- await new Promise(resolve => setTimeout(resolve, 100));
25
- await _log(5, "Re-opening database file for optimization");
26
- await cmp.open();
27
-
28
- let offset = roundUpCapacity(cmp.meta, cmp.meta.payloadLength + HEADER_SIZE) + cmp.meta.blockSize;
29
- for (const [collection, data] of allData) {
30
- await _log(6, "Writing optimized collection:", collection);
31
- const len = roundUpCapacity(cmp.meta, data.length + 4);
32
- const buf = Buffer.alloc(4);
33
- buf.writeInt32LE(data.length, 0);
34
- await writeData(cmp.fd, offset, buf, 4);
35
- await writeData(cmp.fd, offset + 4, data, len);
36
- cmp.meta.collections.push({
37
- name: collection,
38
- offset,
39
- capacity: len
40
- });
41
- offset += len;
42
- }
43
-
44
- await saveHeaderAndPayload(cmp);
45
- await _log(3, "Database optimization complete");
46
- }
package/src/bin/rm.ts DELETED
@@ -1,30 +0,0 @@
1
- import { BinManager } from ".";
2
- import { findCollection } from "./data";
3
- import { saveHeaderAndPayload } from "./head";
4
- import { writeData } from "./utils";
5
-
6
- export async function removeCollection(cmp: BinManager, collection: string) {
7
- const { meta, fd, options } = cmp;
8
- const collectionMeta = findCollection(cmp, collection);
9
- if (!collectionMeta) throw new Error("Collection not found");
10
-
11
- if (meta.collections.length === 1) {
12
- meta.collections = [];
13
- meta.freeList = [];
14
- await fd.truncate(0);
15
- await saveHeaderAndPayload(cmp);
16
- return;
17
- }
18
-
19
- meta.collections.splice(meta.collections.findIndex(c => c.name === collection), 1);
20
- meta.freeList.push({
21
- offset: collectionMeta.offset,
22
- capacity: collectionMeta.capacity
23
- });
24
-
25
- if (options.overwriteRemovedCollection) {
26
- await writeData(fd, collectionMeta.offset, Buffer.alloc(collectionMeta.capacity), collectionMeta.capacity);
27
- }
28
-
29
- await saveHeaderAndPayload(cmp);
30
- }
package/src/bin/static.ts DELETED
@@ -1,2 +0,0 @@
1
- export const HEADER_SIZE = 64;
2
- export const VERSION = 1;
package/src/bin/utils.ts DELETED
@@ -1,90 +0,0 @@
1
- import { FileHandle } from "fs/promises";
2
- import { _log } from "../log";
3
- import { Block, FileMeta } from "./head";
4
-
5
- export function roundUpCapacity(result: FileMeta, size: number) {
6
- return Math.ceil(size / result.blockSize) * result.blockSize;
7
- }
8
-
9
- export async function writeData(fd: FileHandle, offset: number, data: Buffer, capacity: number) {
10
- if (!fd) throw new Error("File not open");
11
- if (data.length > capacity) throw new Error("Data size exceeds capacity");
12
-
13
- await _log(6, "Writing data at offset:", offset, "length:", data.length, "capacity:", capacity);
14
-
15
- const { bytesWritten } = await fd.write(data, 0, data.length, offset);
16
- await _log(5, "Bytes written:", bytesWritten);
17
-
18
- if (data.length < capacity) {
19
- const pad = Buffer.alloc(capacity - data.length, 0);
20
- const padStart = offset + data.length;
21
- await _log(6, "Padding with zeros:", pad.length, "at offset:", padStart);
22
- const { bytesWritten: padBytesWritten } = await fd.write(pad, 0, pad.length, padStart);
23
- await _log(6, "Bytes written:", padBytesWritten);
24
- }
25
-
26
- await _log(6, "Data written");
27
- }
28
-
29
- export async function readData(fd: FileHandle, offset: number, length: number): Promise<Buffer> {
30
- if (!fd) throw new Error("File not open");
31
-
32
- await _log(6, "Reading data from offset:", offset, "length:", length);
33
-
34
- const buf = Buffer.alloc(length);
35
- const { bytesRead } = await fd.read(buf, 0, length, offset);
36
-
37
- await _log(5, "Bytes read:", bytesRead);
38
-
39
- return buf;
40
- }
41
-
42
- export function optimizeFreeList(blocks: Block[]): Block[] {
43
- if (blocks.length <= 1) return blocks;
44
-
45
- const sorted = [...blocks].sort((a, b) => a.offset - b.offset);
46
-
47
- const merged: Block[] = [];
48
- let current = sorted[0];
49
-
50
- for (let i = 1; i < sorted.length; i++) {
51
- const next = sorted[i];
52
-
53
- if (current.offset + current.capacity === next.offset) {
54
- current = {
55
- offset: current.offset,
56
- capacity: current.capacity + next.capacity
57
- };
58
- } else {
59
- merged.push(current);
60
- current = next;
61
- }
62
- }
63
-
64
- merged.push(current);
65
-
66
- return merged;
67
- }
68
-
69
- function checkCollection(start1: number, end1: number, start2: number, end2: number) {
70
- _log(6, "Checking collection:", start1, end1, start2, end2);
71
- return start1 < end2 && start2 < end1;
72
- }
73
-
74
- export function detectCollisions(result: FileMeta, start: number, size: number, skip: string[] = []) {
75
- for (const { name, offset, capacity } of result.collections) {
76
- if (skip.includes(name)) continue;
77
- if (checkCollection(offset, offset + capacity, start, start + size))
78
- return true;
79
- }
80
-
81
- return false;
82
- }
83
-
84
- export function pushToFreeList(result: FileMeta, offset: number, len: number) {
85
- result.freeList.push({
86
- offset,
87
- capacity: roundUpCapacity(result, len),
88
- });
89
- result.freeList = optimizeFreeList(result.freeList);
90
- }
package/src/crc32.ts DELETED
@@ -1,44 +0,0 @@
1
- import { FileHandle } from "fs/promises";
2
- import { HEADER_SIZE } from "./bin/static";
3
-
4
- const CRC32_TABLE = new Uint32Array(256);
5
-
6
- for (let i = 0; i < 256; i++) {
7
- let crc = i;
8
- for (let j = 0; j < 8; j++) {
9
- crc = (crc & 1) ? (0xEDB88320 ^ (crc >>> 1)) : (crc >>> 1);
10
- }
11
- CRC32_TABLE[i] = crc >>> 0;
12
- }
13
-
14
- export function crc32(buf: Uint8Array | string, seed = 0xFFFFFFFF): number {
15
- if (typeof buf === "string") {
16
- buf = new TextEncoder().encode(buf);
17
- }
18
-
19
- let crc = seed ^ 0xFFFFFFFF;
20
- for (let i = 0; i < buf.length; i++) {
21
- const byte = buf[i];
22
- crc = (crc >>> 8) ^ CRC32_TABLE[(crc ^ byte) & 0xFF];
23
- }
24
- return (crc ^ 0xFFFFFFFF) >>> 0;
25
- }
26
-
27
- export async function getFileCrc(fd: FileHandle, short = false) {
28
- const { size } = await fd.stat();
29
- if (size < HEADER_SIZE) return { storedCrc: 0, computedCrc: 0 };
30
-
31
- const buffer = Buffer.alloc(size);
32
- await fd.read(buffer, 0, size, 0);
33
-
34
- const storedCrc = buffer.readUInt32LE(16);
35
- if (short && storedCrc === 0) return { storedCrc: 0, computedCrc: 0 };
36
-
37
- buffer.fill(0, 16, 20);
38
- const computedCrc = crc32(buffer);
39
-
40
- return {
41
- storedCrc,
42
- computedCrc
43
- }
44
- }
package/src/index.ts DELETED
@@ -1,20 +0,0 @@
1
- import { ValtheraClass } from "@wxn0brp/db-core";
2
- import { BinFileAction } from "./actions";
3
- import { BinManager, Options } from "./bin";
4
-
5
- export * from "./actions";
6
- export * from "./bin";
7
-
8
- export async function createBinValthera(path: string, opts: Partial<Options> = {}, init = true) {
9
- const mgr = new BinManager(path, opts);
10
- const actions = new BinFileAction(mgr);
11
- const db = new ValtheraClass({ dbAction: actions });
12
-
13
- if (init) await actions.init();
14
-
15
- return {
16
- db,
17
- actions,
18
- mgr,
19
- }
20
- }
package/src/log.ts DELETED
@@ -1,13 +0,0 @@
1
- const dir = process.cwd() + "/";
2
- export async function _log(level: number, ...data: any[]) {
3
- const logLevel = parseInt(process.env.VDB_BIN_LOG_LEVEL || '0', 10);
4
- if (logLevel < level) return;
5
-
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
-
10
- if (path.length < 2) path = line.replace(dir, "").replace("at ", ""); // if path is 2 (callback):
11
-
12
- console.log(`[${level}] ` + "\x1b[36m" + path + ":", "\x1b[33m" + at + "\x1b[0m", ...data);
13
- }
package/suglite.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "cmd": "NODE_ENV=development tsc --noEmit && yarn bun",
3
- "watch": [
4
- "src"
5
- ],
6
- "restart_cmd": "clear",
7
- "events": {
8
- "rs": "clear"
9
- }
10
- }
package/tsconfig.json DELETED
@@ -1,22 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "module": "ES2022",
4
- "target": "ES2022",
5
- "moduleResolution": "bundler",
6
- "paths": {},
7
- "esModuleInterop": true,
8
- "skipLibCheck": true,
9
- "outDir": "./dist",
10
- "declaration": true
11
- },
12
- "include": [
13
- "./src"
14
- ],
15
- "exclude": [
16
- "node_modules"
17
- ],
18
- "tsc-alias": {
19
- "resolveFullPaths": true,
20
- "verbose": false
21
- }
22
- }