@incoqnito.io/bajadab 1.1.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.
- package/LICENSE +1 -0
- package/README.md +101 -0
- package/dist/bajadab.d.ts +3 -0
- package/dist/bajadab.js +4 -0
- package/dist/bajadab.js.map +1 -0
- package/dist/base.d.ts +110 -0
- package/dist/base.js +54 -0
- package/dist/base.js.map +1 -0
- package/dist/basics.d.ts +50 -0
- package/dist/basics.js +434 -0
- package/dist/basics.js.map +1 -0
- package/dist/fileio.d.ts +24 -0
- package/dist/fileio.js +125 -0
- package/dist/fileio.js.map +1 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +7 -0
- package/dist/utils.js.map +1 -0
- package/package.json +52 -0
- package/src/main/bajadab.ts +3 -0
- package/src/main/base.ts +125 -0
- package/src/main/basics.ts +482 -0
- package/src/main/fileio.ts +154 -0
- package/src/main/utils.ts +7 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { IBCollectionConfig, IBDatabase, IBDatabaseConfig, IBFunctionRegistry, } from "./base.js";
|
|
2
|
+
import { BCollection, BDatabase, DEFAULT_DATABASE_CONFIG, } from "./basics.js";
|
|
3
|
+
import { uuidv7 } from "uuidv7";
|
|
4
|
+
import { access, constants, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
|
|
7
|
+
interface IStoredDatabase {
|
|
8
|
+
config: IBDatabaseConfig;
|
|
9
|
+
nameIDMap: { [k: string]: string; };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface IStoredCollection<T> {
|
|
13
|
+
collectionID: string;
|
|
14
|
+
collectionName: string;
|
|
15
|
+
collectionConfig: IBCollectionConfig;
|
|
16
|
+
path: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const DB_META_FILE_NAME = "dbmeta.json";
|
|
20
|
+
const DB_COLLECTION_FOLDER_SUFFIX = "_data";
|
|
21
|
+
const DB_COLLECTION_META_FILE_SUFFIX = ".json";
|
|
22
|
+
const DB_COLLECTION_DATA_FILE = "data.json";
|
|
23
|
+
const RESTRICTED_FILE_MODE = 0o600;
|
|
24
|
+
const RESTRICTED_DIR_MODE = 0o700;
|
|
25
|
+
|
|
26
|
+
async function atomicWriteFile(targetPath: string, data: string): Promise<void> {
|
|
27
|
+
const tempPath = `${targetPath}.${uuidv7()}.tmp`;
|
|
28
|
+
await writeFile(tempPath, data, { encoding: "utf-8", mode: RESTRICTED_FILE_MODE });
|
|
29
|
+
await rename(tempPath, targetPath);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class IODatabase {
|
|
33
|
+
readonly basePath: string;
|
|
34
|
+
private readonly nameIDMap: Map<string, string>;
|
|
35
|
+
private databaseConfig: IBDatabaseConfig;
|
|
36
|
+
|
|
37
|
+
public constructor(basePath: string, databaseConfig: IBDatabaseConfig, nameIDMap?: Map<string, string>) {
|
|
38
|
+
this.basePath = basePath;
|
|
39
|
+
this.databaseConfig = databaseConfig;
|
|
40
|
+
this.nameIDMap = nameIDMap ?? new Map();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private async loadCollectionMeta(id: string, database: BDatabase): Promise<BCollection> {
|
|
44
|
+
const meta: IStoredCollection<any> = JSON.parse(await readFile(join(this.basePath, id + DB_COLLECTION_META_FILE_SUFFIX), { encoding: "utf-8" }));
|
|
45
|
+
return new BCollection(database, id, meta.collectionName, meta.collectionConfig);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public static async createOrLoad(basePath: string, functionRegistry?: IBFunctionRegistry): Promise<BDatabase> {
|
|
49
|
+
const metaPath = join(basePath, DB_META_FILE_NAME);
|
|
50
|
+
let data: string;
|
|
51
|
+
try {
|
|
52
|
+
await access(metaPath, constants.F_OK);
|
|
53
|
+
data = await readFile(metaPath, { encoding: "utf-8" })
|
|
54
|
+
}
|
|
55
|
+
catch (err: any) {
|
|
56
|
+
if (err.code === 'ENOENT') {
|
|
57
|
+
const iodb = new IODatabase(basePath, DEFAULT_DATABASE_CONFIG);
|
|
58
|
+
return new BDatabase(iodb, DEFAULT_DATABASE_CONFIG, functionRegistry);
|
|
59
|
+
}
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
const meta: IStoredDatabase = JSON.parse(data);
|
|
63
|
+
const nameIDMap = new Map(Object.entries(meta.nameIDMap));
|
|
64
|
+
const iodb = new IODatabase(basePath, meta.config, nameIDMap);
|
|
65
|
+
const database = new BDatabase(iodb, meta.config, functionRegistry);
|
|
66
|
+
const collections = await Promise.all(
|
|
67
|
+
nameIDMap.values()
|
|
68
|
+
.map(id => iodb.loadCollectionMeta(id, database))
|
|
69
|
+
.toArray());
|
|
70
|
+
collections.forEach(c => database.addCollection(c));
|
|
71
|
+
return database;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public async flush(): Promise<void> {
|
|
75
|
+
const meta: IStoredDatabase = {
|
|
76
|
+
config: this.databaseConfig,
|
|
77
|
+
nameIDMap: Object.fromEntries(this.nameIDMap),
|
|
78
|
+
};
|
|
79
|
+
const data = JSON.stringify(meta);
|
|
80
|
+
await atomicWriteFile(join(this.basePath, DB_META_FILE_NAME), data);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public async createCollection<T = any>(collection: BCollection<T>): Promise<BCollection<T>> {
|
|
84
|
+
const id = collection.collectionID;
|
|
85
|
+
const collectionPath = join(this.basePath, id + DB_COLLECTION_FOLDER_SUFFIX);
|
|
86
|
+
await mkdir(collectionPath, { recursive: true, mode: RESTRICTED_DIR_MODE });
|
|
87
|
+
const meta: IStoredCollection<T> = {
|
|
88
|
+
collectionConfig: collection.collectionConfig,
|
|
89
|
+
collectionName: collection.collectionName,
|
|
90
|
+
collectionID: id,
|
|
91
|
+
path: collectionPath,
|
|
92
|
+
};
|
|
93
|
+
const data = JSON.stringify(meta);
|
|
94
|
+
await atomicWriteFile(join(this.basePath, id + DB_COLLECTION_META_FILE_SUFFIX), data);
|
|
95
|
+
this.nameIDMap.set(collection.collectionName, id);
|
|
96
|
+
await this.flush();
|
|
97
|
+
return collection;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
public async deleteCollection(collectionName: string): Promise<void> {
|
|
101
|
+
const id = this.nameIDMap.get(collectionName);
|
|
102
|
+
if (!id) {
|
|
103
|
+
throw new Error(`no such database ${collectionName}`);
|
|
104
|
+
}
|
|
105
|
+
const collectionPath = join(this.basePath, id + DB_COLLECTION_FOLDER_SUFFIX);
|
|
106
|
+
await rm(collectionPath, { recursive: true, force: true });
|
|
107
|
+
const metaPath = join(this.basePath, id + DB_COLLECTION_META_FILE_SUFFIX);
|
|
108
|
+
await rm(metaPath, { force: true });
|
|
109
|
+
this.nameIDMap.delete(collectionName);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private async loadData(collectionID: string): Promise<any> {
|
|
113
|
+
try {
|
|
114
|
+
const data = await readFile(join(this.basePath, collectionID + DB_COLLECTION_FOLDER_SUFFIX, DB_COLLECTION_DATA_FILE), { encoding: "utf-8" });
|
|
115
|
+
return JSON.parse(data);
|
|
116
|
+
}
|
|
117
|
+
catch (err: any) {
|
|
118
|
+
if (err.code === 'ENOENT') {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
throw err;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public async loadArrayData<T>(collectionID: string): Promise<T[]> {
|
|
126
|
+
const data = await this.loadData(collectionID);
|
|
127
|
+
return data || [];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public async loadMapData<T>(collectionID: string): Promise<Map<string, T>> {
|
|
131
|
+
const data = await this.loadData(collectionID);
|
|
132
|
+
return data ? new Map(Object.entries(data)) : new Map();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private async writeData(collectionID: string, data: any): Promise<void> {
|
|
136
|
+
return atomicWriteFile(join(this.basePath, collectionID + DB_COLLECTION_FOLDER_SUFFIX, DB_COLLECTION_DATA_FILE), JSON.stringify(data));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
public async writeArrayData<T = any>(collectionID: string, arrayData: T[]): Promise<void> {
|
|
140
|
+
return this.writeData(collectionID, arrayData);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
public async writeMapData<T = any>(collectionID: string, mapData: Map<string, T>): Promise<void> {
|
|
144
|
+
return this.writeData(collectionID, Object.fromEntries(mapData));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Opens the database at `basePath`, creating it if it doesn't exist yet.
|
|
150
|
+
* `functionRegistry` resolves any {@link IBCollectionConfig.getID}/`setID` keys used by its collections.
|
|
151
|
+
*/
|
|
152
|
+
export async function getDatabase(basePath: string, functionRegistry?: IBFunctionRegistry): Promise<IBDatabase> {
|
|
153
|
+
return IODatabase.createOrLoad(basePath, functionRegistry);
|
|
154
|
+
}
|