Skip to content

Commit

Permalink
bump version
Browse files Browse the repository at this point in the history
  • Loading branch information
USERSATOSHI committed Jun 10, 2024
1 parent dfb7ca2 commit e105548
Show file tree
Hide file tree
Showing 7 changed files with 530 additions and 3 deletions.
127 changes: 127 additions & 0 deletions lib/KeyValue/newsrc/Data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { Optional } from "../../typings/type.js";
import { KeyValueTypeList } from "../typings/type.js";

import {
KeyValueDataInterface,
KeyValueJSONOption,
} from "../typings/interface.js";
import { types } from "util";
export default class Data {
file: string;
key: string;
value: any;
type: KeyValueTypeList;
deleted: boolean = false;

/**
* @description create data
* @param data data to create
*
* @memberof Data
*
* @example
* ```js
* const data = new Data({
* file:"file",
* key:"key",
* value:"value",
* type:"string"
* })
* ```
*/

constructor(data: Optional<KeyValueDataInterface, "type">) {
this.file = data.file;
this.key = data.key;
this.type = data.type ?? this.#getType(data.value);
this.value = this.#parseValue(data);
}
/**
* @private
* @description get type of value
* @param value value to get type
* @returns
*/
#getType(value: any): KeyValueTypeList {
return value instanceof Date ? "date" : typeof value;
}
/**
* @private
* @description parse value to correct type
* @param data data to parse
* @returns
*/
#parseValue(data: Optional<KeyValueDataInterface, "type">) {
return data.type === "date" &&
(typeof data.value === "string" ||
typeof data.value === "number" ||
types.isDate(data.value))
? // @ts-ignore
new Date(data.value)
: data.type === "bigint" &&
(typeof data.value === "string" || typeof data.value === "number")
? BigInt(data.value)
: typeof data.value === "number" &&
data.value > Number.MAX_SAFE_INTEGER
? BigInt(data.value)
: data.type === "boolean"
? Boolean(data.value)
: data.type === "object"
? (typeof data.value === "string" ? JSON.parse(data.value) : data.value)
: data.value;
}
/**
* @description convert data to json
* @returns
* @memberof Data
* @example
* ```js
* <KeyValueData>.toJSON()
* ```
*/
toJSON(): KeyValueJSONOption {
return {
value: types.isDate(this.value)
? this.value.toISOString()
: typeof this.value === "bigint"
? this.value.toString()
: this.value,
type: this.type,
key: this.key,
};
}

get size() {
return Buffer.byteLength(JSON.stringify(this.toJSON()));
}
/**
* @description create empty data
* @static
* @returns
*/
static emptyData() {
return new Data({
file: "",
key: "",
value: "",
type: "undefined",
});
}

static deletedData(key: string,file:string) {
const data = Data.emptyData();
data.key = key;
data.deleted = true;
data.file = file;
return data;
}

static fromJSON(data: KeyValueJSONOption,file:string) {
return new Data({
file,
key: data.key,
value: data.value,
type: data.type,
});
}
}
252 changes: 252 additions & 0 deletions lib/KeyValue/newsrc/File.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
/**
** json file
** max keys 10k
**
*/

import { constants, FileHandle, open, rename } from "node:fs/promises";
import { IJSONOptions, KeyValueJSONOption } from "./typings/interface.js";
import Data from "./Data.js";
import Mutex from "./Mutex.js";
import { PriorityQueue } from "@akarui/structures";

export default class JSONFile {
#options: IJSONOptions;
#data: Record<string, KeyValueJSONOption> | null = null;
#tmpData: Record<string, Data> = {};
#fileHandle: FileHandle | null = null;
#mutex: Mutex;
#maxTries = 10;
constructor(options: IJSONOptions) {
this.#options = options;
this.#mutex = new Mutex();
}

async #load() {
const data = await this.#fileHandle?.readFile();
if (!data) return;
this.#data = JSON.parse(data.toString()) as Record<
string,
KeyValueJSONOption
>;
}

async #readAllDataFromFile() {
await this.#mutex.lock();
return JSON.parse(
((await this.#fileHandle?.readFile()) ?? "{}").toString()
);
}

async #atomicWrite() {
const tmpPath = `${this.#options.filePath}.tmp`;
const tmpHandle = await open(
tmpPath,
constants.O_RDWR | constants.O_CREAT | constants.O_TRUNC
);
await tmpHandle.writeFile(JSON.stringify(this.#data));
await tmpHandle.close();
await this.#fileHandle?.close();
try {
await rename(tmpPath, this.#options.filePath);
} catch (error) {
if (this.#maxTries === 0) {
this.#maxTries = 10;
throw error;
}
await this.#atomicWrite();
this.#maxTries--;
}

this.#fileHandle = await open(
this.#options.filePath,
constants.O_RDWR | constants.O_CREAT
);
}

async #atomicFlush(data: Data[]) {
const tmpPath = `${this.#options.filePath}.tmp`;
const tmpHandle = await open(
tmpPath,
constants.O_RDWR | constants.O_CREAT | constants.O_TRUNC
);
const allFileData = await this.#readAllDataFromFile()
for (const item of data) {
if (item.deleted) {
delete allFileData[item.key];
continue;
}
allFileData[item.key] = item.toJSON();
}
await tmpHandle.writeFile(JSON.stringify(allFileData));
await tmpHandle.close();
await this.#fileHandle?.close();
try {
await rename(tmpPath, this.#options.filePath);
} catch (error) {
if (this.#maxTries === 0) {
this.#maxTries = 10;
throw error;
}
await this.#atomicFlush(data);
this.#maxTries--;
}

this.#fileHandle = await open(
this.#options.filePath,
constants.O_RDWR | constants.O_CREAT
);
this.#data = this.#tmpData;
this.#tmpData = {};
}

async open() {
this.#fileHandle = await open(
this.#options.filePath,
constants.O_RDWR | constants.O_CREAT
);

if (this.#options.loadInMemory) {
await this.#load();
}
}

async set(data: Data[]) {
await this.#mutex.lock();
if (this.#data !== null) {
for (const item of data) {
if (item.deleted) {
delete this.#data[item.key];
continue;
}
this.#data[item.key] = item.toJSON();
}

await this.#atomicWrite();
} else {
for (const item of data) {
this.#tmpData[item.key] = item;
}
await this.#atomicFlush(data);
}

this.#mutex.unlock();
}

async get(key: string) {
await this.#mutex.lock();

if (this.#data) {
const data = this.#data[key];
this.#mutex.unlock();
return data
? Data.fromJSON(data, this.#options.filePath)
: Data.emptyData();
}

if (this.#tmpData[key]) {
this.#mutex.unlock();
if (this.#tmpData[key].deleted) {
return null;
}
return Data.fromJSON(this.#tmpData[key], this.#options.filePath);
}

const allData = await this.#readAllDataFromFile();
this.#mutex.unlock();
const data = allData[key];
return data ? Data.fromJSON(data, this.#options.filePath) : null;
}

async findMany(query: (data: KeyValueJSONOption) => boolean) {
await this.#mutex.lock();
const list: Data[] = [];
if (this.#data) {
for (const key in this.#data) {
if (query(this.#data[key])) {
list.push(
Data.fromJSON(this.#data[key], this.#options.filePath)
);
}
}

this.#mutex.unlock();
return list;
} else {
for (const key in this.#tmpData) {
if (query(this.#tmpData[key]) && !this.#tmpData[key].deleted) {
list.push(this.#tmpData[key]);
}
}

const allData = await this.#readAllDataFromFile();
this.#mutex.unlock();

for (const key in allData) {
if (query(allData[key])) {
list.push(
Data.fromJSON(allData[key], this.#options.filePath)
);
}
}

return list;
}
}

async findOne(query: (data: KeyValueJSONOption) => boolean) {
await this.#mutex.lock();
if (this.#data) {
for (const key in this.#data) {
if (query(this.#data[key])) {
this.#mutex.unlock();
return Data.fromJSON(
this.#data[key],
this.#options.filePath
);
}
}

this.#mutex.unlock();
return null;
} else {
for (const key in this.#tmpData) {
if (query(this.#tmpData[key]) && !this.#tmpData[key].deleted) {
this.#mutex.unlock();
return this.#tmpData[key];
}
}

const allData = await this.#readAllDataFromFile();
this.#mutex.unlock();

for (const key in allData) {
if (query(allData[key])) {
return Data.fromJSON(allData[key], this.#options.filePath);
}
}

return null;
}
}

async all(
query: (data: KeyValueJSONOption) => boolean,
order: "asc" | "desc" | "firstN" = "asc",
start = 0,
length = 10
): Promise<Data[]> {
const list = await this.findMany(query);
if (order === "asc") {
return list
.sort((a, b) => a.value - b.value)
.slice(start, start + length);
} else if (order === "desc") {
return list
.sort((a, b) => b.value - a.value)
.slice(start, start + length);
} else {
return list.slice(start, start + length);
}
}
}
Loading

0 comments on commit e105548

Please sign in to comment.