yolodb 1.0.0 โ 1.2.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/README.md +6 -4
- package/dist/index.d.mts +48 -0
- package/dist/index.mjs +120 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +19 -26
- package/dist/index.d.ts +0 -44
- package/dist/index.js +0 -131
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -30,11 +30,13 @@ Install the package:
|
|
|
30
30
|
npm install -D yolodb
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
Example:
|
|
34
|
+
|
|
33
35
|
```typescript
|
|
34
36
|
import { yolodb } from 'yolodb'
|
|
35
37
|
|
|
36
38
|
// Create a table with a primary key
|
|
37
|
-
const usersTable = yolodb<User>('users.json', 'id', [])
|
|
39
|
+
const usersTable = yolodb<User>('full/path/to/users.json', 'id', [])
|
|
38
40
|
|
|
39
41
|
// Insert a record
|
|
40
42
|
usersTable.insert({
|
|
@@ -55,13 +57,11 @@ const activeUsers = usersTable.search((user) => user.status === 'active')
|
|
|
55
57
|
### Use Cases
|
|
56
58
|
|
|
57
59
|
- ๐งช **Testing and Development**
|
|
58
|
-
|
|
59
60
|
- Mock your production database (best way is via abstractions such as repository classes)
|
|
60
61
|
- Quick prototyping without database setup
|
|
61
62
|
- Isolated test environments
|
|
62
63
|
|
|
63
64
|
- ๐ฎ **Small Applications**
|
|
64
|
-
|
|
65
65
|
- Simple data persistence needs
|
|
66
66
|
- Prototypes and MVPs
|
|
67
67
|
- Local development tools
|
|
@@ -73,6 +73,7 @@ const activeUsers = usersTable.search((user) => user.status === 'active')
|
|
|
73
73
|
|
|
74
74
|
### Advantages
|
|
75
75
|
|
|
76
|
+
- **Quick data access**: Compared to SQL-based engines, you can easily check the data by opening the JSON files.
|
|
76
77
|
- **Simple but Powerful**: Basic CRUD operations with a simple and familiar API
|
|
77
78
|
- **No Configuration**: Works out of the box
|
|
78
79
|
- **Type Safety**: Full TypeScript support
|
|
@@ -86,6 +87,8 @@ const activeUsers = usersTable.search((user) => user.status === 'active')
|
|
|
86
87
|
- **Performance costs**: Reads table files on every operation, and writes to disk on every write operation
|
|
87
88
|
- **Concurrency**: Basic file-based locking
|
|
88
89
|
- **Scale**: Not suitable for large datasets, since data is loaded into memory
|
|
90
|
+
- **Sorting, Joins and more complex queries**: Not implemented yet.
|
|
91
|
+
- **Data migrations**: Not implemented, and not planned.
|
|
89
92
|
|
|
90
93
|
## API Reference
|
|
91
94
|
|
|
@@ -141,7 +144,6 @@ class DrizzleUserRepository implements UserRepository { ... }
|
|
|
141
144
|
```
|
|
142
145
|
|
|
143
146
|
2. **Implement Repository Pattern**
|
|
144
|
-
|
|
145
147
|
- Encapsulate database logic
|
|
146
148
|
- Add domain-specific methods
|
|
147
149
|
- Maintain clean architecture
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//#region src/yolodb.d.ts
|
|
2
|
+
declare const logger: Console;
|
|
3
|
+
type YoloDbDebugLogger = typeof logger.debug;
|
|
4
|
+
declare class YoloDbError extends Error {
|
|
5
|
+
constructor(message: string);
|
|
6
|
+
}
|
|
7
|
+
type YoloDbTableOptions = {
|
|
8
|
+
superjsonEnabled?: boolean;
|
|
9
|
+
debugLogger?: YoloDbDebugLogger;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Simple file-based JSON database built on top of SuperJSON.
|
|
13
|
+
*/
|
|
14
|
+
declare class YoloDbTable<R extends Record<string, any>> {
|
|
15
|
+
private readonly tableName;
|
|
16
|
+
private readonly pkField;
|
|
17
|
+
private readonly filePath;
|
|
18
|
+
private readonly options;
|
|
19
|
+
constructor(absoluteFilePath: string, pkField: keyof R, initialData?: R[], options?: YoloDbTableOptions);
|
|
20
|
+
private get log();
|
|
21
|
+
private getData;
|
|
22
|
+
all(): R[];
|
|
23
|
+
readFile(): R[];
|
|
24
|
+
saveFile(db: R[]): void;
|
|
25
|
+
findById(id: string): R | undefined;
|
|
26
|
+
findBy(field: keyof R, value: any): R[];
|
|
27
|
+
findFirstBy(field: keyof R, value: any): R | undefined;
|
|
28
|
+
search(filterFn: (record: R) => boolean): R[];
|
|
29
|
+
insert(record: R): void;
|
|
30
|
+
insertMany(records: R[]): void;
|
|
31
|
+
update(record: Partial<R>): void;
|
|
32
|
+
updateMany(records: Array<Partial<R>>): void;
|
|
33
|
+
delete(id: string): void;
|
|
34
|
+
deleteMany(ids: string[]): void;
|
|
35
|
+
truncate(): void;
|
|
36
|
+
}
|
|
37
|
+
declare class YoloDbRepository<R extends Record<string, any>> {
|
|
38
|
+
protected table: YoloDbTable<R>;
|
|
39
|
+
constructor(dataPath: string, pkField: keyof R);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Helper function to create a new YoloDB table.
|
|
43
|
+
* It reuses the same table instance if the same file path is used.
|
|
44
|
+
*/
|
|
45
|
+
declare function yolodb<R extends Record<string, any>>(filePath: string, pkField: keyof R, initialData: R[], options?: YoloDbTableOptions): YoloDbTable<R>;
|
|
46
|
+
//#endregion
|
|
47
|
+
export { YoloDbDebugLogger, YoloDbError, YoloDbRepository, YoloDbTable, YoloDbTableOptions, yolodb };
|
|
48
|
+
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import superjson from "superjson";
|
|
4
|
+
|
|
5
|
+
//#region src/yolodb.ts
|
|
6
|
+
const logger = console;
|
|
7
|
+
var YoloDbError = class extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(`[YoloDB] ${message}`);
|
|
10
|
+
this.name = "YoloDbError";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Simple file-based JSON database built on top of SuperJSON.
|
|
15
|
+
*/
|
|
16
|
+
var YoloDbTable = class {
|
|
17
|
+
constructor(absoluteFilePath, pkField, initialData = [], options) {
|
|
18
|
+
const dirName = path.dirname(absoluteFilePath);
|
|
19
|
+
if (!fs.existsSync(dirName)) fs.mkdirSync(dirName, { recursive: true });
|
|
20
|
+
this.tableName = path.basename(absoluteFilePath).replace(".json", "");
|
|
21
|
+
this.pkField = pkField;
|
|
22
|
+
this.filePath = absoluteFilePath;
|
|
23
|
+
this.options = {
|
|
24
|
+
debugLogger: (message, ctx) => logger.debug(`[YoloDB] ${this.tableName} - ${message}`, ctx),
|
|
25
|
+
superjsonEnabled: true,
|
|
26
|
+
...options
|
|
27
|
+
};
|
|
28
|
+
if (!fs.existsSync(absoluteFilePath)) this.saveFile(initialData);
|
|
29
|
+
}
|
|
30
|
+
get log() {
|
|
31
|
+
return this.options.debugLogger;
|
|
32
|
+
}
|
|
33
|
+
getData() {
|
|
34
|
+
const db = this.readFile();
|
|
35
|
+
if (!Array.isArray(db)) throw new YoloDbError(`Invalid data in ${this.filePath}`);
|
|
36
|
+
return db;
|
|
37
|
+
}
|
|
38
|
+
all() {
|
|
39
|
+
return this.getData();
|
|
40
|
+
}
|
|
41
|
+
readFile() {
|
|
42
|
+
this.log(`Reading table from ${this.filePath}`);
|
|
43
|
+
if (!fs.existsSync(this.filePath)) {
|
|
44
|
+
this.saveFile([]);
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
const fileContent = fs.readFileSync(this.filePath, "utf8");
|
|
48
|
+
return this.options.superjsonEnabled ? superjson.parse(fileContent) : JSON.parse(fileContent);
|
|
49
|
+
}
|
|
50
|
+
saveFile(db) {
|
|
51
|
+
this.log(`Saving table to ${this.filePath}`);
|
|
52
|
+
const serializedDb = this.options.superjsonEnabled ? JSON.stringify(superjson.serialize(db), null, 2) : JSON.stringify(db, null, 2);
|
|
53
|
+
fs.writeFileSync(this.filePath, serializedDb);
|
|
54
|
+
}
|
|
55
|
+
findById(id) {
|
|
56
|
+
return this.getData().find((record) => record[this.pkField] === id);
|
|
57
|
+
}
|
|
58
|
+
findBy(field, value) {
|
|
59
|
+
return this.getData().filter((record) => record[field] === value);
|
|
60
|
+
}
|
|
61
|
+
findFirstBy(field, value) {
|
|
62
|
+
return this.getData().find((record) => record[field] === value);
|
|
63
|
+
}
|
|
64
|
+
search(filterFn) {
|
|
65
|
+
return this.getData().filter((record) => filterFn(record));
|
|
66
|
+
}
|
|
67
|
+
insert(record) {
|
|
68
|
+
if (!record[this.pkField]) throw new YoloDbError(`Record does not have a primary key: ${this.tableName}.${String(this.pkField)}`);
|
|
69
|
+
const db = this.getData();
|
|
70
|
+
db.push(record);
|
|
71
|
+
this.saveFile(db);
|
|
72
|
+
}
|
|
73
|
+
insertMany(records) {
|
|
74
|
+
const db = this.getData();
|
|
75
|
+
db.push(...records);
|
|
76
|
+
this.saveFile(db);
|
|
77
|
+
}
|
|
78
|
+
update(record) {
|
|
79
|
+
const pk = record[this.pkField];
|
|
80
|
+
if (!pk) throw new YoloDbError(`Record not found in ${this.tableName}: ${pk}`);
|
|
81
|
+
const db = this.getData();
|
|
82
|
+
for (const currentRecord of db) if (currentRecord[this.pkField] === pk) {
|
|
83
|
+
Object.assign(currentRecord, record);
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
this.saveFile(db);
|
|
87
|
+
}
|
|
88
|
+
updateMany(records) {
|
|
89
|
+
records.forEach((record) => this.update(record));
|
|
90
|
+
}
|
|
91
|
+
delete(id) {
|
|
92
|
+
this.deleteMany([id]);
|
|
93
|
+
}
|
|
94
|
+
deleteMany(ids) {
|
|
95
|
+
this.log(`Deleting ${ids.length} records`);
|
|
96
|
+
const db = this.getData().filter((record) => !ids.includes(record[this.pkField]));
|
|
97
|
+
this.saveFile(db);
|
|
98
|
+
}
|
|
99
|
+
truncate() {
|
|
100
|
+
this.saveFile([]);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
var YoloDbRepository = class {
|
|
104
|
+
constructor(dataPath, pkField) {
|
|
105
|
+
this.table = yolodb(dataPath, pkField, []);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
const _yoloDbTables = {};
|
|
109
|
+
/**
|
|
110
|
+
* Helper function to create a new YoloDB table.
|
|
111
|
+
* It reuses the same table instance if the same file path is used.
|
|
112
|
+
*/
|
|
113
|
+
function yolodb(filePath, pkField, initialData, options) {
|
|
114
|
+
if (!_yoloDbTables[filePath]) _yoloDbTables[filePath] = new YoloDbTable(filePath, pkField, initialData, options);
|
|
115
|
+
return _yoloDbTables[filePath];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
//#endregion
|
|
119
|
+
export { YoloDbError, YoloDbRepository, YoloDbTable, yolodb };
|
|
120
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/yolodb.ts"],"sourcesContent":["import fs from 'node:fs'\nimport path from 'node:path'\nimport superjson from 'superjson'\n\nconst logger = console\n\nexport type YoloDbDebugLogger = typeof logger.debug\n\nexport class YoloDbError extends Error {\n constructor(message: string) {\n super(`[YoloDB] ${message}`)\n this.name = 'YoloDbError'\n }\n}\n\nexport type YoloDbTableOptions = {\n superjsonEnabled?: boolean\n debugLogger?: YoloDbDebugLogger\n}\n\n/**\n * Simple file-based JSON database built on top of SuperJSON.\n */\nexport class YoloDbTable<R extends Record<string, any>> {\n private readonly tableName: string\n private readonly pkField: keyof R\n private readonly filePath: string\n private readonly options: Required<YoloDbTableOptions>\n\n constructor(absoluteFilePath: string, pkField: keyof R, initialData: R[] = [], options?: YoloDbTableOptions) {\n const dirName = path.dirname(absoluteFilePath)\n\n if (!fs.existsSync(dirName)) {\n fs.mkdirSync(dirName, { recursive: true })\n }\n\n this.tableName = path.basename(absoluteFilePath).replace('.json', '')\n this.pkField = pkField\n this.filePath = absoluteFilePath\n\n this.options = {\n debugLogger: (message, ctx) => logger.debug(`[YoloDB] ${this.tableName} - ${message}`, ctx),\n superjsonEnabled: true,\n ...options,\n }\n\n if (!fs.existsSync(absoluteFilePath)) {\n this.saveFile(initialData)\n }\n }\n\n private get log(): YoloDbDebugLogger {\n return this.options.debugLogger\n }\n\n private getData(): R[] {\n const db = this.readFile()\n if (!Array.isArray(db)) {\n throw new YoloDbError(`Invalid data in ${this.filePath}`)\n }\n return db\n }\n\n all(): R[] {\n return this.getData()\n }\n\n readFile(): R[] {\n this.log(`Reading table from ${this.filePath}`)\n\n if (!fs.existsSync(this.filePath)) {\n this.saveFile([])\n return []\n }\n\n const fileContent = fs.readFileSync(this.filePath, 'utf8')\n return this.options.superjsonEnabled ? superjson.parse<R[]>(fileContent) : JSON.parse(fileContent)\n }\n\n saveFile(db: R[]): void {\n this.log(`Saving table to ${this.filePath}`)\n const serializedDb = this.options.superjsonEnabled\n ? JSON.stringify(superjson.serialize(db), null, 2)\n : JSON.stringify(db, null, 2)\n fs.writeFileSync(this.filePath, serializedDb)\n }\n\n findById(id: string): R | undefined {\n const db = this.getData()\n return db.find((record) => record[this.pkField] === id)\n }\n\n findBy(field: keyof R, value: any): R[] {\n const db = this.getData()\n return db.filter((record) => record[field] === value)\n }\n\n findFirstBy(field: keyof R, value: any): R | undefined {\n const db = this.getData()\n return db.find((record) => record[field] === value)\n }\n\n search(filterFn: (record: R) => boolean): R[] {\n const db = this.getData()\n return db.filter((record) => filterFn(record))\n }\n\n insert(record: R): void {\n const pk = record[this.pkField]\n if (!pk) {\n throw new YoloDbError(`Record does not have a primary key: ${this.tableName}.${String(this.pkField)}`)\n }\n\n const db = this.getData()\n db.push(record)\n\n this.saveFile(db)\n }\n\n insertMany(records: R[]): void {\n const db = this.getData()\n db.push(...records)\n\n this.saveFile(db)\n }\n\n update(record: Partial<R>): void {\n const pk = record[this.pkField]\n if (!pk) {\n throw new YoloDbError(`Record not found in ${this.tableName}: ${pk}`)\n }\n\n const db = this.getData()\n\n for (const currentRecord of db) {\n if (currentRecord[this.pkField] === pk) {\n Object.assign(currentRecord, record)\n break\n }\n }\n\n this.saveFile(db)\n }\n\n updateMany(records: Array<Partial<R>>): void {\n records.forEach((record) => this.update(record))\n }\n\n delete(id: string): void {\n this.deleteMany([id])\n }\n\n deleteMany(ids: string[]): void {\n this.log(`Deleting ${ids.length} records`)\n const db = this.getData().filter((record) => !ids.includes(record[this.pkField]))\n\n this.saveFile(db)\n }\n\n truncate(): void {\n this.saveFile([])\n }\n}\n\nexport class YoloDbRepository<R extends Record<string, any>> {\n protected table: YoloDbTable<R>\n\n constructor(dataPath: string, pkField: keyof R) {\n this.table = yolodb<R>(dataPath, pkField, [])\n }\n}\n\n// In-memory cache of YoloDB table instances\nconst _yoloDbTables: Record<string, YoloDbTable<any>> = {}\n\n/**\n * Helper function to create a new YoloDB table.\n * It reuses the same table instance if the same file path is used.\n */\nexport function yolodb<R extends Record<string, any>>(\n filePath: string,\n pkField: keyof R,\n initialData: R[],\n options?: YoloDbTableOptions,\n): YoloDbTable<R> {\n if (!_yoloDbTables[filePath]) {\n _yoloDbTables[filePath] = new YoloDbTable<R>(filePath, pkField, initialData, options)\n }\n return _yoloDbTables[filePath]\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yolodb",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"homepage": "https://itsjavi.com/yolodb",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -10,40 +10,33 @@
|
|
|
10
10
|
"type": "module",
|
|
11
11
|
"exports": {
|
|
12
12
|
".": {
|
|
13
|
-
"import": "./dist/index.
|
|
14
|
-
"require": "./dist/index.
|
|
13
|
+
"import": "./dist/index.mjs",
|
|
14
|
+
"require": "./dist/index.mjs"
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
|
-
"main": "./dist/index.
|
|
18
|
-
"module": "./dist/index.
|
|
19
|
-
"types": "./dist/index.d.
|
|
17
|
+
"main": "./dist/index.mjs",
|
|
18
|
+
"module": "./dist/index.mjs",
|
|
19
|
+
"types": "./dist/index.d.mts",
|
|
20
20
|
"files": [
|
|
21
21
|
"dist",
|
|
22
22
|
"README.md",
|
|
23
23
|
"LICENSE"
|
|
24
24
|
],
|
|
25
|
-
"scripts": {
|
|
26
|
-
"build": "tsup src/index.ts --dts --sourcemap --format esm --clean",
|
|
27
|
-
"format": "sort-package-json && prettier --write ./src README.md tsconfig.json",
|
|
28
|
-
"lint": "pnpm run typecheck && prettier --check ./src README.md tsconfig.json && publint",
|
|
29
|
-
"prepare": "sort-package-json",
|
|
30
|
-
"prepublishOnly": "pnpm run lint && pnpm run build",
|
|
31
|
-
"typecheck": "tsc --noEmit"
|
|
32
|
-
},
|
|
33
25
|
"dependencies": {
|
|
34
|
-
"superjson": "^2.2.
|
|
26
|
+
"superjson": "^2.2.6"
|
|
35
27
|
},
|
|
36
28
|
"devDependencies": {
|
|
37
|
-
"@types/node": "^
|
|
38
|
-
"prettier": "^3.
|
|
39
|
-
"publint": "^0.3.
|
|
40
|
-
"sort-package-json": "^
|
|
41
|
-
"
|
|
42
|
-
"typescript": "^5.
|
|
29
|
+
"@types/node": "^25.0.3",
|
|
30
|
+
"prettier": "^3.7.4",
|
|
31
|
+
"publint": "^0.3.16",
|
|
32
|
+
"sort-package-json": "^3.6.0",
|
|
33
|
+
"tsdown": "0.19.0-beta.2",
|
|
34
|
+
"typescript": "^5.9.3"
|
|
43
35
|
},
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsdown src/index.ts --dts --sourcemap --format esm --clean",
|
|
38
|
+
"format": "sort-package-json && prettier --write ./src README.md tsconfig.json",
|
|
39
|
+
"lint": "pnpm run typecheck && prettier --check ./src README.md tsconfig.json && publint",
|
|
40
|
+
"typecheck": "tsc --noEmit"
|
|
48
41
|
}
|
|
49
|
-
}
|
|
42
|
+
}
|
package/dist/index.d.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
type YoloDbLogger = (...args: any[]) => void;
|
|
2
|
-
declare class YoloDbError extends Error {
|
|
3
|
-
constructor(message: string);
|
|
4
|
-
}
|
|
5
|
-
type YoloDbTableOptions = {
|
|
6
|
-
logger?: YoloDbLogger;
|
|
7
|
-
};
|
|
8
|
-
/**
|
|
9
|
-
* Simple file-based JSON database built on top of SuperJSON.
|
|
10
|
-
*/
|
|
11
|
-
declare class YoloDbTable<R extends Record<string, any>> {
|
|
12
|
-
private readonly tableName;
|
|
13
|
-
private readonly pkField;
|
|
14
|
-
private readonly filePath;
|
|
15
|
-
private readonly options;
|
|
16
|
-
constructor(absoluteFilePath: string, pkField: keyof R, initialData?: R[], options?: YoloDbTableOptions);
|
|
17
|
-
private get log();
|
|
18
|
-
private getData;
|
|
19
|
-
all(): R[];
|
|
20
|
-
readFile(): R[];
|
|
21
|
-
saveFile(db: R[]): void;
|
|
22
|
-
findById(id: string): R | undefined;
|
|
23
|
-
findBy(field: keyof R, value: any): R[];
|
|
24
|
-
findFirstBy(field: keyof R, value: any): R | undefined;
|
|
25
|
-
search(filterFn: (record: R) => boolean): R[];
|
|
26
|
-
insert(record: R): void;
|
|
27
|
-
insertMany(records: R[]): void;
|
|
28
|
-
update(record: Partial<R>): void;
|
|
29
|
-
updateMany(records: Array<Partial<R>>): void;
|
|
30
|
-
delete(id: string): void;
|
|
31
|
-
deleteMany(ids: string[]): void;
|
|
32
|
-
truncate(): void;
|
|
33
|
-
}
|
|
34
|
-
declare class YoloDbRepository<R extends Record<string, any>> {
|
|
35
|
-
protected table: YoloDbTable<R>;
|
|
36
|
-
constructor(dataPath: string, pkField: keyof R);
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Helper function to create a new YoloDB table.
|
|
40
|
-
* It reuses the same table instance if the same file path is used.
|
|
41
|
-
*/
|
|
42
|
-
declare function yolodb<R extends Record<string, any>>(filePath: string, pkField: keyof R, initialData: R[], options?: YoloDbTableOptions): YoloDbTable<R>;
|
|
43
|
-
|
|
44
|
-
export { YoloDbError, type YoloDbLogger, YoloDbRepository, YoloDbTable, type YoloDbTableOptions, yolodb };
|
package/dist/index.js
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
// src/yolodb.ts
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import superjson from "superjson";
|
|
5
|
-
var YoloDbError = class extends Error {
|
|
6
|
-
constructor(message) {
|
|
7
|
-
super(`[YoloDB] ${message}`);
|
|
8
|
-
this.name = "YoloDbError";
|
|
9
|
-
}
|
|
10
|
-
};
|
|
11
|
-
var YoloDbTable = class {
|
|
12
|
-
constructor(absoluteFilePath, pkField, initialData = [], options) {
|
|
13
|
-
const dirName = path.dirname(absoluteFilePath);
|
|
14
|
-
if (!fs.existsSync(dirName)) {
|
|
15
|
-
fs.mkdirSync(dirName, { recursive: true });
|
|
16
|
-
}
|
|
17
|
-
this.tableName = path.basename(absoluteFilePath).replace(".json", "");
|
|
18
|
-
this.pkField = pkField;
|
|
19
|
-
this.filePath = absoluteFilePath;
|
|
20
|
-
this.options = {
|
|
21
|
-
logger: (...args) => console.debug(`[YoloDB] ${this.tableName}`, ...args),
|
|
22
|
-
...options
|
|
23
|
-
};
|
|
24
|
-
if (!fs.existsSync(absoluteFilePath)) {
|
|
25
|
-
this.saveFile(initialData);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
get log() {
|
|
29
|
-
return this.options.logger;
|
|
30
|
-
}
|
|
31
|
-
getData() {
|
|
32
|
-
const db = this.readFile();
|
|
33
|
-
if (!Array.isArray(db)) {
|
|
34
|
-
throw new YoloDbError(`Invalid data in ${this.filePath}`);
|
|
35
|
-
}
|
|
36
|
-
return db;
|
|
37
|
-
}
|
|
38
|
-
all() {
|
|
39
|
-
return this.getData();
|
|
40
|
-
}
|
|
41
|
-
readFile() {
|
|
42
|
-
this.log(`Reading table from ${this.filePath}`);
|
|
43
|
-
if (!fs.existsSync(this.filePath)) {
|
|
44
|
-
this.saveFile([]);
|
|
45
|
-
return [];
|
|
46
|
-
}
|
|
47
|
-
const fileContent = fs.readFileSync(this.filePath, "utf8");
|
|
48
|
-
return superjson.parse(fileContent);
|
|
49
|
-
}
|
|
50
|
-
saveFile(db) {
|
|
51
|
-
this.log(`Saving table to ${this.filePath}`);
|
|
52
|
-
fs.writeFileSync(this.filePath, JSON.stringify(superjson.serialize(db), null, 2));
|
|
53
|
-
}
|
|
54
|
-
findById(id) {
|
|
55
|
-
const db = this.getData();
|
|
56
|
-
return db.find((record) => record[this.pkField] === id);
|
|
57
|
-
}
|
|
58
|
-
findBy(field, value) {
|
|
59
|
-
const db = this.getData();
|
|
60
|
-
return db.filter((record) => record[field] === value);
|
|
61
|
-
}
|
|
62
|
-
findFirstBy(field, value) {
|
|
63
|
-
const db = this.getData();
|
|
64
|
-
return db.find((record) => record[field] === value);
|
|
65
|
-
}
|
|
66
|
-
search(filterFn) {
|
|
67
|
-
const db = this.getData();
|
|
68
|
-
return db.filter((record) => filterFn(record));
|
|
69
|
-
}
|
|
70
|
-
insert(record) {
|
|
71
|
-
const pk = record[this.pkField];
|
|
72
|
-
if (!pk) {
|
|
73
|
-
throw new YoloDbError(`Record does not have a primary key: ${this.tableName}.${String(this.pkField)}`);
|
|
74
|
-
}
|
|
75
|
-
const db = this.getData();
|
|
76
|
-
db.push(record);
|
|
77
|
-
this.saveFile(db);
|
|
78
|
-
}
|
|
79
|
-
insertMany(records) {
|
|
80
|
-
const db = this.getData();
|
|
81
|
-
db.push(...records);
|
|
82
|
-
this.saveFile(db);
|
|
83
|
-
}
|
|
84
|
-
update(record) {
|
|
85
|
-
const pk = record[this.pkField];
|
|
86
|
-
if (!pk) {
|
|
87
|
-
throw new YoloDbError(`Record not found in ${this.tableName}: ${pk}`);
|
|
88
|
-
}
|
|
89
|
-
const db = this.getData();
|
|
90
|
-
for (const currentRecord of db) {
|
|
91
|
-
if (currentRecord[this.pkField] === pk) {
|
|
92
|
-
Object.assign(currentRecord, record);
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
this.saveFile(db);
|
|
97
|
-
}
|
|
98
|
-
updateMany(records) {
|
|
99
|
-
records.forEach((record) => this.update(record));
|
|
100
|
-
}
|
|
101
|
-
delete(id) {
|
|
102
|
-
this.deleteMany([id]);
|
|
103
|
-
}
|
|
104
|
-
deleteMany(ids) {
|
|
105
|
-
this.log(`Deleting ${ids.length} records`);
|
|
106
|
-
const db = this.getData().filter((record) => !ids.includes(record[this.pkField]));
|
|
107
|
-
this.saveFile(db);
|
|
108
|
-
}
|
|
109
|
-
truncate() {
|
|
110
|
-
this.saveFile([]);
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
var YoloDbRepository = class {
|
|
114
|
-
constructor(dataPath, pkField) {
|
|
115
|
-
this.table = yolodb(dataPath, pkField, []);
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
var _yoloDbTables = {};
|
|
119
|
-
function yolodb(filePath, pkField, initialData, options) {
|
|
120
|
-
if (!_yoloDbTables[filePath]) {
|
|
121
|
-
_yoloDbTables[filePath] = new YoloDbTable(filePath, pkField, initialData, options);
|
|
122
|
-
}
|
|
123
|
-
return _yoloDbTables[filePath];
|
|
124
|
-
}
|
|
125
|
-
export {
|
|
126
|
-
YoloDbError,
|
|
127
|
-
YoloDbRepository,
|
|
128
|
-
YoloDbTable,
|
|
129
|
-
yolodb
|
|
130
|
-
};
|
|
131
|
-
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/yolodb.ts"],"sourcesContent":["import fs from 'node:fs'\nimport path from 'node:path'\nimport superjson from 'superjson'\n\nexport type YoloDbLogger = (...args: any[]) => void\n\nexport class YoloDbError extends Error {\n constructor(message: string) {\n super(`[YoloDB] ${message}`)\n this.name = 'YoloDbError'\n }\n}\n\nexport type YoloDbTableOptions = {\n logger?: YoloDbLogger\n}\n\n/**\n * Simple file-based JSON database built on top of SuperJSON.\n */\nexport class YoloDbTable<R extends Record<string, any>> {\n private readonly tableName: string\n private readonly pkField: keyof R\n private readonly filePath: string\n private readonly options: Required<YoloDbTableOptions>\n\n constructor(absoluteFilePath: string, pkField: keyof R, initialData: R[] = [], options?: YoloDbTableOptions) {\n const dirName = path.dirname(absoluteFilePath)\n\n if (!fs.existsSync(dirName)) {\n fs.mkdirSync(dirName, { recursive: true })\n }\n\n this.tableName = path.basename(absoluteFilePath).replace('.json', '')\n this.pkField = pkField\n this.filePath = absoluteFilePath\n\n this.options = {\n logger: (...args) => console.debug(`[YoloDB] ${this.tableName}`, ...args),\n ...options,\n }\n\n if (!fs.existsSync(absoluteFilePath)) {\n this.saveFile(initialData)\n }\n }\n\n private get log(): YoloDbLogger {\n return this.options.logger\n }\n\n private getData(): R[] {\n const db = this.readFile()\n if (!Array.isArray(db)) {\n throw new YoloDbError(`Invalid data in ${this.filePath}`)\n }\n return db\n }\n\n all(): R[] {\n return this.getData()\n }\n\n readFile(): R[] {\n this.log(`Reading table from ${this.filePath}`)\n\n if (!fs.existsSync(this.filePath)) {\n this.saveFile([])\n return []\n }\n\n const fileContent = fs.readFileSync(this.filePath, 'utf8')\n return superjson.parse<R[]>(fileContent)\n }\n\n saveFile(db: R[]): void {\n this.log(`Saving table to ${this.filePath}`)\n fs.writeFileSync(this.filePath, JSON.stringify(superjson.serialize(db), null, 2))\n }\n\n findById(id: string): R | undefined {\n const db = this.getData()\n return db.find((record) => record[this.pkField] === id)\n }\n\n findBy(field: keyof R, value: any): R[] {\n const db = this.getData()\n return db.filter((record) => record[field] === value)\n }\n\n findFirstBy(field: keyof R, value: any): R | undefined {\n const db = this.getData()\n return db.find((record) => record[field] === value)\n }\n\n search(filterFn: (record: R) => boolean): R[] {\n const db = this.getData()\n return db.filter((record) => filterFn(record))\n }\n\n insert(record: R): void {\n const pk = record[this.pkField]\n if (!pk) {\n throw new YoloDbError(`Record does not have a primary key: ${this.tableName}.${String(this.pkField)}`)\n }\n\n const db = this.getData()\n db.push(record)\n\n this.saveFile(db)\n }\n\n insertMany(records: R[]): void {\n const db = this.getData()\n db.push(...records)\n\n this.saveFile(db)\n }\n\n update(record: Partial<R>): void {\n const pk = record[this.pkField]\n if (!pk) {\n throw new YoloDbError(`Record not found in ${this.tableName}: ${pk}`)\n }\n\n const db = this.getData()\n\n for (const currentRecord of db) {\n if (currentRecord[this.pkField] === pk) {\n Object.assign(currentRecord, record)\n break\n }\n }\n\n this.saveFile(db)\n }\n\n updateMany(records: Array<Partial<R>>): void {\n records.forEach((record) => this.update(record))\n }\n\n delete(id: string): void {\n this.deleteMany([id])\n }\n\n deleteMany(ids: string[]): void {\n this.log(`Deleting ${ids.length} records`)\n const db = this.getData().filter((record) => !ids.includes(record[this.pkField]))\n\n this.saveFile(db)\n }\n\n truncate(): void {\n this.saveFile([])\n }\n}\n\nexport class YoloDbRepository<R extends Record<string, any>> {\n protected table: YoloDbTable<R>\n\n constructor(dataPath: string, pkField: keyof R) {\n this.table = yolodb<R>(dataPath, pkField, [])\n }\n}\n\n// In-memory cache of YoloDB table instances\nconst _yoloDbTables: Record<string, YoloDbTable<any>> = {}\n\n/**\n * Helper function to create a new YoloDB table.\n * It reuses the same table instance if the same file path is used.\n */\nexport function yolodb<R extends Record<string, any>>(\n filePath: string,\n pkField: keyof R,\n initialData: R[],\n options?: YoloDbTableOptions,\n): YoloDbTable<R> {\n if (!_yoloDbTables[filePath]) {\n _yoloDbTables[filePath] = new YoloDbTable<R>(filePath, pkField, initialData, options)\n }\n return _yoloDbTables[filePath]\n}\n"],"mappings":"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","names":[]}
|