@wxn0brp/db-storage-dir 0.0.1
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/LICENSE +21 -0
- package/README.md +9 -0
- package/dist/action.d.ts +69 -0
- package/dist/action.js +234 -0
- package/dist/file/find.d.ts +11 -0
- package/dist/file/find.js +74 -0
- package/dist/file/index.d.ts +3 -0
- package/dist/file/index.js +16 -0
- package/dist/file/remove.d.ts +7 -0
- package/dist/file/remove.js +42 -0
- package/dist/file/update.d.ts +7 -0
- package/dist/file/update.js +51 -0
- package/dist/file/utils.d.ts +4 -0
- package/dist/file/utils.js +78 -0
- package/dist/format.d.ts +12 -0
- package/dist/format.js +23 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/version.js +1 -0
- package/package.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 wxn0brP
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
package/dist/action.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import dbActionBase from "@wxn0brp/db-core/base/actions";
|
|
2
|
+
import Data from "@wxn0brp/db-core/types/data";
|
|
3
|
+
import FileCpu from "@wxn0brp/db-core/types/fileCpu";
|
|
4
|
+
import { DbOpts } from "@wxn0brp/db-core/types/options";
|
|
5
|
+
import { VQuery } from "@wxn0brp/db-core/types/query";
|
|
6
|
+
/**
|
|
7
|
+
* A class representing database actions on files.
|
|
8
|
+
* @class
|
|
9
|
+
*/
|
|
10
|
+
declare class dbActionC extends dbActionBase {
|
|
11
|
+
folder: string;
|
|
12
|
+
options: DbOpts;
|
|
13
|
+
fileCpu: FileCpu;
|
|
14
|
+
/**
|
|
15
|
+
* Creates a new instance of dbActionC.
|
|
16
|
+
* @constructor
|
|
17
|
+
* @param folder - The folder where database files are stored.
|
|
18
|
+
* @param options - The options object.
|
|
19
|
+
*/
|
|
20
|
+
constructor(folder: string, options: DbOpts, fileCpu: FileCpu);
|
|
21
|
+
_getCollectionPath(collection: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Get a list of available databases in the specified folder.
|
|
24
|
+
*/
|
|
25
|
+
getCollections(): Promise<string[]>;
|
|
26
|
+
/**
|
|
27
|
+
* Check and create the specified collection if it doesn't exist.
|
|
28
|
+
*/
|
|
29
|
+
checkCollection({ collection }: VQuery): Promise<boolean>;
|
|
30
|
+
/**
|
|
31
|
+
* Check if a collection exists.
|
|
32
|
+
*/
|
|
33
|
+
issetCollection({ collection }: VQuery): Promise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Add a new entry to the specified database.
|
|
36
|
+
*/
|
|
37
|
+
add({ collection, data, id_gen }: VQuery): Promise<import("@wxn0brp/db-core/types/arg").Arg>;
|
|
38
|
+
/**
|
|
39
|
+
* Find entries in the specified database based on search criteria.
|
|
40
|
+
*/
|
|
41
|
+
find({ collection, search, context, dbFindOpts, findOpts }: VQuery): Promise<Data[]>;
|
|
42
|
+
/**
|
|
43
|
+
* Find the first matching entry in the specified database based on search criteria.
|
|
44
|
+
*/
|
|
45
|
+
findOne({ collection, search, context, findOpts }: VQuery): Promise<Data>;
|
|
46
|
+
/**
|
|
47
|
+
* Update entries in the specified database based on search criteria and an updater function or object.
|
|
48
|
+
*/
|
|
49
|
+
update({ collection, search, updater, context }: VQuery): Promise<boolean>;
|
|
50
|
+
/**
|
|
51
|
+
* Update the first matching entry in the specified database based on search criteria and an updater function or object.
|
|
52
|
+
*/
|
|
53
|
+
updateOne({ collection, search, updater, context }: VQuery): Promise<boolean>;
|
|
54
|
+
/**
|
|
55
|
+
* Remove entries from the specified database based on search criteria.
|
|
56
|
+
*/
|
|
57
|
+
remove({ collection, search, context }: VQuery): Promise<boolean>;
|
|
58
|
+
/**
|
|
59
|
+
* Remove the first matching entry from the specified database based on search criteria.
|
|
60
|
+
*/
|
|
61
|
+
removeOne({ collection, search, context }: VQuery): Promise<boolean>;
|
|
62
|
+
/**
|
|
63
|
+
* Removes a database collection from the file system.
|
|
64
|
+
*/
|
|
65
|
+
removeCollection({ collection }: {
|
|
66
|
+
collection: any;
|
|
67
|
+
}): Promise<boolean>;
|
|
68
|
+
}
|
|
69
|
+
export default dbActionC;
|
package/dist/action.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import dbActionBase from "@wxn0brp/db-core/base/actions";
|
|
2
|
+
import gen from "@wxn0brp/db-core/helpers/gen";
|
|
3
|
+
import { compareSafe } from "@wxn0brp/db-core/utils/sort";
|
|
4
|
+
import { existsSync, mkdirSync, promises, statSync } from "fs";
|
|
5
|
+
import { resolve, sep } from "path";
|
|
6
|
+
/**
|
|
7
|
+
* A class representing database actions on files.
|
|
8
|
+
* @class
|
|
9
|
+
*/
|
|
10
|
+
class dbActionC extends dbActionBase {
|
|
11
|
+
folder;
|
|
12
|
+
options;
|
|
13
|
+
fileCpu;
|
|
14
|
+
/**
|
|
15
|
+
* Creates a new instance of dbActionC.
|
|
16
|
+
* @constructor
|
|
17
|
+
* @param folder - The folder where database files are stored.
|
|
18
|
+
* @param options - The options object.
|
|
19
|
+
*/
|
|
20
|
+
constructor(folder, options, fileCpu) {
|
|
21
|
+
super();
|
|
22
|
+
this.folder = folder;
|
|
23
|
+
this.options = {
|
|
24
|
+
maxFileSize: 2 * 1024 * 1024, //2 MB
|
|
25
|
+
...options,
|
|
26
|
+
};
|
|
27
|
+
this.fileCpu = fileCpu;
|
|
28
|
+
if (!existsSync(folder))
|
|
29
|
+
mkdirSync(folder, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
_getCollectionPath(collection) {
|
|
32
|
+
return this.folder + "/" + collection + "/";
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get a list of available databases in the specified folder.
|
|
36
|
+
*/
|
|
37
|
+
async getCollections() {
|
|
38
|
+
const allCollections = await promises.readdir(this.folder, { recursive: true, withFileTypes: true });
|
|
39
|
+
const collections = allCollections
|
|
40
|
+
.filter(dirent => dirent.isDirectory())
|
|
41
|
+
.map(dirent => {
|
|
42
|
+
const parentPath = resolve(dirent.parentPath);
|
|
43
|
+
const baseFolder = resolve(this.folder);
|
|
44
|
+
if (parentPath === baseFolder)
|
|
45
|
+
return dirent.name;
|
|
46
|
+
return parentPath.replace(baseFolder + sep, "") + "/" + dirent.name;
|
|
47
|
+
});
|
|
48
|
+
return collections;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Check and create the specified collection if it doesn't exist.
|
|
52
|
+
*/
|
|
53
|
+
async checkCollection({ collection }) {
|
|
54
|
+
if (await this.issetCollection(collection))
|
|
55
|
+
return;
|
|
56
|
+
const cpath = this._getCollectionPath(collection);
|
|
57
|
+
await promises.mkdir(cpath, { recursive: true });
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Check if a collection exists.
|
|
62
|
+
*/
|
|
63
|
+
async issetCollection({ collection }) {
|
|
64
|
+
const path = this._getCollectionPath(collection);
|
|
65
|
+
try {
|
|
66
|
+
await promises.access(path);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Add a new entry to the specified database.
|
|
75
|
+
*/
|
|
76
|
+
async add({ collection, data, id_gen = true }) {
|
|
77
|
+
await this.checkCollection(arguments[0]);
|
|
78
|
+
const cpath = this._getCollectionPath(collection);
|
|
79
|
+
const file = cpath + await getLastFile(cpath, this.options.maxFileSize);
|
|
80
|
+
if (id_gen)
|
|
81
|
+
data._id = data._id || gen();
|
|
82
|
+
await this.fileCpu.add(file, data);
|
|
83
|
+
return data;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Find entries in the specified database based on search criteria.
|
|
87
|
+
*/
|
|
88
|
+
async find({ collection, search, context = {}, dbFindOpts = {}, findOpts = {} }) {
|
|
89
|
+
const { reverse = false, max = -1, offset = 0, sortBy, sortAsc = true } = dbFindOpts;
|
|
90
|
+
await this.checkCollection(arguments[0]);
|
|
91
|
+
const cpath = this._getCollectionPath(collection);
|
|
92
|
+
let files = await getSortedFiles(cpath);
|
|
93
|
+
if (reverse && !sortBy)
|
|
94
|
+
files.reverse();
|
|
95
|
+
let datas = [];
|
|
96
|
+
let totalEntries = 0;
|
|
97
|
+
let skippedEntries = 0;
|
|
98
|
+
for (const f of files) {
|
|
99
|
+
let entries = await this.fileCpu.find(cpath + f, search, context, findOpts);
|
|
100
|
+
if (reverse && !sortBy)
|
|
101
|
+
entries.reverse();
|
|
102
|
+
if (!sortBy) {
|
|
103
|
+
if (offset > skippedEntries) {
|
|
104
|
+
const remainingSkip = offset - skippedEntries;
|
|
105
|
+
if (entries.length <= remainingSkip) {
|
|
106
|
+
skippedEntries += entries.length;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
entries = entries.slice(remainingSkip);
|
|
110
|
+
skippedEntries = offset;
|
|
111
|
+
}
|
|
112
|
+
if (max !== -1) {
|
|
113
|
+
if (totalEntries + entries.length > max) {
|
|
114
|
+
const remaining = max - totalEntries;
|
|
115
|
+
entries = entries.slice(0, remaining);
|
|
116
|
+
totalEntries = max;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
totalEntries += entries.length;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
datas.push(...entries);
|
|
123
|
+
if (max !== -1 && totalEntries >= max)
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
datas.push(...entries);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (sortBy) {
|
|
131
|
+
const dir = sortAsc ? 1 : -1;
|
|
132
|
+
datas.sort((a, b) => compareSafe(a[sortBy], b[sortBy]) * dir);
|
|
133
|
+
const start = offset;
|
|
134
|
+
const end = max !== -1 ? offset + max : undefined;
|
|
135
|
+
datas = datas.slice(start, end);
|
|
136
|
+
}
|
|
137
|
+
return datas;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Find the first matching entry in the specified database based on search criteria.
|
|
141
|
+
*/
|
|
142
|
+
async findOne({ collection, search, context = {}, findOpts = {} }) {
|
|
143
|
+
await this.checkCollection(arguments[0]);
|
|
144
|
+
const cpath = this._getCollectionPath(collection);
|
|
145
|
+
const files = await getSortedFiles(cpath);
|
|
146
|
+
for (let f of files) {
|
|
147
|
+
let data = await this.fileCpu.findOne(cpath + f, search, context, findOpts);
|
|
148
|
+
if (data)
|
|
149
|
+
return data;
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Update entries in the specified database based on search criteria and an updater function or object.
|
|
155
|
+
*/
|
|
156
|
+
async update({ collection, search, updater, context = {} }) {
|
|
157
|
+
await this.checkCollection(arguments[0]);
|
|
158
|
+
return await operationUpdater(this._getCollectionPath(collection), this.fileCpu.update.bind(this.fileCpu), false, search, updater, context);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Update the first matching entry in the specified database based on search criteria and an updater function or object.
|
|
162
|
+
*/
|
|
163
|
+
async updateOne({ collection, search, updater, context = {} }) {
|
|
164
|
+
await this.checkCollection(arguments[0]);
|
|
165
|
+
return await operationUpdater(this._getCollectionPath(collection), this.fileCpu.update.bind(this.fileCpu), true, search, updater, context);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Remove entries from the specified database based on search criteria.
|
|
169
|
+
*/
|
|
170
|
+
async remove({ collection, search, context = {} }) {
|
|
171
|
+
await this.checkCollection(arguments[0]);
|
|
172
|
+
return await operationUpdater(this._getCollectionPath(collection), this.fileCpu.remove.bind(this.fileCpu), false, search, context);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Remove the first matching entry from the specified database based on search criteria.
|
|
176
|
+
*/
|
|
177
|
+
async removeOne({ collection, search, context = {} }) {
|
|
178
|
+
await this.checkCollection(arguments[0]);
|
|
179
|
+
return await operationUpdater(this._getCollectionPath(collection), this.fileCpu.remove.bind(this.fileCpu), true, search, context);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Removes a database collection from the file system.
|
|
183
|
+
*/
|
|
184
|
+
async removeCollection({ collection }) {
|
|
185
|
+
await promises.rm(this.folder + "/" + collection, { recursive: true, force: true });
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Get the last file in the specified directory.
|
|
191
|
+
*/
|
|
192
|
+
async function getLastFile(path, maxFileSize = 1024 * 1024) {
|
|
193
|
+
if (!existsSync(path))
|
|
194
|
+
mkdirSync(path, { recursive: true });
|
|
195
|
+
const files = await getSortedFiles(path);
|
|
196
|
+
if (files.length == 0) {
|
|
197
|
+
await promises.writeFile(path + "/1.db", "");
|
|
198
|
+
return "1.db";
|
|
199
|
+
}
|
|
200
|
+
const last = files[files.length - 1];
|
|
201
|
+
const info = path + "/" + last;
|
|
202
|
+
if (statSync(info).size < maxFileSize)
|
|
203
|
+
return last;
|
|
204
|
+
const num = parseInt(last.replace(".db", ""), 10) + 1;
|
|
205
|
+
await promises.writeFile(path + "/" + num + ".db", "");
|
|
206
|
+
return num + ".db";
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Get all files in a directory sorted by name.
|
|
210
|
+
*/
|
|
211
|
+
async function getSortedFiles(folder) {
|
|
212
|
+
const files = await promises.readdir(folder, { withFileTypes: true });
|
|
213
|
+
return files
|
|
214
|
+
.filter(file => file.isFile() && !file.name.endsWith(".tmp"))
|
|
215
|
+
.map(file => file.name)
|
|
216
|
+
.filter(name => /^\d+\.db$/.test(name))
|
|
217
|
+
.sort((a, b) => {
|
|
218
|
+
const numA = parseInt(a, 10);
|
|
219
|
+
const numB = parseInt(b, 10);
|
|
220
|
+
return numA - numB;
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
async function operationUpdater(cpath, worker, one, ...args) {
|
|
224
|
+
const files = await getSortedFiles(cpath);
|
|
225
|
+
let update = false;
|
|
226
|
+
for (const file of files) {
|
|
227
|
+
const updated = await worker(cpath + file, one, ...args);
|
|
228
|
+
update = update || updated;
|
|
229
|
+
if (one && updated)
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
return update;
|
|
233
|
+
}
|
|
234
|
+
export default dbActionC;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Search } from "@wxn0brp/db-core/types/arg";
|
|
2
|
+
import { FindOpts } from "@wxn0brp/db-core/types/options";
|
|
3
|
+
import { VContext } from "@wxn0brp/db-core/types/types";
|
|
4
|
+
/**
|
|
5
|
+
* Asynchronously finds entries in a file based on search criteria.
|
|
6
|
+
*/
|
|
7
|
+
export declare function find(file: string, arg: Search, context?: VContext, findOpts?: FindOpts): Promise<any[] | false>;
|
|
8
|
+
/**
|
|
9
|
+
* Asynchronously finds one entry in a file based on search criteria.
|
|
10
|
+
*/
|
|
11
|
+
export declare function findOne(file: string, arg: Search, context?: VContext, findOpts?: FindOpts): Promise<any | false>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import hasFieldsAdvanced from "@wxn0brp/db-core/utils/hasFieldsAdvanced";
|
|
2
|
+
import updateFindObject from "@wxn0brp/db-core/utils/updateFindObject";
|
|
3
|
+
import { existsSync, promises } from "fs";
|
|
4
|
+
import { createRL } from "./utils.js";
|
|
5
|
+
import { parseData } from "../format.js";
|
|
6
|
+
import { pathRepair } from "@wxn0brp/db-core/customFileCpu";
|
|
7
|
+
/**
|
|
8
|
+
* Processes a line of text from a file and checks if it matches the search criteria.
|
|
9
|
+
*/
|
|
10
|
+
async function findProcesLine(arg, line, context = {}, findOpts = {}) {
|
|
11
|
+
const ob = parseData(line);
|
|
12
|
+
let res = false;
|
|
13
|
+
if (typeof arg === "function") {
|
|
14
|
+
if (arg(ob, context))
|
|
15
|
+
res = true;
|
|
16
|
+
}
|
|
17
|
+
else if (typeof arg === "object" && !Array.isArray(arg)) {
|
|
18
|
+
if (hasFieldsAdvanced(ob, arg))
|
|
19
|
+
res = true;
|
|
20
|
+
}
|
|
21
|
+
if (res)
|
|
22
|
+
return updateFindObject(ob, findOpts);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Asynchronously finds entries in a file based on search criteria.
|
|
27
|
+
*/
|
|
28
|
+
export async function find(file, arg, context = {}, findOpts = {}) {
|
|
29
|
+
file = pathRepair(file);
|
|
30
|
+
return await new Promise(async (resolve) => {
|
|
31
|
+
if (!existsSync(file)) {
|
|
32
|
+
await promises.writeFile(file, "");
|
|
33
|
+
resolve(false);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const rl = createRL(file);
|
|
37
|
+
const resF = [];
|
|
38
|
+
for await (const line of rl) {
|
|
39
|
+
if (line == "" || !line)
|
|
40
|
+
continue;
|
|
41
|
+
const res = await findProcesLine(arg, line, context, findOpts);
|
|
42
|
+
if (res)
|
|
43
|
+
resF.push(res);
|
|
44
|
+
}
|
|
45
|
+
;
|
|
46
|
+
resolve(resF);
|
|
47
|
+
rl.close();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Asynchronously finds one entry in a file based on search criteria.
|
|
52
|
+
*/
|
|
53
|
+
export async function findOne(file, arg, context = {}, findOpts = {}) {
|
|
54
|
+
file = pathRepair(file);
|
|
55
|
+
return await new Promise(async (resolve) => {
|
|
56
|
+
if (!existsSync(file)) {
|
|
57
|
+
await promises.writeFile(file, "");
|
|
58
|
+
resolve(false);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const rl = createRL(file);
|
|
62
|
+
for await (const line of rl) {
|
|
63
|
+
if (line == "" || !line)
|
|
64
|
+
continue;
|
|
65
|
+
const res = await findProcesLine(arg, line, context, findOpts);
|
|
66
|
+
if (res) {
|
|
67
|
+
resolve(res);
|
|
68
|
+
rl.close();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
;
|
|
72
|
+
resolve(false);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import update from "./update.js";
|
|
2
|
+
import remove from "./remove.js";
|
|
3
|
+
import { find, findOne } from "./find.js";
|
|
4
|
+
import { appendFileSync } from "fs";
|
|
5
|
+
import { stringifyData } from "../format.js";
|
|
6
|
+
const vFileCpu = {
|
|
7
|
+
add: async (file, data) => {
|
|
8
|
+
const dataString = stringifyData(data);
|
|
9
|
+
appendFileSync(file, dataString + "\n");
|
|
10
|
+
},
|
|
11
|
+
find,
|
|
12
|
+
findOne,
|
|
13
|
+
update,
|
|
14
|
+
remove,
|
|
15
|
+
};
|
|
16
|
+
export default vFileCpu;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Search } from "@wxn0brp/db-core/types/arg";
|
|
2
|
+
import { VContext } from "@wxn0brp/db-core/types/types";
|
|
3
|
+
/**
|
|
4
|
+
* Removes entries from a file based on search criteria.
|
|
5
|
+
*/
|
|
6
|
+
declare function removeWorker(file: string, one: boolean, search: Search, context?: VContext): Promise<boolean>;
|
|
7
|
+
export default removeWorker;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { existsSync, promises, appendFileSync } from "fs";
|
|
2
|
+
import { createRL } from "./utils.js";
|
|
3
|
+
import hasFieldsAdvanced from "@wxn0brp/db-core/utils/hasFieldsAdvanced";
|
|
4
|
+
import { parseData } from "../format.js";
|
|
5
|
+
import { pathRepair } from "@wxn0brp/db-core/customFileCpu";
|
|
6
|
+
/**
|
|
7
|
+
* Removes entries from a file based on search criteria.
|
|
8
|
+
*/
|
|
9
|
+
async function removeWorker(file, one, search, context = {}) {
|
|
10
|
+
file = pathRepair(file);
|
|
11
|
+
if (!existsSync(file)) {
|
|
12
|
+
await promises.writeFile(file, "");
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
await promises.copyFile(file, file + ".tmp");
|
|
16
|
+
await promises.writeFile(file, "");
|
|
17
|
+
const rl = createRL(file + ".tmp");
|
|
18
|
+
let removed = false;
|
|
19
|
+
for await (let line of rl) {
|
|
20
|
+
if (one && removed) {
|
|
21
|
+
appendFileSync(file, line + "\n");
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const data = parseData(line);
|
|
25
|
+
if (typeof search === "function") {
|
|
26
|
+
if (search(data, context)) {
|
|
27
|
+
removed = true;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else if (typeof search === "object" && !Array.isArray(search)) {
|
|
32
|
+
if (hasFieldsAdvanced(data, search)) {
|
|
33
|
+
removed = true;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
appendFileSync(file, line + "\n");
|
|
38
|
+
}
|
|
39
|
+
await promises.writeFile(file + ".tmp", "");
|
|
40
|
+
return removed;
|
|
41
|
+
}
|
|
42
|
+
export default removeWorker;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Search, Updater } from "@wxn0brp/db-core/types/arg";
|
|
2
|
+
import { VContext } from "@wxn0brp/db-core/types/types";
|
|
3
|
+
/**
|
|
4
|
+
* Updates a file based on search criteria and an updater function or object.
|
|
5
|
+
*/
|
|
6
|
+
declare function updateWorker(file: string, one: boolean, search: Search, updater: Updater, context?: VContext): Promise<boolean>;
|
|
7
|
+
export default updateWorker;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { existsSync, promises } from "fs";
|
|
2
|
+
import { createRL } from "./utils.js";
|
|
3
|
+
import hasFieldsAdvanced from "@wxn0brp/db-core/utils/hasFieldsAdvanced";
|
|
4
|
+
import updateObjectAdvanced from "@wxn0brp/db-core/utils/updateObject";
|
|
5
|
+
import { parseData, stringifyData } from "../format.js";
|
|
6
|
+
import { pathRepair } from "@wxn0brp/db-core/customFileCpu";
|
|
7
|
+
/**
|
|
8
|
+
* Updates a file based on search criteria and an updater function or object.
|
|
9
|
+
*/
|
|
10
|
+
async function updateWorker(file, one, search, updater, context = {}) {
|
|
11
|
+
file = pathRepair(file);
|
|
12
|
+
if (!existsSync(file)) {
|
|
13
|
+
await promises.writeFile(file, "");
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
await promises.copyFile(file, file + ".tmp");
|
|
17
|
+
await promises.writeFile(file, "");
|
|
18
|
+
const rl = createRL(file + ".tmp");
|
|
19
|
+
let updated = false;
|
|
20
|
+
for await (let line of rl) {
|
|
21
|
+
if (one && updated) {
|
|
22
|
+
await promises.appendFile(file, line + "\n");
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const data = parseData(line);
|
|
26
|
+
let ob = false;
|
|
27
|
+
if (typeof search === "function") {
|
|
28
|
+
ob = search(data, context) || false;
|
|
29
|
+
}
|
|
30
|
+
else if (typeof search === "object" && !Array.isArray(search)) {
|
|
31
|
+
ob = hasFieldsAdvanced(data, search);
|
|
32
|
+
}
|
|
33
|
+
if (ob) {
|
|
34
|
+
let updateObj = data;
|
|
35
|
+
if (typeof updater === "function") {
|
|
36
|
+
const updateObjValue = updater(data, context);
|
|
37
|
+
if (updateObjValue)
|
|
38
|
+
updateObj = updateObjValue;
|
|
39
|
+
}
|
|
40
|
+
else if (typeof updater === "object" && !Array.isArray(updater)) {
|
|
41
|
+
updateObj = updateObjectAdvanced(data, updater);
|
|
42
|
+
}
|
|
43
|
+
line = await stringifyData(updateObj);
|
|
44
|
+
updated = true;
|
|
45
|
+
}
|
|
46
|
+
await promises.appendFile(file, line + "\n");
|
|
47
|
+
}
|
|
48
|
+
await promises.writeFile(file + ".tmp", "");
|
|
49
|
+
return updated;
|
|
50
|
+
}
|
|
51
|
+
export default updateWorker;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createReadStream } from "fs";
|
|
2
|
+
export function createRL(file) {
|
|
3
|
+
const stream = createReadStream(file, { highWaterMark: 64 * 1024 });
|
|
4
|
+
let buffer = "";
|
|
5
|
+
let done = false;
|
|
6
|
+
let error = null;
|
|
7
|
+
const lines = [];
|
|
8
|
+
let waiting = null;
|
|
9
|
+
stream.on("data", (chunk) => {
|
|
10
|
+
buffer += chunk.toString("utf8");
|
|
11
|
+
let index;
|
|
12
|
+
while ((index = buffer.search(/\r?\n/)) >= 0) {
|
|
13
|
+
const line = buffer.slice(0, index);
|
|
14
|
+
lines.push(line);
|
|
15
|
+
buffer = buffer.slice(index + (buffer[index] === "\r" && buffer[index + 1] === "\n" ? 2 : 1));
|
|
16
|
+
}
|
|
17
|
+
feed();
|
|
18
|
+
});
|
|
19
|
+
stream.on("end", () => {
|
|
20
|
+
if (buffer.length > 0) {
|
|
21
|
+
lines.push(buffer);
|
|
22
|
+
buffer = "";
|
|
23
|
+
}
|
|
24
|
+
done = true;
|
|
25
|
+
feed();
|
|
26
|
+
});
|
|
27
|
+
stream.on("error", (err) => {
|
|
28
|
+
error = err;
|
|
29
|
+
done = true;
|
|
30
|
+
feed();
|
|
31
|
+
});
|
|
32
|
+
const feed = () => {
|
|
33
|
+
if (waiting) {
|
|
34
|
+
if (error) {
|
|
35
|
+
waiting(Promise.reject(error));
|
|
36
|
+
}
|
|
37
|
+
else if (lines.length > 0) {
|
|
38
|
+
waiting({ value: lines.shift(), done: false });
|
|
39
|
+
}
|
|
40
|
+
else if (done) {
|
|
41
|
+
waiting({ value: undefined, done: true });
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
waiting = null;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const iterator = {
|
|
50
|
+
next() {
|
|
51
|
+
if (error)
|
|
52
|
+
return Promise.reject(error);
|
|
53
|
+
if (lines.length > 0)
|
|
54
|
+
return Promise.resolve({ value: lines.shift(), done: false });
|
|
55
|
+
if (done)
|
|
56
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
57
|
+
return new Promise(res => {
|
|
58
|
+
waiting = res;
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
return() {
|
|
62
|
+
rl.close();
|
|
63
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const rl = {
|
|
67
|
+
[Symbol.asyncIterator]() {
|
|
68
|
+
return iterator;
|
|
69
|
+
},
|
|
70
|
+
close() {
|
|
71
|
+
if (!done) {
|
|
72
|
+
done = true;
|
|
73
|
+
stream.destroy();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
return rl;
|
|
78
|
+
}
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses given string into a JSON object. If the string does not start with
|
|
3
|
+
* a {, it is wrapped in one. This allows for a shorthand when
|
|
4
|
+
* storing/reading data from a file.
|
|
5
|
+
*/
|
|
6
|
+
export declare function parseData(data: string): any;
|
|
7
|
+
/**
|
|
8
|
+
* Converts given object to a string. If the string is a valid json5, it is
|
|
9
|
+
* returned as is. If it is a valid json5 wrapped in {}, the curly brackets
|
|
10
|
+
* are removed. Otherwise the string is wrapped in {}.
|
|
11
|
+
*/
|
|
12
|
+
export declare function stringifyData(data: any): any;
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import json5 from "json5";
|
|
2
|
+
/**
|
|
3
|
+
* Parses given string into a JSON object. If the string does not start with
|
|
4
|
+
* a {, it is wrapped in one. This allows for a shorthand when
|
|
5
|
+
* storing/reading data from a file.
|
|
6
|
+
*/
|
|
7
|
+
export function parseData(data) {
|
|
8
|
+
if (!data.startsWith("{"))
|
|
9
|
+
data = "{" + data + "}";
|
|
10
|
+
return json5.parse(data);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Converts given object to a string. If the string is a valid json5, it is
|
|
14
|
+
* returned as is. If it is a valid json5 wrapped in {}, the curly brackets
|
|
15
|
+
* are removed. Otherwise the string is wrapped in {}.
|
|
16
|
+
*/
|
|
17
|
+
export function stringifyData(data) {
|
|
18
|
+
data = json5.stringify(data);
|
|
19
|
+
if (data.startsWith("{")) {
|
|
20
|
+
data = data.slice(1, -1);
|
|
21
|
+
}
|
|
22
|
+
return data;
|
|
23
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./file/index.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./file/index.js";
|
package/dist/version.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const version = "0.0.1";
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wxn0brp/db-storage-dir",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"author": "wxn0brP",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"description": "ValtheraDB Dir Storage",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/wxn0brP/ValtheraDB-storage-dir.git"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/wxn0brP/ValtheraDB",
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"json5": "^2.2.3",
|
|
17
|
+
"@wxn0brp/db-core": ">=0.0.1 <0.1.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^22.12.0",
|
|
21
|
+
"tsc-alias": "^1.8.10",
|
|
22
|
+
"typescript": "^5.7.3"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js",
|
|
31
|
+
"default": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./*": {
|
|
34
|
+
"types": "./dist/*.d.ts",
|
|
35
|
+
"import": "./dist/*.js",
|
|
36
|
+
"default": "./dist/*.js"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|