@wxn0brp/db-storage-bin 0.100.2 → 0.110.0-alpha.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.
@@ -0,0 +1,3 @@
1
+ import { VQueryT } from "@wxn0brp/db-core/types/query";
2
+ import { BinManager } from "./index.js";
3
+ export declare function add(cmp: BinManager, config: VQueryT.Add): Promise<void>;
@@ -0,0 +1,27 @@
1
+ import { _log } from "../log.js";
2
+ import { ensureCollection } from "./collection.js";
3
+ import { INT_SIZE } from "./static.js";
4
+ import { readCollectionEof, writeData } from "./utils.js";
5
+ export async function add(cmp, config) {
6
+ const { data } = config;
7
+ const { fd } = cmp;
8
+ await _log(3, "Writing data to collection:", config.collection);
9
+ const encoded = Buffer.from(await cmp.options.format.encode(data, config.collection));
10
+ const length = encoded.length;
11
+ await _log(5, "Encoded data length:", length);
12
+ const collection = await ensureCollection(cmp, config.collection, length, true);
13
+ const collectionEOF = await readCollectionEof(fd, collection.offset);
14
+ await _log(5, "Read collection EOF:", collectionEOF);
15
+ // skip: collection length metadata + collection length
16
+ const offset = collection.offset + collectionEOF + INT_SIZE;
17
+ await _log(5, "Calculated offset:", offset);
18
+ const dataLengthBuffer = Buffer.alloc(INT_SIZE);
19
+ dataLengthBuffer.writeUInt32LE(length, 0);
20
+ await writeData(fd, offset, dataLengthBuffer, INT_SIZE);
21
+ await writeData(fd, offset + INT_SIZE, encoded, length);
22
+ const newEOF = collectionEOF + INT_SIZE + length;
23
+ await _log(5, "New collection EOF:", newEOF);
24
+ const collectionLengthBuffer = Buffer.alloc(INT_SIZE);
25
+ collectionLengthBuffer.writeUInt32LE(newEOF, 0);
26
+ await writeData(fd, collection.offset, collectionLengthBuffer, INT_SIZE);
27
+ }
@@ -0,0 +1,2 @@
1
+ import { BinManager, CollectionMeta } from "./index.js";
2
+ export declare function ensureCollection(cmp: BinManager, name: string, length: number, append: boolean): Promise<CollectionMeta>;
@@ -0,0 +1,82 @@
1
+ import { _log } from "../log.js";
2
+ import { findCollection, getFreeSlot } from "./data.js";
3
+ import { saveHeaderAndPayload } from "./head.js";
4
+ import { INT_SIZE } from "./static.js";
5
+ import { readCollectionEof, readData, roundUpCapacity, writeData } from "./utils.js";
6
+ export async function ensureCollection(cmp, name, length, append) {
7
+ let lengthToStore;
8
+ const existingCollection = findCollection(cmp, name);
9
+ const isLastCollection = existingCollection && cmp.meta.collections[cmp.meta.collections.length - 1].offset === existingCollection.offset;
10
+ if (existingCollection) {
11
+ if (append) {
12
+ const collectionEOF = await readCollectionEof(cmp.fd, existingCollection.offset);
13
+ lengthToStore = roundUpCapacity(cmp.meta, INT_SIZE + collectionEOF + INT_SIZE + length);
14
+ await _log(6, "Append mode, calculated lengthToStore:", lengthToStore);
15
+ }
16
+ else {
17
+ lengthToStore = roundUpCapacity(cmp.meta, length + INT_SIZE);
18
+ }
19
+ await _log(6, "Existing collection found:", existingCollection.name, "capacity:", existingCollection.capacity, "needed:", lengthToStore);
20
+ if (existingCollection.capacity >= lengthToStore) {
21
+ await _log(5, "Existing collection has enough capacity, reusing");
22
+ return existingCollection;
23
+ }
24
+ await _log(5, "Existing collection too small, moving to free list and creating new");
25
+ cmp.meta.collections = cmp.meta.collections.filter(c => c.name !== name);
26
+ }
27
+ else {
28
+ lengthToStore = roundUpCapacity(cmp.meta, length + INT_SIZE);
29
+ await _log(6, "No existing collection found, calculated lengthToStore:", lengthToStore);
30
+ }
31
+ let newCollection;
32
+ // if exists and is last collection -> reuse offset
33
+ if (existingCollection && isLastCollection) {
34
+ newCollection = { name, offset: existingCollection.offset, capacity: lengthToStore };
35
+ cmp.meta.fileSize += lengthToStore - existingCollection.capacity;
36
+ }
37
+ else {
38
+ const slot = await getFreeSlot(cmp, lengthToStore);
39
+ if (slot) {
40
+ await _log(6, "Using free slot at offset:", slot.offset, "capacity:", slot.capacity);
41
+ newCollection = { name, offset: slot.offset, capacity: slot.capacity };
42
+ }
43
+ else {
44
+ await _log(6, "No free slot, appending at offset:", cmp.meta.fileSize);
45
+ newCollection = { name, offset: cmp.meta.fileSize, capacity: lengthToStore };
46
+ cmp.meta.fileSize += lengthToStore;
47
+ }
48
+ }
49
+ cmp.meta.collections.push(newCollection);
50
+ if (existingCollection) {
51
+ if (!isLastCollection) {
52
+ const chunkSize = 2048;
53
+ const tmpBuffer = Buffer.alloc(chunkSize);
54
+ const collectionLength = (await readData(cmp.fd, existingCollection.offset, INT_SIZE)).readUInt32LE(0);
55
+ const loopCount = Math.floor((collectionLength + INT_SIZE) / chunkSize);
56
+ await _log(3, "Copying", collectionLength, "bytes from old offset", existingCollection.offset, "to new offset", newCollection.offset);
57
+ for (let i = 0; i < loopCount; i++) {
58
+ const readDataOffset = existingCollection.offset + i * chunkSize;
59
+ const writeDataOffset = newCollection.offset + i * chunkSize;
60
+ await cmp.fd.read(tmpBuffer, 0, chunkSize, readDataOffset);
61
+ await cmp.fd.write(tmpBuffer, 0, chunkSize, writeDataOffset);
62
+ }
63
+ const lastChunkSize = (collectionLength + INT_SIZE) % chunkSize;
64
+ if (lastChunkSize) {
65
+ const readDataOffset = existingCollection.offset + loopCount * chunkSize;
66
+ const writeDataOffset = newCollection.offset + loopCount * chunkSize;
67
+ await cmp.fd.read(tmpBuffer, 0, lastChunkSize, readDataOffset);
68
+ await cmp.fd.write(tmpBuffer, 0, lastChunkSize, writeDataOffset);
69
+ }
70
+ cmp.meta.freeList.push(existingCollection);
71
+ }
72
+ }
73
+ else {
74
+ // Write 0 length for new collection
75
+ const collectionLengthBuffer = Buffer.alloc(INT_SIZE);
76
+ collectionLengthBuffer.writeUInt32LE(0, 0);
77
+ await writeData(cmp.fd, newCollection.offset, collectionLengthBuffer, INT_SIZE);
78
+ }
79
+ await saveHeaderAndPayload(cmp);
80
+ await _log(5, "Collection ensured:", newCollection);
81
+ return newCollection;
82
+ }
@@ -1,6 +1,4 @@
1
1
  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
- export declare function findFreeSlot(cmp: BinManager, size: number): Promise<FileMeta["freeList"][number] | undefined>;
5
- export declare function writeLogic(cmp: BinManager, collection: string, data: any): Promise<void>;
6
- export declare function readLogic(cmp: BinManager, collection: string): Promise<any>;
4
+ export declare function getFreeSlot(cmp: BinManager, size: number): Promise<FileMeta["freeList"][number] | undefined>;
package/dist/bin/data.js CHANGED
@@ -1,11 +1,8 @@
1
- import { getFileCrc } from "../crc32.js";
2
1
  import { _log } from "../log.js";
3
- import { saveHeaderAndPayload } from "./head.js";
4
- import { detectCollisions, pushToFreeList, readData, roundUpCapacity, writeData } from "./utils.js";
5
2
  export function findCollection(cmp, name) {
6
3
  return cmp.meta.collections.find(c => c.name === name);
7
4
  }
8
- export async function findFreeSlot(cmp, size) {
5
+ export async function getFreeSlot(cmp, size) {
9
6
  const { meta } = cmp;
10
7
  await _log(6, "Finding free slot for size:", size);
11
8
  const idx = meta.freeList.findIndex(f => f.capacity >= size);
@@ -19,72 +16,3 @@ export async function findFreeSlot(cmp, size) {
19
16
  await _log(6, "Slot removed from freeList:", slot);
20
17
  return slot;
21
18
  }
22
- export async function writeLogic(cmp, collection, data) {
23
- const { fd, meta } = cmp;
24
- await _log(3, "Writing data to collection:", collection);
25
- const existingCollection = findCollection(cmp, collection);
26
- const encoded = Buffer.from(await cmp.options.format.encode(data, collection));
27
- const length = encoded.length;
28
- const capacity = roundUpCapacity(meta, length + 4);
29
- let offset = existingCollection?.offset;
30
- let existingOffset = existingCollection?.offset;
31
- let existingCapacity = existingCollection?.capacity;
32
- const collision = detectCollisions(meta, offset, capacity, [collection]);
33
- if (collision || !existingCollection) {
34
- if (collision)
35
- await _log(2, "Collision detected");
36
- const slot = await findFreeSlot(cmp, capacity);
37
- if (slot) {
38
- offset = slot.offset;
39
- await _log(4, "Found free slot at offset:", offset);
40
- }
41
- else {
42
- offset = meta.fileSize;
43
- meta.fileSize += capacity;
44
- await _log(4, "No free slot found, appending at offset:", offset);
45
- }
46
- if (!existingCollection) {
47
- meta.collections.push({ name: collection, offset, capacity });
48
- }
49
- else if (collision) {
50
- pushToFreeList(meta, existingOffset, existingCapacity);
51
- meta.collections = meta.collections.map(c => {
52
- if (c.offset === existingOffset)
53
- return { name: c.name, offset, capacity };
54
- return c;
55
- });
56
- }
57
- await _log(3, "Collection written");
58
- await saveHeaderAndPayload(cmp);
59
- }
60
- const buf = Buffer.alloc(4);
61
- buf.writeUInt32LE(length, 0);
62
- await writeData(fd, offset, buf, 4);
63
- await writeData(fd, offset + 4, encoded, capacity - 4);
64
- if (existingCollection && length >= existingCollection.capacity) {
65
- meta.collections = meta.collections.map(c => {
66
- if (c.offset === offset)
67
- return { name: c.name, offset, capacity };
68
- return c;
69
- });
70
- await saveHeaderAndPayload(cmp);
71
- await _log(2, "Capacity exceeded");
72
- }
73
- else {
74
- const crc = cmp.options.crc;
75
- if (crc === 3 || crc === 4) {
76
- const { computedCrc } = await getFileCrc(fd);
77
- const crcBuf = Buffer.alloc(16);
78
- crcBuf.writeUInt32LE(computedCrc);
79
- await writeData(fd, 16, crcBuf, 16);
80
- }
81
- }
82
- }
83
- export async function readLogic(cmp, collection) {
84
- const collectionMeta = findCollection(cmp, collection);
85
- if (!collectionMeta)
86
- throw new Error("Collection not found");
87
- const len = await readData(cmp.fd, collectionMeta.offset, 4);
88
- const data = await readData(cmp.fd, collectionMeta.offset + 4, len.readUInt32LE(0));
89
- return await cmp.options.format.decode(data, collection);
90
- }
@@ -0,0 +1,5 @@
1
+ import { DataInternal } from "@wxn0brp/db-core/types/data";
2
+ import { BinManager } from "./index.js";
3
+ import { VQueryT } from "@wxn0brp/db-core/types/query";
4
+ export declare function findOne(cmp: BinManager, config: VQueryT.FindOne): Promise<DataInternal | null>;
5
+ export declare function find(cmp: BinManager, config: VQueryT.Find): Promise<DataInternal[]>;
@@ -0,0 +1,52 @@
1
+ import { _log } from "../log.js";
2
+ import { findCollection } from "./data.js";
3
+ import { INT_SIZE } from "./static.js";
4
+ import { readCollectionEof, readData } from "./utils.js";
5
+ import { findObj } from "@wxn0brp/db-core/utils/process";
6
+ export async function findOne(cmp, config) {
7
+ await _log(2, "Find one in collection:", config.collection);
8
+ const collection = findCollection(cmp, config.collection);
9
+ let cursor = collection.offset + INT_SIZE;
10
+ const collectionEOF = (await readData(cmp.fd, collection.offset, INT_SIZE)).readUInt32LE(0);
11
+ if (collectionEOF === 0)
12
+ return null;
13
+ const endOffset = collection.offset + INT_SIZE + collectionEOF;
14
+ while (cursor < endOffset) {
15
+ const dataLength = (await readData(cmp.fd, cursor, INT_SIZE)).readUInt32LE(0);
16
+ cursor += INT_SIZE;
17
+ const data = await readData(cmp.fd, cursor, dataLength);
18
+ cursor += dataLength;
19
+ // if removed
20
+ if (new Uint8Array(data).every(byte => byte === 0))
21
+ continue;
22
+ const obj = await cmp.options.format.decode(data, config.collection);
23
+ const res = findObj(config, obj);
24
+ if (res)
25
+ return res;
26
+ }
27
+ return null;
28
+ }
29
+ export async function find(cmp, config) {
30
+ await _log(2, "Find in collection:", config.collection);
31
+ const collection = findCollection(cmp, config.collection);
32
+ let cursor = collection.offset + INT_SIZE;
33
+ const collectionEOF = await readCollectionEof(cmp.fd, collection.offset);
34
+ if (collectionEOF === 0)
35
+ return [];
36
+ const res = [];
37
+ const endOffset = collection.offset + INT_SIZE + collectionEOF;
38
+ while (cursor < endOffset) {
39
+ const dataLength = (await readData(cmp.fd, cursor, INT_SIZE)).readUInt32LE(0);
40
+ cursor += INT_SIZE;
41
+ const data = await readData(cmp.fd, cursor, dataLength);
42
+ cursor += dataLength;
43
+ // if removed
44
+ if (new Uint8Array(data).every(byte => byte === 0))
45
+ continue;
46
+ const obj = await cmp.options.format.decode(data, config.collection);
47
+ const match = findObj(config, obj);
48
+ if (match)
49
+ res.push(match);
50
+ }
51
+ return res;
52
+ }
@@ -13,8 +13,5 @@ export interface FileMeta {
13
13
  }
14
14
  export declare function openFile(cmp: BinManager): Promise<FileMeta>;
15
15
  export declare function readHeaderPayload(cmp: BinManager): Promise<void>;
16
- export declare function getHeaderPayload(meta: FileMeta): {
17
- c: (string | number)[][];
18
- f: number[][];
19
- };
16
+ export declare function getHeaderPayload(meta: FileMeta): (string | number)[][][];
20
17
  export declare function saveHeaderAndPayload(cmp: BinManager, recursion?: boolean): Promise<void>;
package/dist/bin/head.js CHANGED
@@ -1,6 +1,5 @@
1
- import { getFileCrc } from "../crc32.js";
2
1
  import { _log } from "../log.js";
3
- import { findFreeSlot } from "./data.js";
2
+ import { getFreeSlot } from "./data.js";
4
3
  import { HEADER_SIZE, VERSION } from "./static.js";
5
4
  import { detectCollisions, pushToFreeList, roundUpCapacity, writeData } from "./utils.js";
6
5
  ;
@@ -42,19 +41,6 @@ export async function openFile(cmp) {
42
41
  const blockSize = headerBuf.readUInt32LE(12);
43
42
  meta.blockSize = blockSize;
44
43
  await _log(2, "Block size:", blockSize);
45
- if (options.crc) {
46
- const { computedCrc, storedCrc } = await getFileCrc(fd);
47
- const validCrc = computedCrc === storedCrc || storedCrc === 0;
48
- await _log(2, "CRC:", computedCrc, "Needed CRC:", storedCrc, "Valid:", validCrc);
49
- if (storedCrc === 0) {
50
- await _log(1, "Warning: CRC is zero, CRC will not be checked");
51
- }
52
- if (!validCrc) {
53
- await _log(0, "err", "Invalid CRC");
54
- if (options.crc === 2 || options.crc === 4)
55
- throw new Error("Invalid CRC");
56
- }
57
- }
58
44
  if (payloadOffset + payloadLength > fileSize - HEADER_SIZE) {
59
45
  await _log(6, "err", "Invalid payload length");
60
46
  throw new Error("Invalid payload length");
@@ -77,15 +63,15 @@ export async function readHeaderPayload(cmp) {
77
63
  throw new Error(`Incomplete payload header read: expected ${payloadLength} bytes, got ${bytesRead}`);
78
64
  }
79
65
  const obj = await cmp.options.format.decode(payloadBuf, "");
80
- meta.collections = (obj.c || []).map(([name, offset, capacity]) => ({ name, offset, capacity }));
81
- meta.freeList = (obj.f || []).map(([offset, capacity]) => ({ offset, capacity }));
66
+ meta.collections = obj[0].map(([name, offset, capacity]) => ({ name, offset, capacity }));
67
+ meta.freeList = obj[1].map(([offset, capacity]) => ({ offset, capacity }));
82
68
  await _log(6, "Collections and freeList loaded", meta);
83
69
  }
84
70
  export function getHeaderPayload(meta) {
85
- return {
86
- c: meta.collections.map(({ name, offset, capacity }) => ([name, offset, capacity])),
87
- f: meta.freeList.map(({ offset, capacity }) => [offset, capacity]),
88
- };
71
+ return [
72
+ meta.collections.map(({ name, offset, capacity }) => ([name, offset, capacity])),
73
+ meta.freeList.map(({ offset, capacity }) => [offset, capacity]),
74
+ ];
89
75
  }
90
76
  export async function saveHeaderAndPayload(cmp, recursion = false) {
91
77
  const { fd, meta, options } = cmp;
@@ -94,7 +80,7 @@ export async function saveHeaderAndPayload(cmp, recursion = false) {
94
80
  const { collections, freeList, fileSize } = meta;
95
81
  await _log(6, "Saving header payload:", collections, freeList);
96
82
  const payloadObj = getHeaderPayload(meta);
97
- const payloadBuf = Buffer.from(await cmp.options.format.encode(payloadObj, ""));
83
+ const payloadBuf = Buffer.from(await options.format.encode(payloadObj, ""));
98
84
  if (payloadBuf.length > 64 * 1024) {
99
85
  console.error("Header payload too large");
100
86
  throw new Error("Header payload too large");
@@ -106,10 +92,6 @@ export async function saveHeaderAndPayload(cmp, recursion = false) {
106
92
  headerBuf.writeUInt32LE(meta.payloadOffset, 8);
107
93
  headerBuf.writeUInt32LE(meta.blockSize, 12);
108
94
  meta.payloadLength = payloadBuf.length;
109
- if (options.crc) {
110
- const { computedCrc: crc } = await getFileCrc(fd);
111
- headerBuf.writeUInt32LE(crc, 16);
112
- }
113
95
  await _log(6, "Writing header:", headerBuf.toString("hex"));
114
96
  // Write header
115
97
  await fd.write(headerBuf, 0, HEADER_SIZE, 0);
@@ -117,7 +99,7 @@ export async function saveHeaderAndPayload(cmp, recursion = false) {
117
99
  const roundPayload = roundUpCapacity(meta, payloadBuf.length);
118
100
  if (detectCollisions(meta, HEADER_SIZE + meta.payloadOffset, roundPayload)) {
119
101
  await _log(2, "Collision detected");
120
- const slot = !recursion && await findFreeSlot(cmp, roundPayload);
102
+ const slot = !recursion && await getFreeSlot(cmp, roundPayload);
121
103
  if (slot) {
122
104
  meta.payloadOffset = slot.offset - HEADER_SIZE;
123
105
  }
@@ -1,3 +1,6 @@
1
+ import { ActionsBase } from "@wxn0brp/db-core/base/actions";
2
+ import { DataInternal } from "@wxn0brp/db-core/types/data";
3
+ import { VQueryT } from "@wxn0brp/db-core/types/query";
1
4
  import { FileHandle } from "fs/promises";
2
5
  import { FileMeta } from "./head.js";
3
6
  export interface CollectionMeta {
@@ -7,25 +10,18 @@ export interface CollectionMeta {
7
10
  }
8
11
  export interface Options {
9
12
  preferredSize: number;
10
- /**
11
- * 0 - crc off
12
- * 1 - warn if error
13
- * 2 - throw if error
14
- * 3 - 1 & save always on edit
15
- * 4 - 2 & save always on edit
16
- */
17
- crc: number;
18
13
  overwriteRemovedCollection: boolean;
19
14
  format: {
20
15
  encode(data: any, collection: string): Promise<Parameters<typeof Buffer.from>[0]>;
21
16
  decode(data: Buffer, collection: string): Promise<any>;
22
17
  };
23
18
  }
24
- export declare class BinManager {
19
+ export declare class BinManager extends ActionsBase {
25
20
  path: string;
26
21
  fd: null | FileHandle;
27
22
  meta: FileMeta;
28
23
  options: Options;
24
+ _inited: boolean;
29
25
  /**
30
26
  * Constructs a new BinManager instance.
31
27
  * @param path - File path.
@@ -34,11 +30,19 @@ export declare class BinManager {
34
30
  * not a positive number.
35
31
  */
36
32
  constructor(path: string, options?: Partial<Options>);
37
- open(): Promise<void>;
33
+ init(): Promise<void>;
38
34
  close(): Promise<void>;
39
35
  [Symbol.asyncDispose](): Promise<void>;
40
- write<T = object[]>(collection: string, data: T): Promise<void>;
41
- read(collection: string): Promise<any>;
36
+ getCollections(): Promise<string[]>;
37
+ issetCollection(collection: string): Promise<boolean>;
38
+ ensureCollection(collection: string): Promise<boolean>;
42
39
  optimize(): Promise<void>;
43
- removeCollection(collection: string): Promise<void>;
40
+ removeCollection(collection: string): Promise<boolean>;
41
+ add(config: VQueryT.Add): Promise<DataInternal>;
42
+ find(config: VQueryT.Find): Promise<DataInternal[]>;
43
+ findOne(config: VQueryT.FindOne): Promise<DataInternal | null>;
44
+ update(config: VQueryT.Update): Promise<DataInternal[]>;
45
+ updateOne(config: VQueryT.Update): Promise<DataInternal | null>;
46
+ remove(config: VQueryT.Remove): Promise<DataInternal[]>;
47
+ removeOne(config: VQueryT.Remove): Promise<DataInternal | null>;
44
48
  }
package/dist/bin/index.js CHANGED
@@ -1,11 +1,17 @@
1
1
  import * as msgpack from "@msgpack/msgpack";
2
+ import { ActionsBase } from "@wxn0brp/db-core/base/actions";
3
+ import { addId } from "@wxn0brp/db-core/helpers/addId";
4
+ import { findUtil } from "@wxn0brp/db-core/utils/action";
2
5
  import { access, constants, open } from "fs/promises";
3
- import { getFileCrc } from "../crc32.js";
4
6
  import { _log } from "../log.js";
5
- import { readLogic, writeLogic } from "./data.js";
7
+ import { add } from "./add.js";
8
+ import { ensureCollection } from "./collection.js";
9
+ import { find, findOne } from "./find.js";
6
10
  import { openFile } from "./head.js";
7
11
  import { optimize } from "./optimize.js";
8
- import { removeCollection } from "./rm.js";
12
+ import { removeCollection } from "./removeCollection.js";
13
+ import { remove } from "./remove.js";
14
+ import { update } from "./update.js";
9
15
  async function safeOpen(path) {
10
16
  try {
11
17
  await access(path, constants.F_OK);
@@ -16,11 +22,12 @@ async function safeOpen(path) {
16
22
  return await open(path, "w+");
17
23
  }
18
24
  }
19
- export class BinManager {
25
+ export class BinManager extends ActionsBase {
20
26
  path;
21
27
  fd = null;
22
28
  meta;
23
29
  options;
30
+ _inited = false;
24
31
  /**
25
32
  * Constructs a new BinManager instance.
26
33
  * @param path - File path.
@@ -29,12 +36,12 @@ export class BinManager {
29
36
  * not a positive number.
30
37
  */
31
38
  constructor(path, options) {
39
+ super();
32
40
  this.path = path;
33
41
  if (!path)
34
42
  throw new Error("Path not provided");
35
43
  this.options = {
36
44
  preferredSize: 512,
37
- crc: 2,
38
45
  overwriteRemovedCollection: false,
39
46
  format: {
40
47
  encode: async (data) => msgpack.encode(data),
@@ -43,23 +50,14 @@ export class BinManager {
43
50
  ...options
44
51
  };
45
52
  if (!this.options.preferredSize || this.options.preferredSize <= 0)
46
- throw new Error("Preferred size not provided");
53
+ throw new Error("Preferred size not provided correctly");
47
54
  }
48
- async open() {
55
+ async init() {
49
56
  this.fd = await safeOpen(this.path);
50
57
  await openFile(this);
51
58
  }
52
59
  async close() {
53
60
  if (this.fd) {
54
- const buff = Buffer.alloc(8);
55
- if (this.options.crc) {
56
- const { computedCrc: crc } = await getFileCrc(this.fd);
57
- buff.writeUInt32LE(crc, 0);
58
- }
59
- else {
60
- buff.fill(0, 0, 8);
61
- }
62
- await this.fd.write(buff, 0, 8, 16);
63
61
  await this.fd.close();
64
62
  this.fd = null;
65
63
  }
@@ -67,15 +65,19 @@ export class BinManager {
67
65
  [Symbol.asyncDispose]() {
68
66
  return this.close();
69
67
  }
70
- async write(collection, data) {
71
- if (!this.fd)
72
- throw new Error("File not open");
73
- await writeLogic(this, collection, data);
68
+ async getCollections() {
69
+ return this.meta.collections.map(c => c.name);
70
+ }
71
+ async issetCollection(collection) {
72
+ return this.meta.collections.map(c => c.name).includes(collection);
74
73
  }
75
- async read(collection) {
74
+ async ensureCollection(collection) {
76
75
  if (!this.fd)
77
76
  throw new Error("File not open");
78
- return await readLogic(this, collection);
77
+ if (this.meta.collections.find(c => c.name === collection))
78
+ return false;
79
+ await ensureCollection(this, collection, 0, false);
80
+ return true;
79
81
  }
80
82
  async optimize() {
81
83
  if (!this.fd)
@@ -86,5 +88,39 @@ export class BinManager {
86
88
  if (!this.fd)
87
89
  throw new Error("File not open");
88
90
  await removeCollection(this, collection);
91
+ return true;
92
+ }
93
+ async add(config) {
94
+ await this.ensureCollection(config.collection);
95
+ await addId(config, this, false);
96
+ await add(this, config);
97
+ return config.data;
98
+ }
99
+ async find(config) {
100
+ await this.ensureCollection(config.collection);
101
+ const data = await find(this, config);
102
+ return findUtil(config, data, [""]);
103
+ }
104
+ async findOne(config) {
105
+ await this.ensureCollection(config.collection);
106
+ return await findOne(this, config);
107
+ }
108
+ async update(config) {
109
+ await this.ensureCollection(config.collection);
110
+ return await update(this, config, false);
111
+ }
112
+ async updateOne(config) {
113
+ await this.ensureCollection(config.collection);
114
+ const data = await update(this, config, true);
115
+ return data[0] ?? null;
116
+ }
117
+ async remove(config) {
118
+ await this.ensureCollection(config.collection);
119
+ return await remove(this, config, false);
120
+ }
121
+ async removeOne(config) {
122
+ await this.ensureCollection(config.collection);
123
+ const data = await remove(this, config, true);
124
+ return data[0] ?? null;
89
125
  }
90
126
  }
@@ -1,6 +1,6 @@
1
1
  import { unlink } from "fs/promises";
2
2
  import { saveHeaderAndPayload } from "./head.js";
3
- import { HEADER_SIZE } from "./static.js";
3
+ import { HEADER_SIZE, INT_SIZE } from "./static.js";
4
4
  import { readData, roundUpCapacity, writeData } from "./utils.js";
5
5
  import { _log } from "../log.js";
6
6
  export async function optimize(cmp) {
@@ -19,15 +19,15 @@ export async function optimize(cmp) {
19
19
  await unlink(cmp.path);
20
20
  await new Promise(resolve => setTimeout(resolve, 100));
21
21
  await _log(5, "Re-opening database file for optimization");
22
- await cmp.open();
22
+ await cmp.init();
23
23
  let offset = roundUpCapacity(cmp.meta, cmp.meta.payloadLength + HEADER_SIZE) + cmp.meta.blockSize;
24
+ const lengthBuffer = Buffer.alloc(INT_SIZE);
24
25
  for (const [collection, data] of allData) {
25
26
  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);
27
+ const len = roundUpCapacity(cmp.meta, data.length + INT_SIZE);
28
+ lengthBuffer.writeInt32LE(data.length, 0);
29
+ await writeData(cmp.fd, offset, lengthBuffer, INT_SIZE);
30
+ await writeData(cmp.fd, offset + INT_SIZE, data, len);
31
31
  cmp.meta.collections.push({
32
32
  name: collection,
33
33
  offset,
@@ -0,0 +1,4 @@
1
+ import { DataInternal } from "@wxn0brp/db-core/types/data";
2
+ import { VQueryT } from "@wxn0brp/db-core/types/query";
3
+ import { BinManager } from "./index.js";
4
+ export declare function remove(cmp: BinManager, config: VQueryT.Remove, one: boolean): Promise<DataInternal[]>;
@@ -0,0 +1,33 @@
1
+ import { matchObj } from "@wxn0brp/db-core/utils/process";
2
+ import { _log } from "../log.js";
3
+ import { findCollection } from "./data.js";
4
+ import { INT_SIZE } from "./static.js";
5
+ import { readCollectionEof, readData, writeData } from "./utils.js";
6
+ export async function remove(cmp, config, one) {
7
+ await _log(2, "Removing from collection:", config.collection);
8
+ const collection = findCollection(cmp, config.collection);
9
+ let cursor = collection.offset + INT_SIZE;
10
+ const collectionEOF = await readCollectionEof(cmp.fd, collection.offset);
11
+ if (collectionEOF === 0)
12
+ return [];
13
+ const removed = [];
14
+ const endOffset = collection.offset + INT_SIZE + collectionEOF;
15
+ while (cursor < endOffset) {
16
+ const dataLength = (await readData(cmp.fd, cursor, INT_SIZE)).readUInt32LE(0);
17
+ cursor += INT_SIZE;
18
+ const dataOffset = cursor;
19
+ const data = await readData(cmp.fd, cursor, dataLength);
20
+ cursor += dataLength;
21
+ // if removed
22
+ if (new Uint8Array(data).every(byte => byte === 0))
23
+ continue;
24
+ const obj = await cmp.options.format.decode(data, config.collection);
25
+ if (!matchObj(config, obj))
26
+ continue;
27
+ await writeData(cmp.fd, dataOffset, Buffer.alloc(dataLength).fill(0), dataLength);
28
+ removed.push(obj);
29
+ if (one)
30
+ break;
31
+ }
32
+ return removed;
33
+ }
@@ -18,8 +18,7 @@ export async function removeCollection(cmp, collection) {
18
18
  offset: collectionMeta.offset,
19
19
  capacity: collectionMeta.capacity
20
20
  });
21
- if (options.overwriteRemovedCollection) {
21
+ if (options.overwriteRemovedCollection)
22
22
  await writeData(fd, collectionMeta.offset, Buffer.alloc(collectionMeta.capacity), collectionMeta.capacity);
23
- }
24
23
  await saveHeaderAndPayload(cmp);
25
24
  }
@@ -1,2 +1,3 @@
1
1
  export declare const HEADER_SIZE = 64;
2
- export declare const VERSION = 1;
2
+ export declare const VERSION = 2;
3
+ export declare const INT_SIZE = 4;
@@ -1,2 +1,3 @@
1
1
  export const HEADER_SIZE = 64;
2
- export const VERSION = 1;
2
+ export const VERSION = 2;
3
+ export const INT_SIZE = 4;
@@ -0,0 +1,4 @@
1
+ import { DataInternal } from "@wxn0brp/db-core/types/data";
2
+ import { VQueryT } from "@wxn0brp/db-core/types/query";
3
+ import { BinManager } from "./index.js";
4
+ export declare function update(cmp: BinManager, config: VQueryT.Update, one: boolean): Promise<DataInternal[]>;
@@ -0,0 +1,85 @@
1
+ import { matchObj, updateObj } from "@wxn0brp/db-core/utils/process";
2
+ import { _log } from "../log.js";
3
+ import { findCollection } from "./data.js";
4
+ import { saveHeaderAndPayload } from "./head.js";
5
+ import { INT_SIZE } from "./static.js";
6
+ import { readCollectionEof, readData, roundUpCapacity, writeData } from "./utils.js";
7
+ export async function update(cmp, config, one) {
8
+ await _log(2, "Updating in collection:", config.collection);
9
+ if (typeof config.updater === "object" && Object.keys(config.updater).length === 0)
10
+ return [];
11
+ const collection = findCollection(cmp, config.collection);
12
+ const collectionEOF = await readCollectionEof(cmp.fd, collection.offset);
13
+ if (collectionEOF === 0)
14
+ return [];
15
+ const updated = [];
16
+ let cursor = collection.offset + INT_SIZE;
17
+ const fileEnd = cmp.meta.fileSize;
18
+ let fileEndCursor = fileEnd + INT_SIZE;
19
+ let newLen = 0;
20
+ let isUpdated = false;
21
+ async function writeRecord(data, length, lengthBuff) {
22
+ await writeData(cmp.fd, fileEndCursor, lengthBuff, INT_SIZE);
23
+ fileEndCursor += INT_SIZE;
24
+ await writeData(cmp.fd, fileEndCursor, data, length);
25
+ fileEndCursor += length;
26
+ newLen += INT_SIZE + length;
27
+ }
28
+ const endOffset = collection.offset + INT_SIZE + collectionEOF;
29
+ const lengthBuff = Buffer.alloc(INT_SIZE);
30
+ while (cursor < endOffset) {
31
+ const dataLengthBuffer = await readData(cmp.fd, cursor, INT_SIZE);
32
+ const dataLength = dataLengthBuffer.readUInt32LE(0);
33
+ cursor += INT_SIZE;
34
+ const data = await readData(cmp.fd, cursor, dataLength);
35
+ cursor += dataLength;
36
+ // if removed
37
+ if (new Uint8Array(data).every(byte => byte === 0))
38
+ continue;
39
+ if (one && isUpdated) {
40
+ await writeRecord(data, dataLength, dataLengthBuffer);
41
+ continue;
42
+ }
43
+ const obj = await cmp.options.format.decode(data, config.collection);
44
+ const match = matchObj(config, obj);
45
+ if (!match) {
46
+ await writeRecord(data, dataLength, dataLengthBuffer);
47
+ continue;
48
+ }
49
+ const updatedObj = updateObj(config, obj);
50
+ const encoded = Buffer.from(await cmp.options.format.encode(updatedObj, config.collection));
51
+ const encodedLen = encoded.length;
52
+ lengthBuff.writeUInt32LE(encodedLen, 0);
53
+ await writeRecord(encoded, encodedLen, lengthBuff);
54
+ isUpdated = true;
55
+ updated.push(updatedObj);
56
+ }
57
+ if (newLen <= collectionEOF) {
58
+ const chunkSize = 2048;
59
+ const tmpBuffer = Buffer.alloc(chunkSize);
60
+ const loopCount = Math.floor((newLen + INT_SIZE) / chunkSize);
61
+ for (let i = 0; i < loopCount; i++) {
62
+ const readDataOffset = cmp.meta.fileSize + i * chunkSize;
63
+ const writeDataOffset = collection.offset + i * chunkSize;
64
+ await cmp.fd.read(tmpBuffer, 0, chunkSize, readDataOffset);
65
+ await cmp.fd.write(tmpBuffer, 0, chunkSize, writeDataOffset);
66
+ }
67
+ const lastChunkSize = (newLen + INT_SIZE) % chunkSize;
68
+ if (lastChunkSize) {
69
+ const readDataOffset = cmp.meta.fileSize + loopCount * chunkSize;
70
+ const writeDataOffset = collection.offset + loopCount * chunkSize;
71
+ await cmp.fd.read(tmpBuffer, 0, lastChunkSize, readDataOffset);
72
+ await cmp.fd.write(tmpBuffer, 0, lastChunkSize, writeDataOffset);
73
+ }
74
+ }
75
+ else {
76
+ cmp.meta.freeList.push({ capacity: collection.capacity, offset: collection.offset });
77
+ collection.offset = cmp.meta.fileSize;
78
+ collection.capacity = roundUpCapacity(cmp.meta, newLen);
79
+ cmp.meta.fileSize += collection.capacity;
80
+ await saveHeaderAndPayload(cmp);
81
+ lengthBuff.writeUInt32LE(newLen, 0);
82
+ await writeData(cmp.fd, collection.offset, lengthBuff, INT_SIZE);
83
+ }
84
+ return updated;
85
+ }
@@ -6,3 +6,4 @@ export declare function readData(fd: FileHandle, offset: number, length: number)
6
6
  export declare function optimizeFreeList(blocks: Block[]): Block[];
7
7
  export declare function detectCollisions(result: FileMeta, start: number, size: number, skip?: string[]): boolean;
8
8
  export declare function pushToFreeList(result: FileMeta, offset: number, len: number): void;
9
+ export declare function readCollectionEof(fd: FileHandle, offset: number): Promise<number>;
package/dist/bin/utils.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { _log } from "../log.js";
2
+ import { INT_SIZE } from "./static.js";
2
3
  export function roundUpCapacity(result, size) {
3
4
  return Math.ceil(size / result.blockSize) * result.blockSize;
4
5
  }
@@ -70,3 +71,8 @@ export function pushToFreeList(result, offset, len) {
70
71
  });
71
72
  result.freeList = optimizeFreeList(result.freeList);
72
73
  }
74
+ export async function readCollectionEof(fd, offset) {
75
+ const buf = Buffer.alloc(INT_SIZE);
76
+ await fd.read(buf, 0, INT_SIZE, offset);
77
+ return buf.readUInt32LE(0);
78
+ }
package/dist/index.d.ts CHANGED
@@ -1,13 +1,10 @@
1
1
  import { ValtheraClass } from "@wxn0brp/db-core";
2
- import { BinFileAction } from "./actions.js";
3
2
  import { BinManager, Options } from "./bin/index.js";
4
- export * from "./actions.js";
5
3
  export * from "./bin/index.js";
6
4
  export declare function createBinValthera(path: string, opts?: Partial<Options>, init?: boolean): Promise<{
7
5
  db: ValtheraClass;
8
- actions: BinFileAction;
9
6
  mgr: BinManager;
10
7
  }>;
11
8
  export declare const DYNAMIC: {
12
- bin(path: string, opts?: Partial<Options>): Promise<BinFileAction>;
9
+ bin(path: string, opts?: Partial<Options>): Promise<BinManager>;
13
10
  };
package/dist/index.js CHANGED
@@ -1,25 +1,20 @@
1
1
  import { ValtheraClass } from "@wxn0brp/db-core";
2
- import { BinFileAction } from "./actions.js";
3
2
  import { BinManager } from "./bin/index.js";
4
- export * from "./actions.js";
5
3
  export * from "./bin/index.js";
6
4
  export async function createBinValthera(path, opts = {}, init = true) {
7
5
  const mgr = new BinManager(path, opts);
8
- const actions = new BinFileAction(mgr);
9
- const db = new ValtheraClass({ dbAction: actions });
6
+ const db = new ValtheraClass({ dbAction: mgr });
10
7
  if (init)
11
- await actions.init();
8
+ await mgr.init();
12
9
  return {
13
10
  db,
14
- actions,
15
11
  mgr,
16
12
  };
17
13
  }
18
14
  export const DYNAMIC = {
19
15
  async bin(path, opts = {}) {
20
16
  const mgr = new BinManager(path, opts);
21
- const actions = new BinFileAction(mgr);
22
- await actions.init();
23
- return actions;
17
+ await mgr.init();
18
+ return mgr;
24
19
  }
25
20
  };
package/dist/log.js CHANGED
@@ -3,7 +3,7 @@ export async function _log(level, ...data) {
3
3
  const logLevel = parseInt(process.env.VDB_BIN_LOG_LEVEL || '0', 10);
4
4
  if (logLevel < level)
5
5
  return;
6
- let line = new Error().stack.split('\n')[3].trim();
6
+ let line = new Error().stack.split('\n')[2].trim();
7
7
  let path = line.slice(line.indexOf("(")).replace(dir, "").replace("(", "").replace(")", "");
8
8
  const at = line.slice(3, line.indexOf("(") - 1);
9
9
  if (path.length < 2)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wxn0brp/db-storage-bin",
3
- "version": "0.100.2",
3
+ "version": "0.110.0-alpha.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "author": "wxn0brP",
@@ -19,7 +19,7 @@
19
19
  ],
20
20
  "devDependencies": {
21
21
  "@types/bun": "*",
22
- "@wxn0brp/db-core": "^0.10.3",
22
+ "@wxn0brp/db-core": "^0.11.0-alpha.4",
23
23
  "tsc-alias": "^1",
24
24
  "typescript": "^6"
25
25
  },
@@ -27,7 +27,7 @@
27
27
  "@msgpack/msgpack": "^3.1.3"
28
28
  },
29
29
  "peerDependencies": {
30
- "@wxn0brp/db-core": "^0.10.3"
30
+ "@wxn0brp/db-core": "^0.11.0-alpha.4"
31
31
  },
32
32
  "files": [
33
33
  "dist"
package/dist/actions.d.ts DELETED
@@ -1,32 +0,0 @@
1
- import { CustomActionsBase } from "@wxn0brp/db-core/base/custom";
2
- import { DbOpts } from "@wxn0brp/db-core/types/options";
3
- import { BinManager } from "./bin/index.js";
4
- export declare class BinFileAction extends CustomActionsBase {
5
- private mgr;
6
- folder: string;
7
- options: DbOpts;
8
- /**
9
- * Creates a new instance of dbActionC.
10
- * @constructor
11
- * @param folder - The folder where database files are stored.
12
- * @param options - The options object.
13
- */
14
- constructor(mgr: BinManager);
15
- init(): Promise<void>;
16
- /**
17
- * Get a list of available databases in the specified folder.
18
- */
19
- getCollections(): Promise<string[]>;
20
- /**
21
- * Check and create the specified collection if it doesn't exist.
22
- */
23
- ensureCollection(collection: string): Promise<boolean>;
24
- /**
25
- * Check if a collection exists.
26
- */
27
- issetCollection(collection: string): Promise<boolean>;
28
- /**
29
- * Removes a database collection from the file system.
30
- */
31
- removeCollection(collection: string): Promise<boolean>;
32
- }
package/dist/actions.js DELETED
@@ -1,51 +0,0 @@
1
- import { CustomFileCpu } from "@wxn0brp/db-core";
2
- import { CustomActionsBase } from "@wxn0brp/db-core/base/custom";
3
- export class BinFileAction extends CustomActionsBase {
4
- mgr;
5
- folder;
6
- options;
7
- /**
8
- * Creates a new instance of dbActionC.
9
- * @constructor
10
- * @param folder - The folder where database files are stored.
11
- * @param options - The options object.
12
- */
13
- constructor(mgr) {
14
- super();
15
- this.mgr = mgr;
16
- this.fileCpu = new CustomFileCpu(this.mgr.read.bind(this.mgr), this.mgr.write.bind(this.mgr));
17
- }
18
- async init() {
19
- await this.mgr.open();
20
- }
21
- /**
22
- * Get a list of available databases in the specified folder.
23
- */
24
- async getCollections() {
25
- const collections = this.mgr.meta.collections.map(c => c.name);
26
- return collections;
27
- }
28
- /**
29
- * Check and create the specified collection if it doesn't exist.
30
- */
31
- async ensureCollection(collection) {
32
- if (await this.issetCollection(collection))
33
- return false;
34
- await this.mgr.write(collection, []);
35
- return true;
36
- }
37
- /**
38
- * Check if a collection exists.
39
- */
40
- async issetCollection(collection) {
41
- const collections = await this.getCollections();
42
- return collections.includes(collection);
43
- }
44
- /**
45
- * Removes a database collection from the file system.
46
- */
47
- async removeCollection(collection) {
48
- await this.mgr.removeCollection(collection);
49
- return true;
50
- }
51
- }
package/dist/crc32.d.ts DELETED
@@ -1,6 +0,0 @@
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 DELETED
@@ -1,36 +0,0 @@
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
- }
File without changes