@wxn0brp/db-storage-bin 0.0.2 → 0.0.4

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/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
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
4
 
5
+ ### [0.0.4](https://github.com/wxn0brp/ValtheraDB-storage-bin/compare/v0.0.3...v0.0.4) (2025-09-04)
6
+
7
+
8
+ ### Features
9
+
10
+ * add generic, BinManager.write<T> ([a2bb851](https://github.com/wxn0brp/ValtheraDB-storage-bin/commit/a2bb851c2b3977d133b85bde917dd268b3256a41))
11
+ * format.encode(data, collection) ([6a20cb5](https://github.com/wxn0brp/ValtheraDB-storage-bin/commit/6a20cb57444ac6e9e1d5a61a63316f6e3184118e))
12
+
13
+ ### [0.0.3](https://github.com/wxn0brp/ValtheraDB-storage-bin/compare/v0.0.2...v0.0.3) (2025-08-24)
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * crc ([2b872a1](https://github.com/wxn0brp/ValtheraDB-storage-bin/commit/2b872a11fc42c32bbbeed80c76d62524426392d5))
19
+
5
20
  ### 0.0.2 (2025-08-24)
6
21
 
7
22
 
package/README.md CHANGED
@@ -1,13 +1,11 @@
1
1
  # ValtheraDB Bin Plugin
2
2
 
3
- This is a proof-of-concept for an addon/plugin for the `@wxn0brp/db` (ValtheraDB) library.
4
-
5
3
  The purpose of this experiment is to create a storage layer that allows ValtheraDB, which normally operates on a directory/file structure, to instead use a single binary file for data storage.
6
4
 
7
5
  ## Installation
8
6
 
9
7
  ```bash
10
- yarn add github:wxn0brP/ValtheraDB-storage-bin#dist
8
+ yarn add @wxn0brp/db-storage-bin
11
9
  ```
12
10
 
13
11
  ## Usage
@@ -55,6 +53,11 @@ Returns an object containing:
55
53
  - `options`:
56
54
  - `preferredSize`: The preferred block size for the database (default: `256`).
57
55
 
56
+ ## Documentation
57
+
58
+ - [Data Structure](https://github.com/wxn0brP/ValtheraDB-storage-bin/blob/master/docs/data-structure.md)
59
+ - [ValtheraDB-Core](https://github.com/wxn0brP/ValtheraDB-core)
60
+
58
61
  ## License
59
62
 
60
63
  This project is licensed under the MIT License.
@@ -2,5 +2,5 @@ import { BinManager, CollectionMeta } from "./index.js";
2
2
  import { FileMeta } from "./head.js";
3
3
  export declare function findCollection(cmp: BinManager, name: string): CollectionMeta | undefined;
4
4
  export declare function findFreeSlot(cmp: BinManager, size: number): Promise<FileMeta["freeList"][number] | undefined>;
5
- export declare function writeLogic(cmp: BinManager, collection: string, data: object[]): Promise<void>;
5
+ export declare function writeLogic(cmp: BinManager, collection: string, data: any): Promise<void>;
6
6
  export declare function readLogic(cmp: BinManager, collection: string): Promise<any>;
package/dist/bin/data.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { getFileCrc } from "../crc32.js";
1
2
  import { _log } from "../log.js";
2
3
  import { saveHeaderAndPayload } from "./head.js";
3
4
  import { detectCollisions, pushToFreeList, readData, roundUpCapacity, writeData } from "./utils.js";
@@ -22,7 +23,7 @@ export async function writeLogic(cmp, collection, data) {
22
23
  const { fd, meta } = cmp;
23
24
  await _log(3, "Writing data to collection:", collection);
24
25
  const existingCollection = findCollection(cmp, collection);
25
- const encoded = Buffer.from(await cmp.options.format.encode(data));
26
+ const encoded = Buffer.from(await cmp.options.format.encode(data, collection));
26
27
  const length = encoded.length;
27
28
  const capacity = roundUpCapacity(meta, length + 4);
28
29
  let offset = existingCollection?.offset;
@@ -69,6 +70,14 @@ export async function writeLogic(cmp, collection, data) {
69
70
  await saveHeaderAndPayload(cmp);
70
71
  await _log(2, "Capacity exceeded");
71
72
  }
73
+ else {
74
+ if (cmp.options.crc) {
75
+ const { computedCrc } = await getFileCrc(fd);
76
+ const crcBuf = Buffer.alloc(16);
77
+ crcBuf.writeUInt32LE(computedCrc);
78
+ await writeData(fd, 16, crcBuf, 16);
79
+ }
80
+ }
72
81
  }
73
82
  export async function readLogic(cmp, collection) {
74
83
  const collectionMeta = findCollection(cmp, collection);
@@ -76,5 +85,5 @@ export async function readLogic(cmp, collection) {
76
85
  throw new Error("Collection not found");
77
86
  const len = await readData(cmp.fd, collectionMeta.offset, 4);
78
87
  const data = await readData(cmp.fd, collectionMeta.offset + 4, len.readUInt32LE(0));
79
- return await cmp.options.format.decode(data);
88
+ return await cmp.options.format.decode(data, collection);
80
89
  }
package/dist/bin/head.js CHANGED
@@ -76,7 +76,7 @@ export async function readHeaderPayload(cmp) {
76
76
  await _log(6, "err", `Incomplete payload header read: expected ${payloadLength} bytes, got ${bytesRead}`);
77
77
  throw new Error(`Incomplete payload header read: expected ${payloadLength} bytes, got ${bytesRead}`);
78
78
  }
79
- const obj = await cmp.options.format.decode(payloadBuf);
79
+ const obj = await cmp.options.format.decode(payloadBuf, "");
80
80
  meta.collections = (obj.c || []).map(([name, offset, capacity]) => ({ name, offset, capacity }));
81
81
  meta.freeList = (obj.f || []).map(([offset, capacity]) => ({ offset, capacity }));
82
82
  await _log(6, "Collections and freeList loaded", meta);
@@ -94,7 +94,7 @@ export async function saveHeaderAndPayload(cmp, recursion = false) {
94
94
  const { collections, freeList, fileSize } = meta;
95
95
  await _log(6, "Saving header payload:", collections, freeList);
96
96
  const payloadObj = getHeaderPayload(meta);
97
- const payloadBuf = Buffer.from(await cmp.options.format.encode(payloadObj));
97
+ const payloadBuf = Buffer.from(await cmp.options.format.encode(payloadObj, ""));
98
98
  if (payloadBuf.length > 64 * 1024) {
99
99
  console.error("Header payload too large");
100
100
  throw new Error("Header payload too large");
@@ -15,8 +15,8 @@ export interface Options {
15
15
  crc: number;
16
16
  overwriteRemovedCollection: boolean;
17
17
  format: {
18
- encode(data: any): Promise<Parameters<typeof Buffer.from>[0]>;
19
- decode(data: Buffer): Promise<any>;
18
+ encode(data: any, collection: string): Promise<Parameters<typeof Buffer.from>[0]>;
19
+ decode(data: Buffer, collection: string): Promise<any>;
20
20
  };
21
21
  }
22
22
  export declare class BinManager {
@@ -34,7 +34,7 @@ export declare class BinManager {
34
34
  constructor(path: string, options?: Partial<Options>);
35
35
  open(): Promise<void>;
36
36
  close(): Promise<void>;
37
- write(collection: string, data: object[]): Promise<void>;
37
+ write<T = object[]>(collection: string, data: T): Promise<void>;
38
38
  read(collection: string): Promise<any>;
39
39
  optimize(): Promise<void>;
40
40
  removeCollection(collection: string): Promise<void>;
@@ -0,0 +1,44 @@
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wxn0brp/db-storage-bin",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "author": "wxn0brP",
package/src/bin/data.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { BinManager, CollectionMeta } from ".";
2
+ import { getFileCrc } from "../crc32";
2
3
  import { _log } from "../log";
3
4
  import { FileMeta, saveHeaderAndPayload } from "./head";
4
5
  import { detectCollisions, pushToFreeList, readData, roundUpCapacity, writeData } from "./utils";
@@ -26,12 +27,12 @@ export async function findFreeSlot(cmp: BinManager, size: number): Promise<FileM
26
27
  return slot;
27
28
  }
28
29
 
29
- export async function writeLogic(cmp: BinManager, collection: string, data: object[]) {
30
+ export async function writeLogic(cmp: BinManager, collection: string, data: any) {
30
31
  const { fd, meta } = cmp;
31
32
  await _log(3, "Writing data to collection:", collection);
32
33
 
33
34
  const existingCollection = findCollection(cmp, collection);
34
- const encoded = Buffer.from(await cmp.options.format.encode(data));
35
+ const encoded = Buffer.from(await cmp.options.format.encode(data, collection));
35
36
  const length = encoded.length;
36
37
  const capacity = roundUpCapacity(meta, length + 4);
37
38
 
@@ -78,6 +79,13 @@ export async function writeLogic(cmp: BinManager, collection: string, data: obje
78
79
  });
79
80
  await saveHeaderAndPayload(cmp);
80
81
  await _log(2, "Capacity exceeded");
82
+ } else {
83
+ if (cmp.options.crc) {
84
+ const { computedCrc } = await getFileCrc(fd);
85
+ const crcBuf = Buffer.alloc(16);
86
+ crcBuf.writeUInt32LE(computedCrc);
87
+ await writeData(fd, 16, crcBuf, 16);
88
+ }
81
89
  }
82
90
  }
83
91
 
@@ -87,5 +95,5 @@ export async function readLogic(cmp: BinManager, collection: string) {
87
95
 
88
96
  const len = await readData(cmp.fd, collectionMeta.offset, 4);
89
97
  const data = await readData(cmp.fd, collectionMeta.offset + 4, len.readUInt32LE(0));
90
- return await cmp.options.format.decode(data);
98
+ return await cmp.options.format.decode(data, collection);
91
99
  }
package/src/bin/head.ts CHANGED
@@ -106,7 +106,7 @@ export async function readHeaderPayload(cmp: BinManager) {
106
106
  throw new Error(`Incomplete payload header read: expected ${payloadLength} bytes, got ${bytesRead}`);
107
107
  }
108
108
 
109
- const obj = await cmp.options.format.decode(payloadBuf) as {
109
+ const obj = await cmp.options.format.decode(payloadBuf, "") as {
110
110
  c: [string, number, number][];
111
111
  f: [number, number][];
112
112
  };
@@ -133,7 +133,7 @@ export async function saveHeaderAndPayload(cmp: BinManager, recursion = false) {
133
133
 
134
134
  const payloadObj = getHeaderPayload(meta);
135
135
 
136
- const payloadBuf = Buffer.from(await cmp.options.format.encode(payloadObj));
136
+ const payloadBuf = Buffer.from(await cmp.options.format.encode(payloadObj, ""));
137
137
  if (payloadBuf.length > 64 * 1024) {
138
138
  console.error("Header payload too large");
139
139
  throw new Error("Header payload too large");
package/src/bin/index.ts CHANGED
@@ -33,8 +33,8 @@ export interface Options {
33
33
  crc: number;
34
34
  overwriteRemovedCollection: boolean;
35
35
  format: {
36
- encode(data: any): Promise<Parameters<typeof Buffer.from>[0]>;
37
- decode(data: Buffer): Promise<any>;
36
+ encode(data: any, collection: string): Promise<Parameters<typeof Buffer.from>[0]>;
37
+ decode(data: Buffer, collection: string): Promise<any>;
38
38
  }
39
39
  }
40
40
 
@@ -87,7 +87,7 @@ export class BinManager {
87
87
  }
88
88
  }
89
89
 
90
- async write(collection: string, data: object[]) {
90
+ async write<T = object[]>(collection: string, data: T) {
91
91
  if (!this.fd) throw new Error("File not open");
92
92
  await writeLogic(this, collection, data);
93
93
  }