monkey-ldb 1.0.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/.gitattributes +2 -0
- package/LICENSE +21 -0
- package/MonkeyDB.js +327 -0
- package/README.md +112 -0
- package/assets/monkeydb.png +0 -0
- package/modules/native_documents.js +98 -0
- package/modules/operators.js +73 -0
- package/modules/operators_update.js +227 -0
- package/modules/utils.js +39 -0
- package/package.json +21 -0
package/.gitattributes
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ciberinuverse
|
|
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/MonkeyDB.js
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
const fs = require("fs/promises")
|
|
2
|
+
const monkey_document = require("./modules/native_documents.js")
|
|
3
|
+
const monkey_utils = require("./modules/utils.js")
|
|
4
|
+
|
|
5
|
+
async function read_document(name) {
|
|
6
|
+
let document_read = await fs.readFile(name)
|
|
7
|
+
return JSON.parse(document_read)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function save_document(name, new_document_object) {
|
|
11
|
+
await fs.writeFile(name, JSON.stringify(new_document_object))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class MonkeyCli {
|
|
15
|
+
|
|
16
|
+
#document_cached = {
|
|
17
|
+
"time": 0,
|
|
18
|
+
"document": 0,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
constructor(name, path_collections, use_cache = false) {
|
|
22
|
+
this.name = name
|
|
23
|
+
|
|
24
|
+
// Path collections es donde se almacenan todos los documentos
|
|
25
|
+
this.path_collections = monkey_utils.normalize_url(path_collections)
|
|
26
|
+
this.use_cache = use_cache
|
|
27
|
+
|
|
28
|
+
// Full path collection incluye el nombre de este documento con json
|
|
29
|
+
this.full_path_collection = this.path_collections + "/" + this.name + ".json"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async #cache_document() {
|
|
33
|
+
|
|
34
|
+
let files_list = await fs.readdir(this.path_collections + "/")
|
|
35
|
+
|
|
36
|
+
// Si no estamos en modo cache
|
|
37
|
+
if (!this.use_cache) {
|
|
38
|
+
|
|
39
|
+
// Se verifica que no exista el documento. Si no existe se retorna un false
|
|
40
|
+
if (!files_list.includes(this.name + ".json")) {
|
|
41
|
+
this.#document_cached["document"] = []
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Si existe el documento, se lee y se retorna true
|
|
46
|
+
// guardando el contenido de el documento en una variable privada
|
|
47
|
+
this.#document_cached["document"] = await read_document(this.full_path_collection)
|
|
48
|
+
return true
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let time_now = Date.now()
|
|
52
|
+
|
|
53
|
+
// Si el documento no existe dentro de la carpeta de la base de datos
|
|
54
|
+
if (!files_list.includes(this.name + ".json")) {
|
|
55
|
+
|
|
56
|
+
// Se almacena de manera global un array vacio como tambien la hora
|
|
57
|
+
this.#document_cached["time"] = time_now
|
|
58
|
+
this.#document_cached["document"] = []
|
|
59
|
+
|
|
60
|
+
// Se guarad el documento vacio para evitar que siga un siclo
|
|
61
|
+
await save_document(this.full_path_collection, [])
|
|
62
|
+
|
|
63
|
+
// Se retorna el array vacio indicando que no existe ningun documento asociado
|
|
64
|
+
return false
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Si el documento aun no esta cacheado se lee y se guarda y se retorna true
|
|
68
|
+
if (this.#document_cached["document"] === 0) {
|
|
69
|
+
|
|
70
|
+
this.#document_cached["time"] = time_now
|
|
71
|
+
this.#document_cached["document"] = await read_document(this.full_path_collection)
|
|
72
|
+
|
|
73
|
+
return true
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Se suman 5 minutos en ms al tiempo desde que se guardo el anterior estado
|
|
77
|
+
let time_cached = this.#document_cached["time"] + 3000
|
|
78
|
+
|
|
79
|
+
// Si se superan los 5 minutos se actualiza el cacheado y se guarda en el documento
|
|
80
|
+
if (time_cached < time_now) {
|
|
81
|
+
this.#document_cached["time"] = time_now
|
|
82
|
+
await save_document(this.full_path_collection, this.#document_cached["document"])
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Se retorna el documento cacheado
|
|
86
|
+
return true
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async find_one(object_find, object_project = null) {
|
|
90
|
+
|
|
91
|
+
// Se verifica la existencia del archivo o si existe cache
|
|
92
|
+
let exist_doc = await this.#cache_document()
|
|
93
|
+
if (!exist_doc) {return {}}
|
|
94
|
+
|
|
95
|
+
return monkey_document.iter_document_array(
|
|
96
|
+
object_find,
|
|
97
|
+
object_project,
|
|
98
|
+
this.#document_cached["document"],
|
|
99
|
+
"find_one"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async find(object_find, object_project = null) {
|
|
105
|
+
|
|
106
|
+
// Se verifica la existencia del documento
|
|
107
|
+
let exist_doc = await this.#cache_document()
|
|
108
|
+
if (!exist_doc) {return []}
|
|
109
|
+
|
|
110
|
+
return monkey_document.iter_document_array(object_find, object_project, this.#document_cached["document"], "find")
|
|
111
|
+
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async insert_one(object_insert) {
|
|
115
|
+
await this.#cache_document()
|
|
116
|
+
|
|
117
|
+
let _id = object_insert["_id"] || monkey_utils.gen_uuid()
|
|
118
|
+
|
|
119
|
+
object_insert["_id"] = _id
|
|
120
|
+
|
|
121
|
+
// Se verifica la existencia del documento
|
|
122
|
+
this.#document_cached["document"].push(object_insert)
|
|
123
|
+
|
|
124
|
+
// Si no se esta usando la forma de cache se guarda el documento
|
|
125
|
+
if (!this.use_cache) {
|
|
126
|
+
await save_document(this.full_path_collection, this.#document_cached["document"])
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return monkey_utils.monkey_db_return({
|
|
130
|
+
"acknowledged": true,
|
|
131
|
+
"_id": _id,
|
|
132
|
+
"insertedCount": 1
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async insert_many(array_insert) {
|
|
137
|
+
await this.#cache_document()
|
|
138
|
+
|
|
139
|
+
let insertedIds = []
|
|
140
|
+
|
|
141
|
+
for (let doc_one of array_insert) {
|
|
142
|
+
|
|
143
|
+
// Se genera un id para el objeto a insertar
|
|
144
|
+
let _id = doc_one["_id"] || monkey_utils.gen_uuid()
|
|
145
|
+
|
|
146
|
+
// Se le asigna el id
|
|
147
|
+
doc_one["_id"] = _id
|
|
148
|
+
|
|
149
|
+
// Se le asigna al buffer
|
|
150
|
+
this.#document_cached["document"].push(doc_one)
|
|
151
|
+
insertedIds.push(_id)
|
|
152
|
+
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!insertedIds) {
|
|
156
|
+
return monkey_utils.monkey_db_return({
|
|
157
|
+
"acknowledged": false,
|
|
158
|
+
"insertedCount": 0,
|
|
159
|
+
"insertedIds": []
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if (!this.use_cache) {
|
|
165
|
+
await save_document(this.full_path_collection, this.#document_cached["document"])
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return monkey_utils.monkey_db_return({
|
|
169
|
+
"acknowledged": true,
|
|
170
|
+
"insertedCount": insertedIds.length,
|
|
171
|
+
"insertedIds": insertedIds
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async delete_one(object_find) {
|
|
176
|
+
await this.#cache_document()
|
|
177
|
+
|
|
178
|
+
let acknowledged = false
|
|
179
|
+
|
|
180
|
+
let index_delete = monkey_document.iter_document_array(object_find, null, this.#document_cached["document"], "delete_one")
|
|
181
|
+
this.#document_cached["document"].splice(index_delete)
|
|
182
|
+
|
|
183
|
+
if (!this.use_cache) {
|
|
184
|
+
await save_document(this.full_path_collection, this.#document_cached["document"])
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return monkey_utils.monkey_db_return({
|
|
188
|
+
"acknowledged": true,
|
|
189
|
+
"deletedCount": 1,
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async delete_many(object_find) {
|
|
194
|
+
await this.#cache_document()
|
|
195
|
+
|
|
196
|
+
let array_of_index_delete = await monkey_document.iter_document_array(object_find, null, this.#document_cached["document"], "delete_many")
|
|
197
|
+
|
|
198
|
+
if (!array_of_index_delete) {
|
|
199
|
+
return monkey_utils.monkey_db_return({
|
|
200
|
+
"acknowledged": false,
|
|
201
|
+
"deletedCount": 0
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
for (let index_delete of array_of_index_delete) {
|
|
206
|
+
this.#document_cached["document"].splice(index_delete)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
if (!this.use_cache) {
|
|
211
|
+
await save_document(this.full_path_collection, this.#document_cached["document"])
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let deleted_account = array_of_index_delete.length
|
|
215
|
+
return monkey_utils.monkey_db_return({
|
|
216
|
+
"acknowledged": true,
|
|
217
|
+
"deletedCount": deleted_account
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async update_one(object_find, mod_filter) {
|
|
223
|
+
await this.#cache_document()
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
let response = await monkey_document.iter_document_array(object_find, mod_filter, this.#document_cached["document"], "update_one")
|
|
227
|
+
|
|
228
|
+
let new_document = response[0]
|
|
229
|
+
|
|
230
|
+
if (!new_document) {return monkey_utils.monkey_db_return({
|
|
231
|
+
"acknowledged": false,
|
|
232
|
+
"matchedCount": 0,
|
|
233
|
+
"modifiedCount": 0
|
|
234
|
+
})}
|
|
235
|
+
|
|
236
|
+
let index_update = response[1]
|
|
237
|
+
|
|
238
|
+
this.#document_cached["document"][index_update] = new_document
|
|
239
|
+
|
|
240
|
+
if (!this.use_cache) {
|
|
241
|
+
await save_document(this.full_path_collection, this.#document_cached["document"])
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return monkey_utils.monkey_db_return({
|
|
245
|
+
"acknowledged": true,
|
|
246
|
+
"matchedCount": 1,
|
|
247
|
+
"modifiedCount": 1
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async update_many(object_find, mod_filter) {
|
|
253
|
+
await this.#cache_document()
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
let response = await monkey_document.iter_document_array(object_find, mod_filter, this.#document_cached["document"], "update_many")
|
|
257
|
+
|
|
258
|
+
for (let doc_updated of response) {
|
|
259
|
+
|
|
260
|
+
let new_document = doc_updated[0]
|
|
261
|
+
let index_update = doc_updated[1]
|
|
262
|
+
|
|
263
|
+
this.#document_cached["document"][index_update] = new_document
|
|
264
|
+
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!this.use_cache) {
|
|
268
|
+
await save_document(this.full_path_collection, this.#document_cached["document"])
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
let mod_count = response.length
|
|
272
|
+
return monkey_utils.monkey_db_return({
|
|
273
|
+
"acknowledged": true,
|
|
274
|
+
"modifiedCount": mod_count,
|
|
275
|
+
"matchedCount": mod_count
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
class MonkeyDB {
|
|
281
|
+
|
|
282
|
+
#collections = []
|
|
283
|
+
|
|
284
|
+
constructor(name_db, path_db = ".", cache = false) {
|
|
285
|
+
this.name_db = name_db
|
|
286
|
+
this.path_db = monkey_utils.normalize_url(path_db)
|
|
287
|
+
|
|
288
|
+
this.cache = cache
|
|
289
|
+
this.uri = path_db + "/" + name_db
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Funcion privada encargada de verificar que la base de datos se encuentre en la carpeta
|
|
293
|
+
async #dir_db_exist() {
|
|
294
|
+
|
|
295
|
+
let db_dir = await fs.readdir(this.path_db)
|
|
296
|
+
|
|
297
|
+
// De no ser asi la crea
|
|
298
|
+
if (!db_dir.includes(this.name_db)) {
|
|
299
|
+
await fs.mkdir(this.name_db)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Actualiza la lista de colecciones en la bd
|
|
303
|
+
this.#collections = await fs.readdir(this.uri + "/")
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async create_collection(name_collection) {
|
|
307
|
+
// Verifica y crea la carpeta de la base de datos si es necesario
|
|
308
|
+
await this.#dir_db_exist()
|
|
309
|
+
|
|
310
|
+
return new MonkeyCli(name_collection, this.uri, this.cache)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async drop_collection(name_collection) {
|
|
314
|
+
await this.#dir_db_exist()
|
|
315
|
+
|
|
316
|
+
if (!this.#collections.includes(name_collection + ".json")) {
|
|
317
|
+
return false
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
await fs.rm(this.uri + "/" + name_collection + ".json")
|
|
321
|
+
return true
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Se exporta unicamente MonkeyDB encargado de crear la base de datos
|
|
327
|
+
module.exports = {MonkeyDB}
|
package/README.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# MonkeyDB
|
|
2
|
+
|
|
3
|
+
> Una base de datos NoSQL local para Node.js, inspirada en MongoDB y construida con pura curiosidad y ganas de aprender.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
*Si no estoy durmiendo, estoy programando.*
|
|
7
|
+
|
|
8
|
+
MonkeyDB es una base de datos experimental que almacena datos en archivos JSON, diseñada para proyectos pequeños que necesitan una API familiar de MongoDB sin levantar un servidor externo. Nació como un ejercicio de aprendizaje y para cubrir una necesidad personal: quería una base de datos local y liviana que se sintiera como MongoDB, pero sin saber que ya existían varias alternativas consolidadas. Así que… ¿Por qué no construir la mía?
|
|
9
|
+
|
|
10
|
+
## Características
|
|
11
|
+
|
|
12
|
+
- **API inspirada en MongoDB** – usa métodos como `find()`, `insert_one()`, `insert_many()`.
|
|
13
|
+
- **Almacenamiento local** – cada colección se guarda como un archivo JSON.
|
|
14
|
+
- **Proyecciones** – puedes elegir qué campos devolver en las consultas (`{ "_id": 0 }`).
|
|
15
|
+
- **Caché opcional** – mejora el rendimiento cuando trabajas con colecciones grandes.
|
|
16
|
+
- **Manejo asíncrono** – basado en `fs/promises` para no bloquear el event loop.
|
|
17
|
+
- **Multi‑colección** – agrupa tus datos en colecciones dentro de una misma base de datos.
|
|
18
|
+
|
|
19
|
+
## Instalación
|
|
20
|
+
|
|
21
|
+
*(Pendiente de publicación en npm)*
|
|
22
|
+
|
|
23
|
+
## Uso básico
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
const { MonkeyDB } = require('monkeydb');
|
|
27
|
+
|
|
28
|
+
(async () => {
|
|
29
|
+
// Crear o abrir una base de datos
|
|
30
|
+
const db = new MonkeyDB('mi_app', './data', false);
|
|
31
|
+
|
|
32
|
+
// Crear una colección (automáticamente crea la carpeta si no existe)
|
|
33
|
+
const users = await db.create_collection('users');
|
|
34
|
+
|
|
35
|
+
// Insertar un documento
|
|
36
|
+
const result = await users.insert_one({ name: 'Ana', age: 28 });
|
|
37
|
+
console.log(result); // { acknowledged: true, _id: '...' }
|
|
38
|
+
|
|
39
|
+
// Insertar varios documentos
|
|
40
|
+
await users.insert_many([
|
|
41
|
+
{ name: 'Luis', age: 34 },
|
|
42
|
+
{ name: 'Elena', age: 25 }
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
// Buscar documentos con filtro y proyección
|
|
46
|
+
const ana = await users.find_one({ name: 'Ana' }, { _id: 0 });
|
|
47
|
+
console.log(ana); // { name: 'Ana', age: 28 }
|
|
48
|
+
|
|
49
|
+
const mayores = await users.find({ age: { $gt: 30 } }); // pronto operadores
|
|
50
|
+
console.log(mayores); // []
|
|
51
|
+
})();
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## API
|
|
55
|
+
|
|
56
|
+
### `MonkeyDB(name_db, path_db, options)`
|
|
57
|
+
- `name_db` – nombre de la base de datos (se usará como carpeta).
|
|
58
|
+
- `path_db` – ruta donde se almacenará la carpeta de la base de datos (por defecto `"."`).
|
|
59
|
+
- `cache` – habilita el sistema de caché experimental (por defecto `false`).
|
|
60
|
+
|
|
61
|
+
### `db.create_collection(name_collection)`
|
|
62
|
+
Crea una nueva colección (si no existe) y devuelve una instancia de `MonkeyCli`.
|
|
63
|
+
|
|
64
|
+
### `collection.insert_one(document)`
|
|
65
|
+
Inserta un documento y devuelve un objeto con `acknowledged` y el `_id` generado.
|
|
66
|
+
|
|
67
|
+
### `collection.insert_many(array)`
|
|
68
|
+
Inserta un array de documentos y devuelve `acknowledged`, `insertedCount` e `insertedIds`.
|
|
69
|
+
|
|
70
|
+
### `collection.find(filter, projection)`
|
|
71
|
+
Devuelve un array con todos los documentos que coinciden con el filtro.
|
|
72
|
+
`projection` permite excluir campos con `{ campo: 0 }`.
|
|
73
|
+
|
|
74
|
+
### `collection.find_one(filter, projection)`
|
|
75
|
+
Devuelve el primer documento que coincide con el filtro, o `{}` si no hay resultados.
|
|
76
|
+
|
|
77
|
+
## Estado actual y roadmap
|
|
78
|
+
|
|
79
|
+
MonkeyDB está en una fase **experimental**. Lo que ya funciona:
|
|
80
|
+
|
|
81
|
+
- `create_collection`
|
|
82
|
+
- `insert_one` / `insert_many`
|
|
83
|
+
- `find` / `find_one` con proyecciones básicas (solo exclusión)
|
|
84
|
+
- Operadores de consulta: `$gt`, `$lt`, `$in`, `$regex`, etc.
|
|
85
|
+
- Actualizaciones: `update_one`, `update_many`
|
|
86
|
+
- Eliminaciones: `delete_one`, `delete_many`
|
|
87
|
+
- Caché funcional (actualmente en pruebas)
|
|
88
|
+
- Uso asíncrono con `fs/promises`
|
|
89
|
+
- Rutas configurables
|
|
90
|
+
- Tests manuales
|
|
91
|
+
|
|
92
|
+
Próximas mejoras (¡contribuciones bienvenidas!):
|
|
93
|
+
|
|
94
|
+
- 🔄 Índices para mejorar búsquedas
|
|
95
|
+
- 🔄 Migración hacia/desde otras bases de datos (MongoDB, SQLite, etc.)
|
|
96
|
+
- 🔄 Tests unitarios y de integración
|
|
97
|
+
|
|
98
|
+
## Módulos
|
|
99
|
+
|
|
100
|
+
MonkeyDB está organizado en tres módulos internos que mantienen el código limpio y extensible:
|
|
101
|
+
|
|
102
|
+
| Módulo | Archivo | Propósito |
|
|
103
|
+
|--------|---------|-----------|
|
|
104
|
+
| **Operadores de actualización** | `operators_update.js` | Contiene todas las funciones y utilidades para acciones de update: `$set`, `$unset`, `$rename`, `$max`, `$min`, `$currentDate`, `$push`, `$pull`, etc. Exporta como única utilidad la función `operators_update`. |
|
|
105
|
+
| **Operadores de búsqueda** | `operators.js` | Implementa los operadores de consulta: `$ne`, `$nin`, `$in`, `$eq`, `$lt`, `$gt`, etc. Exporta como única función `operators_find`. |
|
|
106
|
+
| **Utilidades** | `utils.js` | Funciones de mantenimiento y ayuda, como las respuestas uniformes de MonkeyDB (consistencia en los formatos de salida). |
|
|
107
|
+
|
|
108
|
+
Estos módulos se integran en el archivo principal y permiten añadir nuevos operadores sin modificar la lógica central.
|
|
109
|
+
|
|
110
|
+
## Agradecimientos
|
|
111
|
+
|
|
112
|
+
Este proyecto está fuertemente inspirado en **MongitaDB**, una base de datos local que a su vez emula MongoDB. ¡Gracias a su creador por allanar el camino y sacarme de varios apuros mientras desarrollaba otros proyectos!
|
|
Binary file
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const monkey_operators = require("./operators.js")
|
|
2
|
+
const monkey_operators_update = require("./operators_update.js")
|
|
3
|
+
|
|
4
|
+
let iters_with_index = ["delete_many", "delete_one", "update_one", "update_many"]
|
|
5
|
+
function iter_document_array(object_find, object_project = null, array_document, type_r = "find") {
|
|
6
|
+
|
|
7
|
+
let return_doc = []
|
|
8
|
+
let index_list = 0 // Variable que se usara para todo lo que requiera indice de listas
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
// Se establece el numero de coincidencias desde el objeto a buscar
|
|
12
|
+
let check = Object.values(object_find).length
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
// Por cada documento encontrado se revisara que cumpla con el filtro enviado
|
|
17
|
+
for (let one_doc of array_document) {
|
|
18
|
+
|
|
19
|
+
// Contador de coincidencias por usuario
|
|
20
|
+
let passed_check = 0
|
|
21
|
+
|
|
22
|
+
// Se setean las variables para usarle dentro del for
|
|
23
|
+
let key, value
|
|
24
|
+
|
|
25
|
+
for ([key, value] of Object.entries(object_find)) {
|
|
26
|
+
if (monkey_operators.operators_find(one_doc, key, value)) {passed_check += 1}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Seccion unicamente para resultados unicos que no requieren mayor iteracion */
|
|
30
|
+
// Se usan else if al ser mas rapidos
|
|
31
|
+
if (type_r === "delete_one" && passed_check === check) {
|
|
32
|
+
return index_list
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Se retorna la primera coincidencia con la proyeccion si es que se esta pidiendo uno solo
|
|
36
|
+
else if (type_r === "find_one" && passed_check === check) {
|
|
37
|
+
return project(one_doc, object_project)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
else if (type_r === "update_one" && passed_check === check) {
|
|
41
|
+
return [monkey_operators_update.operators_update(one_doc, object_project), index_list]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* ===================================================================== */
|
|
45
|
+
|
|
46
|
+
/* Seccion operacion listado
|
|
47
|
+
|
|
48
|
+
Todo lo que esta aqui debajo hasta lo limitado son las operaciones que requieren de una iteracion
|
|
49
|
+
completa del documento. Tales como find, delete_many
|
|
50
|
+
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
// Si se cumple con el filtro se agrega a la lista y se continua
|
|
54
|
+
else if (type_r === "find" && passed_check === check) {
|
|
55
|
+
return_doc.push(project(one_doc, object_project))
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Zonas que requieren indices
|
|
60
|
+
else if (type_r === "delete_many" && passed_check === check) {
|
|
61
|
+
return_doc.push(index_list)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
else if (type_r === "update_many" && passed_check === check) {
|
|
65
|
+
return_doc.push([monkey_operators_update.operators_update(one_doc, object_project), index_list])
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (iters_with_index.includes(type_r)) {index_list += 1}
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Se retornan todos los documentos en la lista
|
|
73
|
+
return return_doc
|
|
74
|
+
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function project(object_return, object_project = null) {
|
|
78
|
+
|
|
79
|
+
// Si es que no se trae ninguna proyeccion se retorna el objeto intacto
|
|
80
|
+
if (object_project === null) {return object_return}
|
|
81
|
+
|
|
82
|
+
let key, value
|
|
83
|
+
for ([key, value] of Object.entries(object_project)) {
|
|
84
|
+
|
|
85
|
+
// Si se le asigno un false o un 0 se elimina esa key del object
|
|
86
|
+
if (!value) {
|
|
87
|
+
delete object_return[key]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Se retorna el nuevo object
|
|
93
|
+
return object_return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = {
|
|
97
|
+
iter_document_array
|
|
98
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
function eq(one_doc_value, value_find) {return one_doc_value === value_find}
|
|
2
|
+
function ne(one_doc_value, value_find) {return one_doc_value !== value_find}
|
|
3
|
+
function gt(one_doc_value, value_find) {return one_doc_value > value_find}
|
|
4
|
+
function gte(one_doc_value, value_find) {return one_doc_value >= value_find}
|
|
5
|
+
function lt(one_doc_value, value_find) {return one_doc_value < value_find}
|
|
6
|
+
function lte(one_doc_value, value_find) {return one_doc_value <= value_find}
|
|
7
|
+
function in_d(one_doc_value, value_find) {return value_find.includes(one_doc_value)}
|
|
8
|
+
function nin_d(one_doc_value, value_find) {return !value_find.includes(one_doc_value)}
|
|
9
|
+
|
|
10
|
+
let operators = {
|
|
11
|
+
"$eq": eq,
|
|
12
|
+
"$ne": ne,
|
|
13
|
+
"$gt": gt,
|
|
14
|
+
"$gte": gte,
|
|
15
|
+
"$lt": lt,
|
|
16
|
+
"$lte": lte,
|
|
17
|
+
"$in": in_d,
|
|
18
|
+
"$nin": nin_d
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
function operators_find(one_doc, key_find, value_find) {
|
|
23
|
+
|
|
24
|
+
// Si se encuentra la coincidencia directa se retorna true
|
|
25
|
+
if (one_doc[key_find] === value_find) {return true}
|
|
26
|
+
|
|
27
|
+
// Si no es un objeto el que se envio es porque no contiene operadores por ende es falso
|
|
28
|
+
if (typeof value_find !== 'object') {return false}
|
|
29
|
+
|
|
30
|
+
// Se obtiene la lista de operadores a trabajar
|
|
31
|
+
let operators_list_find = Object.keys(value_find)
|
|
32
|
+
|
|
33
|
+
// Se verifica si es mas de uno. En caso de serlo se itera por cada operador enviando los resultados a un array
|
|
34
|
+
if (operators_list_find.length > 1) {
|
|
35
|
+
|
|
36
|
+
let result_filter = []
|
|
37
|
+
for (let key_operator of operators_list_find) {
|
|
38
|
+
|
|
39
|
+
operator_function_execute = operators[key_operator]
|
|
40
|
+
if (!operator_function_execute) {
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
result_filter.push(operator_function_execute(one_doc[key_find], value_find[key_operator]))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Cuando se termina la iteracion, se verifica que el array contenga todas las respuestas como true.
|
|
48
|
+
return result_filter.every(x => x === true)
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Value find es esto: {"$ne": "ola"}
|
|
53
|
+
let operator = operators_list_find[0] // Esto se transforma en $ne
|
|
54
|
+
let operator_value = value_find[operator] // Esto se transforma en 'ola'
|
|
55
|
+
|
|
56
|
+
let operator_in = operators[operator] // Operator in obtiene la funcion almacenada en el diccionario
|
|
57
|
+
|
|
58
|
+
// Si no existe un operador valido o no existe una funcion asociada, se retorna falso
|
|
59
|
+
if (!operator || !operator_in) {
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// One doc value es el valor del documento tiene para comparar
|
|
64
|
+
let one_doc_value = one_doc[key_find]
|
|
65
|
+
|
|
66
|
+
// Se pasan los dos parametros a comparar dependiendo del operador
|
|
67
|
+
return operator_in(one_doc_value, operator_value)
|
|
68
|
+
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
72
|
+
operators_find
|
|
73
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
function set(document_object, list_modifications) {
|
|
2
|
+
|
|
3
|
+
let mods_list = Object.keys(list_modifications)
|
|
4
|
+
|
|
5
|
+
for (let key_dict_on_document of mods_list) {
|
|
6
|
+
document_object[key_dict_on_document] = list_modifications[key_dict_on_document]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return document_object
|
|
10
|
+
|
|
11
|
+
}
|
|
12
|
+
function unset(document_object, list_modifications) {
|
|
13
|
+
|
|
14
|
+
let mods_list = Object.keys(list_modifications)
|
|
15
|
+
|
|
16
|
+
for (let key_dict_on_document of mods_list) {
|
|
17
|
+
delete document_object[key_dict_on_document]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return document_object
|
|
21
|
+
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function rename(document_object, list_modifications) {
|
|
25
|
+
let mods_list = Object.keys(list_modifications)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
for (let key_dict_on_document of mods_list) {
|
|
29
|
+
|
|
30
|
+
let buff_doc, is_json
|
|
31
|
+
|
|
32
|
+
// Se intenta copiar el contenido dentro del nombre de la variable
|
|
33
|
+
try {
|
|
34
|
+
buff_doc = JSON.stringify(document_object[key_dict_on_document])
|
|
35
|
+
is_json = true
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
buff_doc = document_object[key_dict_on_document]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Se borra del diccionario original
|
|
42
|
+
delete document_object[key_dict_on_document]
|
|
43
|
+
|
|
44
|
+
// Se verifica que la funcion fue marcada como json
|
|
45
|
+
if (is_json === true) {
|
|
46
|
+
buff_doc = JSON.parse(buff_doc)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Y se crea un nuevo objeto con el nombre ya cambiado con el mismo contenido
|
|
50
|
+
document_object[list_modifications[key_dict_on_document]] = buff_doc
|
|
51
|
+
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return document_object
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function inc(document_object, list_modifications) {
|
|
58
|
+
|
|
59
|
+
let mods_list = Object.keys(list_modifications)
|
|
60
|
+
|
|
61
|
+
for (let key_dict_on_document of mods_list) {
|
|
62
|
+
document_object[key_dict_on_document] += list_modifications[key_dict_on_document]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return document_object
|
|
66
|
+
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function mul(document_object, list_modifications) {
|
|
70
|
+
let mods_list = Object.keys(list_modifications)
|
|
71
|
+
|
|
72
|
+
for (let key_dict_on_document of mods_list) {
|
|
73
|
+
document_object[key_dict_on_document] = document_object[key_dict_on_document] * list_modifications[key_dict_on_document]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return document_object
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function currentDate(document_object, list_modifications) {
|
|
80
|
+
let mods_list = Object.keys(list_modifications)
|
|
81
|
+
|
|
82
|
+
for (let key_dict_on_document of mods_list) {
|
|
83
|
+
document_object[key_dict_on_document] = Date.now()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return document_object
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function push_d(document_object, list_modifications) {
|
|
90
|
+
let mods_list = Object.keys(list_modifications)
|
|
91
|
+
|
|
92
|
+
for (let key_dict_on_document of mods_list) {
|
|
93
|
+
|
|
94
|
+
let list_in_doc = document_object[key_dict_on_document]
|
|
95
|
+
if (!list_in_doc) {
|
|
96
|
+
document_object[key_dict_on_document] = [list_modifications[key_dict_on_document]]
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
document_object[key_dict_on_document].push(list_modifications[key_dict_on_document])
|
|
101
|
+
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return document_object
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function pull_d(document_object, list_modifications) {
|
|
108
|
+
|
|
109
|
+
let mods_list = Object.keys(list_modifications)
|
|
110
|
+
|
|
111
|
+
for (let key_dict_on_document of mods_list) {
|
|
112
|
+
|
|
113
|
+
let list_in_doc = document_object[key_dict_on_document]
|
|
114
|
+
if (!list_in_doc) {
|
|
115
|
+
continue
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
document_object[key_dict_on_document] = document_object[key_dict_on_document].filter(x => x !== list_modifications[key_dict_on_document])
|
|
119
|
+
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return document_object
|
|
123
|
+
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function max(document_object, list_modifications) {
|
|
127
|
+
let mods_list = Object.keys(list_modifications)
|
|
128
|
+
|
|
129
|
+
for (let key_dict_on_document of mods_list) {
|
|
130
|
+
|
|
131
|
+
if (list_modifications[key_dict_on_document] > document_object[key_dict_on_document]) {
|
|
132
|
+
document_object[key_dict_on_document] = list_modifications[key_dict_on_document]
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return document_object
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function min(document_object, list_modifications) {
|
|
140
|
+
let mods_list = Object.keys(list_modifications)
|
|
141
|
+
|
|
142
|
+
for (let key_dict_on_document of mods_list) {
|
|
143
|
+
|
|
144
|
+
if (list_modifications[key_dict_on_document] < document_object[key_dict_on_document]) {
|
|
145
|
+
document_object[key_dict_on_document] = list_modifications[key_dict_on_document]
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return document_object
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function pop_d(document_object, list_modifications) {
|
|
153
|
+
let mods_list = Object.keys(list_modifications)
|
|
154
|
+
|
|
155
|
+
for (let key_dict_on_document of mods_list) {
|
|
156
|
+
|
|
157
|
+
if (!document_object[key_dict_on_document]) {continue}
|
|
158
|
+
|
|
159
|
+
let idx_max = document_object[key_dict_on_document].length
|
|
160
|
+
|
|
161
|
+
// Si la lista solo contiene un elemento, se evita el slice y se retorna directamente vacio
|
|
162
|
+
if (idx_max === 1) {
|
|
163
|
+
document_object[key_dict_on_document] = []
|
|
164
|
+
continue
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Si se desea borrar unicamente el ultimo valor, se borra xd
|
|
168
|
+
if (list_modifications[key_dict_on_document] === 1) {
|
|
169
|
+
document_object[key_dict_on_document].splice(idx_max - 1)
|
|
170
|
+
continue
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Splice 0, 1 se usa porque quiero eliminar el indice 0 y especifico unicamente un elemento
|
|
174
|
+
// a eliminar, o splice toma todo el array completo.
|
|
175
|
+
document_object[key_dict_on_document].splice(0, 1)
|
|
176
|
+
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return document_object
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let operators_update_dict = {
|
|
183
|
+
"$set": set,
|
|
184
|
+
"$unset": unset,
|
|
185
|
+
"$rename": rename,
|
|
186
|
+
"$inc": inc,
|
|
187
|
+
"$mul": mul,
|
|
188
|
+
"$max": max,
|
|
189
|
+
"$min": min,
|
|
190
|
+
"$currentDate": currentDate,
|
|
191
|
+
|
|
192
|
+
"$push": push_d,
|
|
193
|
+
"$pop": pop_d,
|
|
194
|
+
"$pull": pull_d,
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function operators_update(one_doc, operators_project) {
|
|
198
|
+
|
|
199
|
+
let operators_updates = Object.keys(operators_project)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
if (operators_updates.length > 1) {
|
|
203
|
+
|
|
204
|
+
for (let key_update of operators_updates) {
|
|
205
|
+
let function_execute = operators_update_dict[key_update]
|
|
206
|
+
|
|
207
|
+
if (!function_execute) {continue}
|
|
208
|
+
|
|
209
|
+
one_doc = function_execute(one_doc, operators_project[key_update])
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return one_doc
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let function_execute = operators_update_dict[operators_updates[0]]
|
|
216
|
+
|
|
217
|
+
if (!function_execute) {return one_doc}
|
|
218
|
+
|
|
219
|
+
one_doc = function_execute(one_doc, operators_project[operators_updates[0]])
|
|
220
|
+
|
|
221
|
+
return one_doc
|
|
222
|
+
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
module.exports = {
|
|
226
|
+
operators_update
|
|
227
|
+
}
|
package/modules/utils.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const crypto = require("crypto")
|
|
2
|
+
|
|
3
|
+
function monkey_db_return({acknowledged, _id = null, insertedCount = null, insertedIds = null, matchedCount = null, modifiedCount = null, deletedCount = null}) {
|
|
4
|
+
|
|
5
|
+
let return_vars = {
|
|
6
|
+
"acknowledged": acknowledged,
|
|
7
|
+
"_id": _id,
|
|
8
|
+
"insertedCount": insertedCount,
|
|
9
|
+
|
|
10
|
+
"matchedCount": matchedCount,
|
|
11
|
+
"modifiedCount": modifiedCount,
|
|
12
|
+
"insertedIds": insertedIds,
|
|
13
|
+
"deletedCount": deletedCount
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Se itera cada clave valor de el diccionario y se eliminan los vacios
|
|
17
|
+
let key, value
|
|
18
|
+
for ([key, value] of Object.entries(return_vars)) {
|
|
19
|
+
if (value === null) {
|
|
20
|
+
delete return_vars[key]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
console.log(return_vars)
|
|
24
|
+
return return_vars
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalize_url(url = "") {
|
|
28
|
+
if (url.endsWith("/")) {return url.slice(0, -1)}
|
|
29
|
+
return url
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function gen_uuid() {
|
|
33
|
+
let uuid = String(Date.now())
|
|
34
|
+
return crypto.createHash("md5").update(uuid).digest("hex")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = {
|
|
38
|
+
monkey_db_return, normalize_url, gen_uuid
|
|
39
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "monkey-ldb",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "> Una base de datos NoSQL local para Node.js, inspirada en MongoDB y construida con pura curiosidad y ganas de aprender.",
|
|
5
|
+
"main": "MonkeyDB.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/ciberuniverse/MonkeyDB.git"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [],
|
|
14
|
+
"author": "Hernan Miranda",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"type": "commonjs",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/ciberuniverse/MonkeyDB/issues"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/ciberuniverse/MonkeyDB#readme"
|
|
21
|
+
}
|